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.mzph.cn/news/31843.shtml

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

相关文章

一行JS代码导出ant-design中复杂table表格的Excel

使用方式 1、安装依赖 npm install xlsx-js-style2、复制代码文件exportExcel.js至工程 https://github.com/EnthuDai/export-excel-in-one-line 3、在引入excel.js后调用 Excel.export(columns, dataSource, 导出文件名)4、代码demo 5、效果 页面excel 适用范围 对于使…

16bit、8 通道、500kSPS、 SAR 型 ADC——MS5188N

MS5188N 是 8 通道、 16bit 、电荷再分配逐次逼近型模数 转换器&#xff0c;采用单电源供电。 MS5188N 拥有多通道、低功耗数据采集系统所需的所有 组成部分&#xff0c;包括&#xff1a;无失码的真 16 位 SAR ADC &#xff1b;用于将输入配 置为单端输入&#xff0…

Spring IoC 详解

目录 一、引言二、Spring Bean三、将一个类声明为 Bean 所涉及的注解四、Component 和 Bean 的区别五、注入 Bean 的注解六、Autowired 和 Resource 的区别七、Bean7.1 作用域7.2 线程安全7.3 生命周期 一、引言 IoC&#xff08;Inversion of Control:控制反转&#xff09; 是…

【AndV】ant-design-vue中select使用mode=“combobox“无效:

文章目录 一、问题:二、解决: 一、问题: Warning: [antdv: Select] The combobox mode of Select is deprecated,it will be removed in next major version,please use AutoComplete instead 二、解决: 将mode"combobox"改为mode"SECRET_COMBOBOX_MODE_DO_NOT_…

机器学习深度学习——注意力提示、注意力池化(核回归)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——常见循环神经网络结构&#xff08;RNN、LSTM、GRU&#xff09; &#x1f4da;订阅专栏&#xff1a;机器…

外网通过ipv6访问家里设备

目录 1.需要整体理解如何在外网连接家里设备。 2.路由器打通ipv6。 3.移动光猫配置ipv6。 4.test-ipv6.com测试成功&#xff0c;但是ping不通 还是ping不通&#xff0c;提出如下可能 5.动态域名解析&#xff08;ddns-go&#xff09; a.dns服务商权限设置 b.IPv6设置 c…

rv1126设置静态ip

开发板配网--------------------------------------------------------------------------------------------- 刚拿到的开发板里面的网络配置大多不可用&#xff0c;此时是无法ping通的&#xff0c;这个时候需要重新修改相关的配置文件&#xff1b; Vi /etc/profile 最后面…

竞赛项目 深度学习图像风格迁移

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

Qt 多线程、信号和槽连接方式推荐connect(Sender,Singnal,Receiver,Slot,ConnectMode);如下图所示

connect&#xff08;主线程A&#xff0c;信号A,子线程B,槽函数B,DirectConnection /AutoConnection ); connect&#xff08;子线B,信号B,主线程A,槽函数A,QueueConnection );

无涯教程-Perl - grep函数

描述 此函数从LIST中提取EXPR为TRUE的所有元素。 语法 以下是此函数的简单语法- grep EXPR, LIST返回值 此函数返回在标量context中表达式返回true的次数以及在列表context中与表达式匹配的元素列表。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perllist (1,&qu…

为c语言安装easyx图形库

按照图上的步骤&#xff0c;安装easyx图形库。 接下来看代码&#xff1a; #include<easyx.h> #include<stdio.h> #define width 800 #define height 600int main() {initgraph(width, height); // 初始化窗口&#xff08;宽度&#xff0c; 高度&#xff09;…

SAS-proc transpose转置

一、语法 by&#xff1a;纵向变量&#xff0c;不转置&#xff0c;保留的变量&#xff0c;by使用需要先排序。 id&#xff1a;需要转置的变量。 var&#xff1a;新数据集中的数据。 idlabel&#xff1a;转置变量的标签。 copy&#xff1a;不转置的变量直接拷贝到输出数据集中。…

C语言必会题目(1)

W...Y的主页 &#x1f60a; 代码仓库分享❤️ 在学习语言时&#xff0c;最重要的就是练习&#xff0c;光听不练假把式。下面我就推荐一些C语言必会的题。 执行下面程序&#xff0c;正确的输出是&#xff08; &#xff09; int x5,y7; void swap() { int z; zx; xy; yz; } int…

北航基于openEuler构建工业机器人操作系统,打造“开箱即用”的机器人基础软件平台

北京航空航天大学是国家“双一流”建设高校&#xff0c;以建设扎根中国大地的世界一流大学为发展目标。北京航空航天大学在机器人领域一直处于行业前沿&#xff0c;以其亮眼的成果和优秀的师资力量&#xff0c;成为国内机器人领域的重要参与者和建设者。机器人操作系统是机器人…

【Kubernetes】Kubernetes之Kubeadm部署

Kubeadm 一、Kubeadm 部署1. 环境准备2. 所有节点安装 docker3. 所有节点安装 kubeadm&#xff0c;kubelet 和 kubectl4. 部署 K8S 集群4.1 配置 master01 节点4.2 配置 node 节点 二、Kubeadm 高可用部署1. 环境准备2. 所有节点安装 docker2. 所有节点安装kubeadm&#xff0c;…

Substack 如何在去中心化内容创作领域掀起波澜

面对数字内容广告化的困境&#xff0c;Substack回归做内容的初心&#xff0c;通过产品和平台双轮驱动&#xff0c;重塑一个去中心化的多元文化内容聚集地&#xff0c;实现了增长突破。其核心策略在于先使用简洁的创作工具赋能内容生产&#xff0c;进而通过平台的互动机制促进用…

链式二叉树统计结点个数的方法和bug

方法一&#xff1a; 分治&#xff1a;分而治之 int BTreeSize1(BTNode* root) {if (root NULL) return 0;else return BTreeSize(root->left)BTreeSize(root->right)1; } 方法二&#xff1a; 遍历计数&#xff1a;设置一个计数器&#xff0c;对二叉树正常访问&#…

Git入门到精通——保姆级教程(涵盖GitHub、Gitee、GitLab)

文章目录 前言一、Git1.Git-概述1.1.Git-概述-版本控制介绍1.2.Git-概述-分布式版本控制VS集中式版本控制1.3.Git-概述-代码托管中心1.4.Git-概述-安装和客户端的使用 2.Git-命令(常用命令)2.1.Git-命令-设置用户签名2.2.Git-命令-初始化本地库2.3.Git-命令-查看本地库状态2.4.…

如何解决新学期分班查询公布难题?试试这个方法

作为教师&#xff0c;我们常常需要进行学生分班管理&#xff0c;这项工作繁琐但至关重要。为了提高工作效率&#xff0c;我们可以利用现代技术开发一款学生分班查询系统。本文将介绍如何设计和开发这个系统。 首先&#xff0c;我们可以选择使用易查分这样的工具来帮助我们管理…

Java多线程(九)

目录 一、synchronized基本特点 二、synchronized加锁工作过程 2.1 无锁 2.2 偏向锁 2.3 轻量级锁 2.4 重量级锁 三、synchronized其他优化操作 3.1 锁消除 3.2 锁粗化 一、synchronized基本特点 开始是乐观锁&#xff0c;如果锁冲突频繁就会转换成悲观锁开始是轻量级锁&#x…