《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)

网友投稿 276 2022-11-28

《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)

文章目录

​​一、前言​​​​二、入口​​

​​第五步> getConfigurationClassFilter().filter(configurations) --> 过滤候选自动装配Class集合中不符合条件装配的Class成员:​​

​​1)获取所有的Filter![在这里插入图片描述](​​《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息​​​; 2> ​​​《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段​​​; 3> ​​​《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)​​​; 4> ​​​《SpringBoot启动流程四》:图文带你debug源码分析SpringApplication运行阶段和运行后阶段​​​。 5> ​​​《SpringBoot启动流程五》:你确定你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)​​​。 6> ​​​SpringBoot自动装配机制原理​​。

在​​《SpringBoot启动流程五》:你确定你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)​​​一文中聊了​​@EnableAutoConfiguration​​​,​​@Import​​​注解是在何时被扫描的?所有的自动自动装配类是何时被扫描/加载的?也埋了一个坑:为什么127个自动装配类经过​​AutoConfigurationImportFilter​​过滤后只剩23个了?

本文我们就这个坑,聊一下Spring自动装配中自动装配类是如何实现条件装配的!!

注:Spring Boot版本:2.3.7.RELEASE。

二、入口

接着上篇博文​​《SpringBoot启动流程五》:你确定你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)​​​中自动装配入口的代码执行流程图来看;核心在于​​AutoConfigurationImportSelector​​​的​​getAutoConfigurationEntry(AnnotationMetadata)​​方法中。

其负责拿到所有自动配置的节点,大致分为六步;

第一步,在​​getCandidateConfigurations()​​​方法中利用Spring Framework工厂机制的加载器​​SpringFactoriesLoader​​​,通过​​SpringFactoriesLoader#loadFactoryNames(Class, ClassLoader)​​​方法读取所有​​META-INF/spring.factories​​​资源中​​@EnableAutoConfiguration​​所关联的自动装配Class集合。第二步,利用Set不可重复性对​​自动装配Class集合​​进行去重,因为自动装配组件存在重复定义的情况;第三步,读取当前配置类所标注的@EnableAutoConfiguration注解的属性exclude和excludeName,并与​​spring.autoconfigure.exclude​​配置属性的值 合并为自动装配class排除集合。第四步,校验自动装配Class排除集合的合法性、并排除掉​​自动装配Class排除集合​​中的所有Class(不需要自动装配的Class)。第五步,再次过滤后候选自动装配Class集合中不符合条件装配的Class成员;最后一步,触发自动装配的导入事件。

第五步> getConfigurationClassFilter().filter(configurations) --> 过滤候选自动装配Class集合中不符合条件装配的Class成员:

这里分两步:

1)获取所有的Filter

首先通过​​getConfigurationClassFilter()​​​方法从所有​​META-INF/spring.factories​​​文件中获取所有的自动装配过滤器​​AutoConfigurationImportFilter​​​的实现类(一共有三个),然后实例化​​AutoConfigurationImportSelector​​​类的内部类​​ConfigurationClassFilter​​​,并将获取多的所有​​AutoConfigurationImportFilter​​​的实现类集合赋值到ConfigurationClassFilter的​​filters​​属性中(后面会用到它做条件装配)。

2)执行条件装配

然后对获取到的所有自动装配类(最干净、最简单的SpringBoot程序有127个)执行过滤操作(条件装配)后,还剩23个自动装配类。

那么是如何做的条件装配呢?

三、条件装配原理

首先遍历在​​getConfigurationClassFilter()​​​方法中获取到的三个过滤器:OnClassCondition、OnWebApplicationCondition、OnBeanCondition;分别执行他们的​​match()​​方法做条件装配,最后将符合条件的结果放入到一个List集合中返回。下面以实际样例来看一下,这三个过滤器是如何对自动装配类做条件装配的?

无论是OnClassCondition、OnWebApplicationCondition 还是OnBeanCondition他们都继承自​​FilteringSpringBootCondition​​​类,并且它们三个之中都没有重写​​FilteringSpringBootCondition​​​#​​match(String[], AutoConfigurationMetadata)​​​方法,所以条件装配判定逻辑的入口统一为​​FilteringSpringBootCondition​​​#​​match(String[], AutoConfigurationMetadata)​​:

其中调用的getOutcomes(String[],AutoConfigurationMetadata)方法里针对不同的​​FilteringSpringBootCondition​​子类而实现逻辑不同,下面分开看一下。

1、OnClassCondition

OnClassCondition#getOutComs()方法代码执行流程如下:

​​​resolveOutcomesThreaded()​​方法源码解释如下:

private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { // 将自动装配类的条件装配一分为2进行处理, int split = autoConfigurationClasses.length / 2; // 这里会开启一个线程处理前半部分的自动装配类 OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata); OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader()); // 当前线程处理后半部分的自动装配类 ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes(); // 这里调用thread.join()方法等待上面开启的线程run()执行完毕(即:处理完前半部分自动装配类) ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes(); // 最后将处理结果合并 并 返回。 ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length]; System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length); System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length); return outcomes;}

这里挺有意思的,它首先将所有的自动装配类一分为二,前半部分开启一个线程进行处理、后半部分当前线程处理,后半部分处理完之后,调用thread.join()等待thread.join()方法等待上面开启的线程run()执行完毕(即:处理完前半部分自动装配类)。最后合并处理结果并返回。

1> 创建线程处理前半部分自动装配类:

2> 处理后半部分自动装配类:

从这里我们来看条件装配的细节;

1)条件装配逻辑判断

代码逻辑在OnClassCondition静态内部类​​StandardOutcomesResolver#resolveOutcomes()​​方法中:

在getOutcomes(String[],int,int,AutoConfigurationMetadata)方法中:

遍历传入的所有自动装配类名称;首先获取自动装配类​​autoConfigurationClass​​上@ConditionalOnClass注解中的值;如果获取不到,继续下一个循环。获取到相应的类,则通过类加载机制判断其是否存在,如果能加载到则返回。。。。 否则返回null。这里使用类加载机制判断Class是否存在时,不会对Class执行初始化操作。

以​​org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration​​类为例,细看一下其@ConditionalOnClass条件装配逻辑;

JmxAutoConfiguration类上被​​@ConditionalOnClass({ MBeanExporter.class })​​​注解标注,所以获取到的​​candidates​​​局部变量值为:​​MBeanExporter​​。接着来看如何判断MBeanExporter.class类是否存在,代码执行流程如下:

代码流程解析:

根据当前ClassLoader使用反射Class.forNam()加载类:如果能加载到,则给最上层的getOutComes(String)方法返回FALSE;如果加载不到返回一个​​ConditionOutcome​​对象,内容为:@ConditionalOnClass did not find required class ‘org.springframework.jms.core.JmsTemplate’。(JmsTemplate为某自动装配类,是一个变量)如果@ConditionalOnClass中存在多个Class,则for循环判断,只要有一个Class不存在,则直接返回返回一个​​ConditionOutcome​​对象,内容为:@ConditionalOnClass did not find required class ‘org.springframework.jms.core.JmsTemplate’。(JmsTemplate为某自动装配类,是一个变量)

就​​JmxAutoConfiguration​​而言,其可以被自动装配。

最后返回到条件装配判定逻辑的入口统一​​FilteringSpringBootCondition​​​#​​match(String[], AutoConfigurationMetadata)​​的体现为:

就​​JmxAutoConfiguration​​​而言,其在​​String[] autoConfigurationClasses​​​数组中的下标位置为63,而返回的​​boolean[] match​​数组下标位置63处为TRUE,表示其可以被自动装配。

此外:SpringBoot提供了两个基于Class的条件注解:一个是@ConditionalOnClass(类加载器中存在指定的类),另一个是@ConditionalOnMissingClass(类加载器中不存在指定的类),@ConditionalOnClass条件装配的原理我们知道了,@ConditionalOnMissingClass我们同样也就知道了。区别在于@ConditionalOnMissingClass加载到类之后给最上层的getOutComes(String)方法返回TRUE。

OnClassCondition过滤完,候选的自动装配类还剩26个:

继续进入第二个过滤器OnWebApplicationCondition开始第二轮过滤;

2、OnWebApplicationCondition

OnWebApplicationCondition的执行逻辑和OnClassCondition一样,区别在于:

OnClassCondition判断的是@ConditionalOnClass注解,OnWebApplicationCondition判断的是@ConditionalOnWebApplication注解。OnClassCondition依靠类加载机制判断@ConditionalOnClass注解value属性中的Class是否存在;OnWebApplicationCondition同样也是依靠类加载机制,但是判断的内容要少很多,其仅仅判断三种应用类型对应的类是否存在。

就​​org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration​​而言:

其没被​​@ConditionalOnWebApplication​​​注解标注,所以在​​getOutcome(String type)​​方法中直接返回null,否者需要走应用类型的判断逻辑。

上图的两个常量如下:

// SERVLET应用特有类private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";// Reactive应用特有类private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";

如果这两个类同时存在,优先走SERVLET应用类型,这个在之前的博文(​​SpringBoot应用分类?​​)聊过。

最后​​SpringApplicationAdminJmxAutoConfiguration​​类可以被自动装配;

OnWebApplicationCondition过滤完,候选的自动装配类还剩24个:

继续进入第三个过滤器OnBeanCondition开始第三轮过滤;

3、OnBeanCondition

OnBeanCondition#​​getOutcomes(String[],AutoConfigurationMetadata)​​方法执行逻辑如下:

遍历所有的(24个)候选自动装配类;首先获取自动装配类​​autoConfigurationClass​​上@OnBeanCondition注解中的值;如果获取不到,继续下一个循环。获取到相应的类,则通过类加载机制判断其是否存在,如果能加载到则返回。。。。 否则返回null。这里使用类加载机制判断Class是否存在时,不会对Class执行初始化操作。

以​​org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration​​类为例,细看一下其@OnBeanCondition条件装配逻辑;

CacheAutoConfiguration类上被​​@ConditionalOnBean(CacheAspectSupport.class)​​​注解标注,所以获取到的​​onBeanTypes​​​局部变量值为:​​CacheAspectSupport​​。

接着来看如何判断CacheAspectSupport类的Bean实例是否存在,代码执行流程如下:

看到最后我人傻了,卧槽,这和我学的@ConditionalOnBean注解的含义不一样啊,它不是要判断指定的Class是否已经被实例化存在于Spring容器中吗??

但是从代码debug流程上来看它居然也是判断当前类加载器是否可以加载到Class类。

就​​CacheAutoConfiguration​​而言,其可以被自动装配。

最后返回到条件装配判定逻辑的入口统一​​FilteringSpringBootCondition​​​#​​match(String[], AutoConfigurationMetadata)​​的体现为:

就​​CacheAutoConfiguration​​​而言,其在​​String[] autoConfigurationClasses​​​数组中的下标位置为4,而返回的​​boolean[] match​​数组下标位置4处为TRUE,表示其可以被自动装配。

1、验证加载自动装配类时@ConditionalOnBean注解是针对Class而不是Bean!!

但是从代码debug流程上来看@ConditionalOnBean居然也是判断当前类加载器是否可以加载到Class类。

虽然代码debug出来了,但我还是不敢相信!所以我要写个demo验证一下!!

首先自定义一个自动装配类:TestSaintAutoConfiguration

@Configuration@ConditionalOnBean(MyConditionBean.class)public class TestSaintAutoConfiguration { static { System.out.println("TestSaintAutoConfiguration类自动装配了"); }}

MyConditionBean类上不加任何注解,不让其注册到Spring容器中,仅使其可以在Class.forName时加载到。

public class MyConditionBean { public MyConditionBean() { System.out.println("MyConditionBean"); }}

整体项目目录如下:

如何自定义自动装配类,参考博文:​​SprinBoot自定义自动装配类与xxx-spring-boot-starter​​​。经过上述过滤器过滤之后,我们的自动装配类​​TestSaintAutoConfiguration​​通过了过滤,”可以被自动装配“!!!

呀,真的可以自动装配了耶!!

执行运行程序并将自动装配的信息打出来试试看呢!

首先要在application.yml文件中增加配置​​debug: true​​,打印自动装配信息。

控制台输出如下:

卧槽,卧槽,卧槽,不对呀!这里没自动装配​​TestSaintAutoConfiguration​​类啊。

推测肯定后面又做了某些处理!!!

代码一直往上返回,追到最上层获取所有自动装配类的地方 --> ConfigurationClassParser的内部类的​​processGroupImports()​​方法中:

又进到了ConfigurationClassParser#processConfigurationClass()方法中,你会发现这里​​ConfigurationPhase​​​是​​PARSE_CONFIGURATION​​​而不是​​REGISTER_BEAN​​​也就意味着,这里依旧会跳过条件装配,继续解析流程,啊啊啊啊,到底在哪呢?(此时博主已即将崩溃,但是博主没放弃,又找了好久好久,终于找到了)。回到处理自动装配的核心逻辑​​ConfigurationClassPostProcessor​​​#​​processConfigBeanDefinitions(BeanDefinitionRegistry)​​方法:

关于@ConditionalOnBean的处理逻辑,下篇文章<《SpringBoot启动流程七》:@Conditional条件装配实现原理(待补充博文链接)>中再做讨论。

下图为我们自定义的自动装配类​​TestSaintAutoConfiguratioin​​装配失败的表现:

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java SpringMVC异步处理详解
下一篇:基于FPGA设计的医学监测用视力测试仪设计
相关文章

 发表评论

暂时没有评论,来抢沙发吧~