@[TOC]
一、前言
我们前六篇博文,详细讨论了SpringBoot整个启动流程、自动装配。博文如下:
1> 《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同样也是依靠类加载机制,但是判断的内容要少很多,其仅仅判断<ANY、SERVLET、REACTIVE>三种应用类型对应的类是否存在。
就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
装配失败的表现: