SpringBoot源码分析(8)--内置ApplicationContextInitializer

文章目录

  • 1、DelegatingApplicationContextInitializer
  • 2、SharedMetadataReaderFactoryContextInitializer
  • 3、ContextIdApplicationContextInitializer
  • 4、ConfigurationWarningsApplicationContextInitializer
  • 5、ServerPortInfoApplicationContextInitializer
  • 6、ConditionEvaluationReportLoggingListener
  • 7、RSocketPortInfoApplicationContextInitializer

本文基于spring-boot-2.2.14.BUILD-SNAPSHOT源码分析。

本篇文章是对上篇prepareContext的补充,在该方法的执行过程中,遍历了最初从META-INF/spring.factories文件中加载到的ApplicationContextInitializer,依次调用了其initialize方法。
在这里插入图片描述

protected void applyInitializers(ConfigurableApplicationContext context) {for (ApplicationContextInitializer initializer : getInitializers()) {Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");initializer.initialize(context);}}

SpringApplication初始化的时候,一共加载到了7个内置的ApplicationContextInitializer,本篇文章就逐一分析每个内置的初始化器做了哪些事情

spring-boot/src/main/resources/META-INF/spring.factories

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

1、DelegatingApplicationContextInitializer

获取环境中属性context.initializer.classesInitializer配置的ApplicationContextInitializer列表, 执行其initialize()方法。

SpringBoot允许我们通过各种属性配置方式自定义一些ApplicationContextInitializer,它们的调用时机就是该类的initialize方法

//代理初始化器
public class DelegatingApplicationContextInitializer implementsApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {private static final String PROPERTY_NAME = "context.initializer.classes";@Overridepublic void initialize(ConfigurableApplicationContext context) {ConfigurableEnvironment environment = context.getEnvironment();//获取environment中配置的context.initializer.classes属性//其值为各个initializer,用逗号分隔开List<Class<?>> initializerClasses = getInitializerClasses(environment);if (!initializerClasses.isEmpty()) {//获取到各个initializer,并执行其initialize()方法applyInitializerClasses(context, initializerClasses);}}}

进入getInitializerClasses方法

private static final String PROPERTY_NAME = "context.initializer.classes";private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {String classNames = env.getProperty(PROPERTY_NAME);List<Class<?>> classes = new ArrayList<>();if (StringUtils.hasLength(classNames)) {for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {classes.add(getInitializerClass(className));}}return classes;
}

它从environment中找到context.initializer.classes属性,以逗号为分隔符解析成Class列表并返回

由于这里只通过getProperty方法取了一次,我们之前分析过,这个方法会从前往后遍历所有的PropertySource,取到了就立即返回,所以通过不同方式,比如启动参数、系统配置等不同途径设置的context.initializer.classes,只有优先级最高的那个会生效

然后对于找到的自定义初始化器,调用applyInitializerClasses方法

private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) {Class<?> contextClass = context.getClass();List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();for (Class<?> initializerClass : initializerClasses) {initializers.add(instantiateInitializer(contextClass, initializerClass));}applyInitializers(context, initializers);
}

先实例化,然后调用applyInitializers方法

private void applyInitializers(ConfigurableApplicationContext context,List<ApplicationContextInitializer<?>> initializers) {initializers.sort(new AnnotationAwareOrderComparator());for (ApplicationContextInitializer initializer : initializers) {initializer.initialize(context);}
}

最终调用了它们的initialize方法

2、SharedMetadataReaderFactoryContextInitializer

添加了一个类型为CachingMetadataReaderFactoryPostProcessor的BeanFactoryPostProcessor, CachingMetadataReaderFactoryPostProcessor会做两件事情

  • 注册一个名称为internalCachingMetadataReaderFactory, 类型为SharedMetadataReaderFactoryBean的bean, 用于读取bean的元数据Metadata
  • 获取名称为internalConfigurationAnnotationProcessor, 类型为ConfigurationClassPostProcessor的bean定义, 为其添加name为metadataReaderFactory, value为internalCachingMetadataReaderFactory的internalCachingMetadataReaderFactory
class SharedMetadataReaderFactoryContextInitializer implementsApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {public static final String BEAN_NAME = "org.springframework.boot.autoconfigure."+ "internalCachingMetadataReaderFactory";@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {//spring上下文容器添加一个CachingMetadataReaderFactoryPostProcessorapplicationContext.addBeanFactoryPostProcessor(new CachingMetadataReaderFactoryPostProcessor());}
}

具体类型为CachingMetadataReaderFactoryPostProcessor,跟第一个初始化器的逻辑一样,它也是内部类,并且实现的也是BeanDefinitionRegistryPostProcessor接口,其中postProcessBeanFactory方法为空,所以只需要看其postProcessBeanDefinitionRegistry方法

private static class CachingMetadataReaderFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {private CachingMetadataReaderFactoryPostProcessor() {}............public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {this.register(registry);this.configureConfigurationClassPostProcessor(registry);}......
}

首先register方法从内部类SharedMetadataReaderFactoryBean获取了一个BeanDefinition,并注册到了容器的BeanDefinitionMap中

private void register(BeanDefinitionRegistry registry) {BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean.class, SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean::new).getBeanDefinition();registry.registerBeanDefinition("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory", definition);
}

这个SharedMetadataReaderFactoryBean顾名思义,是一个FactoryBean,同时它实现了BeanClassLoaderAware接口,在这个接口的回调方法setBeanClassLoader中初始化了内部的ConcurrentReferenceCachingMetadataReaderFactory,并在getObject方法返回

static class SharedMetadataReaderFactoryBean implements FactoryBean<ConcurrentReferenceCachingMetadataReaderFactory>, BeanClassLoaderAware, ApplicationListener<ContextRefreshedEvent> {private ConcurrentReferenceCachingMetadataReaderFactory metadataReaderFactory;SharedMetadataReaderFactoryBean() {}public void setBeanClassLoader(ClassLoader classLoader) {this.metadataReaderFactory = new ConcurrentReferenceCachingMetadataReaderFactory(classLoader);}public ConcurrentReferenceCachingMetadataReaderFactory getObject() throws Exception {return this.metadataReaderFactory;}

返回的这个ConcurrentReferenceCachingMetadataReaderFactory,它可以生产一个MetadataReader,这个Reader的作用就是读取类的元数据,包括Class相关的信息,比如是否接口、是否抽象类、是否有注解等等,以及获得注解相关的元数据,比如加了哪些注解等等,在整个Bean的生命周期中起到了非常重要的作用

register方法执行完毕,调用configureConfigurationClassPostProcessor方法

private void configureConfigurationClassPostProcessor(BeanDefinitionRegistry registry) {try {BeanDefinition definition = registry.getBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor");definition.getPropertyValues().add("metadataReaderFactory", new RuntimeBeanReference("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"));} catch (NoSuchBeanDefinitionException var3) {;}
}

先从容器获取了名为internalConfigurationAnnotationProcessor的BeanDefinition,然后把上面生成的metadataReaderFactory设置到它的属性中

在新建Spring容器的时候,会初始化一个BeanDefinitionReader,而这个Reader的初始化过程中,会往容器中注册一个ConfigurationClassPostProcessor,名字就是上面的internalConfigurationAnnotationProcessor,它是Spring容器完成扫描的起点,包括@Component、@Configuration的处理都是在这个类中进行的,而完成这些工作,自然需要解析每个类的元数据,所以它把metadataReaderFactory赋给了ConfigurationClassPostProcessor的属性,后续就会用它来完成一些Bean的元数据解析

3、ContextIdApplicationContextInitializer

初始化容器ID,这个类的作用是给容器设置一个ID,其实就是我们的项目名, 获取属性spring.application.name配置的应用名称, 如果不存在的话, 默认使用application

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {//获取并设置容器IDContextId contextId = getContextId(applicationContext);applicationContext.setId(contextId.getId());//容器beanFactory中注册一个名称为ContextId类名//值为contextId的beanapplicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(),contextId);
}

第一行先获取了一个容器ID对象,然后把ID属性设置到容器中,并把这个ID对象作为一个单例Bean注册到容器的单例池
我们看下这个ContextId怎么来的,进入getContextId方法

//获取ContextID
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {//父容器获取spring.application.name对应的beanApplicationContext parent = applicationContext.getParent();if (parent != null && parent.containsBean(ContextId.class.getName())) {return parent.getBean(ContextId.class).createChildId();}//父容器获取不到,则生成一个contextIdreturn new ContextId(getApplicationId(applicationContext.getEnvironment()));
}

如果有父容器,就根据父容器的ID创建一个子容器ID,格式为 父容器ID - 子容器序号

ContextIdApplicationContextInitializer.ContextId createChildId() {return ContextIdApplicationContextInitializer.this.new ContextId(this.id + "-" + this.children.incrementAndGet());
}

如果没有父容器,就到environment中取spring.application.name属性,没有配置的话默认为application

//获取应用ID
private String getApplicationId(ConfigurableEnvironment environment) {//获取属性:spring.application.nameString name = environment.getProperty("spring.application.name");//如果为空,默认名applicationreturn StringUtils.hasText(name) ? name : "application";
}

将取到的结果作为参数传给ContextId的构造函数

 //ContextId类
class ContextId {//原子Long类private final AtomicLong children = new AtomicLong(0);private final String id;ContextId(String id) {this.id = id;}ContextId createChildId() {//线程安全递增return new ContextId(this.id + "-" + this.children.incrementAndGet());}String getId() {return this.id;}
}

也就是说默认情况下,这个ContextId存了我们的项目名,然后把它设置到了容器中

4、ConfigurationWarningsApplicationContextInitializer

配置告警初始化器(通过分析源码实际情况,默认扫描启动类所在的路径(或者@ComponentScan注解指定的路径)如果系统配置包扫描到了org或者org.springframework包就会发出警告打印warn日志并停止系统启动

在这里插入图片描述
ConfigurationWarningsApplicationContextInitializer初始化器源码如下:

public class ConfigurationWarningsApplicationContextInitializerimplements ApplicationContextInitializer<ConfigurableApplicationContext> {private static final Log logger = LogFactory.getLog(ConfigurationWarningsApplicationContextInitializer.class);//初始化方法会在容器启动时调用,并将ConfigurationWarningsPostProcessor后置处理器注入到应用上下文中@Overridepublic void initialize(ConfigurableApplicationContext context) {context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));}/*** 返回有问题的扫描包(@ComponentScan)Check对象* @return the checks to apply*/protected Check[] getChecks() {return new Check[] { new ComponentScanPackageCheck() };}
}

ComponentScanPackageCheck是ConfigurationWarningsApplicationContextInitializer的一个内部类,源码分析

/**
* 可以应用的单一检查*/
@FunctionalInterface
protected interface Check {/*** 返回检查结果,如果检查失败,则返回警告,如果没有问题,则返回null* @param registry the {@link BeanDefinitionRegistry}* @return a warning message or {@code null}*/String getWarning(BeanDefinitionRegistry registry);}/*** 检查@ComponentScan注解扫描有问题的包*/
protected static class ComponentScanPackageCheck implements Check {private static final Set<String> PROBLEM_PACKAGES;//定义扫描有问题的包static {Set<String> packages = new HashSet<>();packages.add("org.springframework");packages.add("org");PROBLEM_PACKAGES = Collections.unmodifiableSet(packages);}//检查@ComponentScan注解扫描的包是否有问题,如果有,则返回警告,否则返回null@Overridepublic String getWarning(BeanDefinitionRegistry registry) {//获取@ComponentScan注解扫描的包集合Set<String> scannedPackages = getComponentScanningPackages(registry);//获取有问题的扫描包集合List<String> problematicPackages = getProblematicPackages(scannedPackages);if (problematicPackages.isEmpty()) {return null;}return "Your ApplicationContext is unlikely to start due to a @ComponentScan of "+ StringUtils.collectionToDelimitedString(problematicPackages, ", ") + ".";}//获取@ComponentScan注解扫描的包protected Set<String> getComponentScanningPackages(BeanDefinitionRegistry registry) {Set<String> packages = new LinkedHashSet<>();//获取容器中所有bean定义名称String[] names = registry.getBeanDefinitionNames();for (String name : names) {//获取name对应的bean定义对象BeanDefinition definition = registry.getBeanDefinition(name);if (definition instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;addComponentScanningPackages(packages, annotatedDefinition.getMetadata());}}return packages;}//将bean实例上注解@ComponentScan扫描包private void addComponentScanningPackages(Set<String> packages, AnnotationMetadata metadata) {AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(ComponentScan.class.getName(), true));if (attributes != null) {addPackages(packages, attributes.getStringArray("value"));addPackages(packages, attributes.getStringArray("basePackages"));addClasses(packages, attributes.getStringArray("basePackageClasses"));if (packages.isEmpty()) {packages.add(ClassUtils.getPackageName(metadata.getClassName()));}}}private void addPackages(Set<String> packages, String[] values) {if (values != null) {Collections.addAll(packages, values);}}private void addClasses(Set<String> packages, String[] values) {if (values != null) {for (String value : values) {packages.add(ClassUtils.getPackageName(value));}}}//获取有问题的扫描包集合,即包名是:org或org.springframeworkprivate List<String> getProblematicPackages(Set<String> scannedPackages) {List<String> problematicPackages = new ArrayList<>();for (String scannedPackage : scannedPackages) {//判定包名是否有问题,即包名是:org或org.springframeworkif (isProblematicPackage(scannedPackage)) {problematicPackages.add(getDisplayName(scannedPackage));}}return problematicPackages;}//判定包名是否有问题,即包名是:org或org.springframeworkprivate boolean isProblematicPackage(String scannedPackage) {if (scannedPackage == null || scannedPackage.isEmpty()) {return true;}return PROBLEM_PACKAGES.contains(scannedPackage);}private String getDisplayName(String scannedPackage) {if (scannedPackage == null || scannedPackage.isEmpty()) {return "the default package";}return "'" + scannedPackage + "'";}
}

最终在控制台打印如下日志

** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of 'org'.

这里只是打印一行日志,并不会停止程序,不过实际测试下来程序是没办法正常启动的,这个路径是Spring框架本身的包路径,扫描这个包会干扰Spring正常执行流程,陷入循环,当然正常情况下我们项目的路径也不会这样定义。

5、ServerPortInfoApplicationContextInitializer

服务端口初始化器, 分别实现了ApplicationContextInitializer和ApplicationListener接口, 在applicationContext中添加了事件监听器this, 监听了WebServerInitializedEvent事件, 配置服务的端口号

public class ServerPortInfoApplicationContextInitializerimplements ApplicationContextInitializer<ConfigurableApplicationContext>,ApplicationListener<WebServerInitializedEvent> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {//把this添加到Application的listener中applicationContext.addApplicationListener(this);}//监听处理WebServerInitializedEvent事件@Overridepublic void onApplicationEvent(WebServerInitializedEvent event) {//获取environment中配置的server.portsString propertyName = "local." + getName(event.getApplicationContext()) + ".port";setPortProperty(event.getApplicationContext(), propertyName,event.getWebServer().getPort());}
}

6、ConditionEvaluationReportLoggingListener

条件评估日志监听器, 主要作用是给applicationContext添加了一个ConditionEvaluationReportListener监听器, ConditionEvaluationReportListener监听了ContextRefreshedEvent和ApplicationFailedEvent事件, 打印相应日志

public class ConditionEvaluationReportLoggingListenerimplements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {this.applicationContext = applicationContext;//applicationContext中添加一个ConditionEvaluationReportListenerapplicationContext.addApplicationListener(new ConditionEvaluationReportListener());if (applicationContext instanceof GenericApplicationContext) {//注册一个ConditionEvaluationReport beanthis.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());}}
}

先往容器的监听器列表添加一个监听器ConditionEvaluationReportListener,这个类是其内部类,通过supportsEventType方法指定了感兴趣的事件类型为ContextRefreshedEvent和ApplicationFailedEvent

private class ConditionEvaluationReportListener implements GenericApplicationListener {private ConditionEvaluationReportListener() {}
......
......public boolean supportsEventType(ResolvableType resolvableType) {Class<?> type = resolvableType.getRawClass();if (type == null) {return false;} else {return ContextRefreshedEvent.class.isAssignableFrom(type) || ApplicationFailedEvent.class.isAssignableFrom(type);}}......
}

具体在这两个事件做了什么处理,我们后面说到具体事件的时候再来分析

然后下面的if分支,当前Spring容器的类型是AnnotationConfigServletWebServerApplicationContext,它是GenericApplicationContext的子类,所以会进if分支,调用ConditionEvaluationReport的get方法

//用于记录Condition注解的评估情况
public final class ConditionEvaluationReport {//bean名称为autoConfigurationReport//类型为ConditionEvaluationReportprivate static final String BEAN_NAME = "autoConfigurationReport";//从beanFactory中获取ConditionEvaluationReport实例public static ConditionEvaluationReport get(ConfigurableListableBeanFactory beanFactory) {synchronized (beanFactory) {ConditionEvaluationReport report;if (beanFactory.containsSingleton(BEAN_NAME)) {//如果bean已经被注册,立即返回report = beanFactory.getBean(BEAN_NAME, ConditionEvaluationReport.class);}else {//否则注册beanreport = new ConditionEvaluationReport();beanFactory.registerSingleton(BEAN_NAME, report);}locateParent(beanFactory.getParentBeanFactory(), report);return report;}}
}

先到容器中找名为autoConfigurationReport的单例Bean,如果没有的话就新建一个,并存储到容器的单例池,然后调用locateParent方法,如果存在父容器,检查父容器中有没有名为autoConfigurationReport的单例Bean,有的话,将父容器中的Report设置到当前Report的parent属性中

 private static void locateParent(BeanFactory beanFactory, ConditionEvaluationReport report) {if (beanFactory != null && report.parent == null && beanFactory.containsBean("autoConfigurationReport")) {report.parent = (ConditionEvaluationReport)beanFactory.getBean("autoConfigurationReport", ConditionEvaluationReport.class);}}

ConditionEvaluationReport的作用是在SpringBoot自动配置的过程中,打印一些匹配结果的DEBUG日志,包括哪些类完成了自动配置,哪些类的哪些条件没有满足而装载失败等等,比如下图中

============================
CONDITIONS EVALUATION REPORT
============================Positive matches:
-----------------CodecsAutoConfiguration matched:- @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)CodecsAutoConfiguration.JacksonCodecConfiguration matched:- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)..................Negative matches:
-----------------ActiveMQAutoConfiguration:Did not match:- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)AopAutoConfiguration:Did not match:- @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)..................

7、RSocketPortInfoApplicationContextInitializer

添加了一个 RSocketServerInitializedEvent事件的 监听器到 ApplicationContext中。

public class RSocketPortInfoApplicationContextInitializerimplements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {/*** 注入一个端口检查和设置的监听器,对应的事件RSocketServerInitializedEvent**/applicationContext.addApplicationListener(new Listener(applicationContext));}//这里直接写了个内部类实现RSocketServerInitializedEvent事件的监听private static class Listener implements ApplicationListener<RSocketServerInitializedEvent> {private static final String PROPERTY_NAME = "local.rsocket.server.port";private final ConfigurableApplicationContext applicationContext;Listener(ConfigurableApplicationContext applicationContext) {this.applicationContext = applicationContext;}@Overridepublic void onApplicationEvent(RSocketServerInitializedEvent event) {if (event.getServer().address() != null) {setPortProperty(this.applicationContext, event.getServer().address().getPort());}}private void setPortProperty(ApplicationContext context, int port) {if (context instanceof ConfigurableApplicationContext) {setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), port);}if (context.getParent() != null) {setPortProperty(context.getParent(), port);}}private void setPortProperty(ConfigurableEnvironment environment, int port) {MutablePropertySources sources = environment.getPropertySources();PropertySource<?> source = sources.get("server.ports");if (source == null) {source = new MapPropertySource("server.ports", new HashMap<>());sources.addFirst(source);}setPortProperty(port, source);}@SuppressWarnings("unchecked")private void setPortProperty(int port, PropertySource<?> source) {((Map<String, Object>) source.getSource()).put(PROPERTY_NAME, port);}}}

所有的这些初始化类都没有进行启动服务的实质性操作,都是通过注册对象,埋点,后面invokeBeanFactoryPostProcessors才真正调用初始化方法,而且在项目启动之前

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/62050.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JMeter处理接口签名之BeanShell实现MD5加密

项目A需要给项目B提供一个接口&#xff0c;这个接口加密了&#xff0c;现在需要测试这个接口&#xff0c;需要怎么编写脚本呢&#xff1f;实现接口签名的方式有两种&#xff1a;BeanShell实现MD5加密和函数助手实现MD5加密&#xff0c;之前已经分享过了函数助手实现MD5加密&…

生信豆芽菜——箱线图+小提琴图使用说明

网站&#xff1a;http://www.sxdyc.com/diffBoxViolin 三、使用方法 1.打开网址&#xff08;http://www.sxdyc.com/singleCollectionTool?href-diff&#xff09;&#xff0c;选择“箱线图小提琴图” 准备数据,数据格式用为txt文本&#xff0c;以制表符分割 第一个文件为特征…

解锁园区交通新模式:园区低速自动驾驶

在当今科技飞速发展的时代&#xff0c;自动驾驶技术成为了备受关注的领域之一。尤其是在园区内部交通管理方面&#xff0c;自动驾驶技术的应用正在日益受到重视。 园区低速自动驾驶的实现需要多个技术领域的协同合作&#xff0c;包括自动驾驶技术、计算机视觉技术、通信技术、物…

NLP 时事和见解【2023】

一、说明 AI的新闻当然不是即时的&#xff0c;但作为趋势和苗头&#xff0c;我们不得不做出自己的决定。比如&#xff0c;一些软件的支持是否持续&#xff0c;哪些现成的软件将不再使用&#xff0c;等等。 图片来自中途 以下是NLPlanet为您选择的有关NLP和AI的每周文章&#x…

竞赛项目 深度学习的视频多目标跟踪实现

文章目录 1 前言2 先上成果3 多目标跟踪的两种方法3.1 方法13.2 方法2 4 Tracking By Detecting的跟踪过程4.1 存在的问题4.2 基于轨迹预测的跟踪方式 5 训练代码6 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的视频多目标跟踪实现 …

tomcat的多实例,动静分离(web服务基础结束)

多实例 多实例就是在一台服务器上有多个tomcat的服务&#xff08;核心是改端口&#xff09; 实验&#xff1a;多实例 安装步骤 1.安装好 jdk 2.安装 tomcat cd /opt tar zxvf apache-tomcat-9.0.16.tar.gz mkdir /usr/local/tomcat mv apache-tomcat-9.0.16 /usr/local/tomca…

Netty宝典

文章目录 一.NIO1.简介2.缓冲区(Buffer)3.通道(Channel)4.选择器(Selector)5.原理6.SelectionKey7.ServerSocketChannel 和 SocketChannel8.Socket 二.线程模型1.传统阻塞 I/O 服务模型2.Reactor 模式3.单 Reactor 单线程4.单Reactor多线程5.主从 Reactor 多线程6.为什么用Nett…

Grafana+Prometheus技术文档-进阶使用-监控spring-boot项目

阿丹&#xff1a; 之前已经实现了使用Prometheus来对服务器进行了监控和仪表盘的创建&#xff0c;现在就需要对这些监控方法使用在spring-boot中去。 实现思路&#xff1a; 1、集成Actuator 2、加入Prometheus的依赖 3、配置开放端口、以及开放监控 4、配置Prometheus中的配置…

10种常见网站安全攻击手段及防御方法

随着互联网技术的发展&#xff0c;网站所遭受的网络攻击频率也在不断上升。某种程度上&#xff0c;我们可以说互联网上的每个网站都容易遭受安全攻击。因为网络攻击者最主要的动机是求财。无论你运营的是电子商务项目还是简单的小型商业网站&#xff0c;潜在攻击的风险就在那里…

小白到运维工程师自学之路 第七十一集 (kubernetes网络设置)

一、概述 Master 节点NotReady 的原因就是因为没有使用任何的网络插件&#xff0c;此时Node 和Master的连接还不正常。目前最流行的Kubernetes 网络插件有Flannel、Calico、Canal、Weave 这里选择使用flannel。 二、安装flannel 1、master下载kube-flannel.yml&#xff0c;所…

three.js上传模型文件并加载显示

效果大概这样&#xff0c;这个宝箱模型是直接初始化就显示的&#xff0c;人物模型是自己本地添加上去的&#xff0c;代码如下。 <template><div class"container" ref"container"><el-row><el-col :span"24"><div c…

SpringBoot 依赖管理和自动配置---带你了解什么是版本仲裁

&#x1f600;前言 本篇博文是关于SpringBoot 依赖管理和自动配置&#xff0c;希望能够帮助到您&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您…