提出问题
在日常开发中有时会遇到这么几种问题:
- SpringBoot的自动装配是在哪个阶段生效
- @AutoConfigureAfter,@AutoConfigureBefore在什么情况下用到
- 为什么要有DeferredImportSelector
- 有时被@ComponentSacn扫到的AutoConfiguration不满足@Conditional条件
- SpringBoot三大注解(@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan)如何工作
要清晰理解上述问题,有一个很关键的类绕不过,这就是ConfigurationClassPostProcessor,下文通过分析其源码,探寻前三个问题的答案.至于最后两个问题,会在下一篇SpringBoot三大注解的文章中解答.请注意,要理解SpringBoot三大注解,必须要明白ConfigurationClassPostProcessor的逻辑处理.
ConfigurationClassPostProcessor源码探究
ConfigurationClassPostProcessors是Spring中很很很重要的后置处理器了,为什么这么说?首先他实现了BeanDefinitionRegistryPostProcessors,属于BeanFactoryPP,比一般的BeanFactoryPP优先级还高,其次他实现了PriorityOrdered,这基本是最优先执行的后置处理器了.然后从功能上看,他负责解析@Configuration,@Import等一系列最重要的注解,所以说他是最重要的后置处理器一点也不为过.接下来我们逐步揭开他神秘的面纱.
首先我们要从整体上将它分为两个阶段,分别是
- Configuration解析阶段,即找出所有符合的Configuration,包括一层层嵌套的Configuration
- BeanDefinition注册阶段,即根据找出的Configuration,将其生成的BeanDefinition注册到BeanFactory
1 Configuration解析阶段
1.1 postProcessBeanDefinitionRegistry
毫无疑问,这个实现方法包含了最重要的逻辑
他首先会找出目前BeanFactory已经注册了的Configuration候选者(通常是主启动类,在启动中的PrepareContext处注册),逐个对其解析处理
什么是Configuration候选者,一共分为Full mode 和Lite mode两类
- Full mode: 被@Configuration注解且proxyBeanMethods=true的
- Lite mode: 剩下proxyBeanMethods=false + 被@Component,@ComponentScan,@Import,@ImportResource注解 + 方法有@Bean的.
- 在lite模式下,@Bean只会被当成工厂方法,不是从容器中取,所以每次出来的对象可能都不是同一个
1.2 解析处理过程核心方法processConfigurationClass
进来先判断这个候选者在PARSE_CONFIGURATION阶段是否符合条件,不符合就跳过
接着是doProcessConfigurationClass(这里不贴源码了,因为逻辑很深,递归很多,还是用图片+文字更直接):
- 1.如果是@Component,则递归这个方法先处理内部类
- 2.接着处理@PropertySource
- 3.然后处理@ComponentScan,将扫到的类注册到BeanFactory,再检查扫到的类是否符合Configuration候选者条件,是的话递归该方法重复上述步骤
- 4.处理@Import,分ImportSelector,DeferredImportSelector,ImportBeanDefinitionRegistrar和普通处理
- 4.1 ImportSelector, 立刻调用selectImports方法,得到import的类,再对这些类递归4.步骤
- 4.2 DeferredImportSelector, 将这个selector放在待处理队列,延后处理(递归调用4.步骤)
- 4.3 ImportBeanDefinitionRegistrar,记录起来,等到loadBeanDefinitions时再调用它的注册方法
- 4.4 其余进入常规处理,被当成Configuration候选者,递归调用该方法
你会发现这些基本都是递归,为的就是一层层找出被引入的Configuration
- 5.再处理@ImportResource配置文件,添加到configClass的importedResources
- 6.再处理@Bean方法,添加到configClass的beanMethods
- 7.再处理接口上的默认方法,也是添加到configClass的beanMethods
- 8.最后递归非JDK的父类
被找出来的每个候选者(包括递归找到的),都会被加入到configurationClasses
图中蓝色线条是主流程,绿色线条是递归调用,可以发现存在大量递归
1.3 DeferredImportSelector延迟处理
完成上述步骤后,再将刚才放到待处理队列的DeferredImportSelector找出来,此时同理调用selectImports方法,然后对返回的类调用4.步骤
在这个阶段如果再产生新的DeferredImportSelector,会立马执行,跟普通ImportSelector差不多 为什么要加入延后处理,因为对于某些特殊的Configuration,他有被@Conditional注解,需要在其他Configuration加载后才适合进行条件判断
1.4 @EnableAutoConfiguration
为什么这里要特意说@EnableAutoConfiguration?它是SpringBoot自动装配最重要的注解,他本质上引入了AutoConfigurationImportSelector,是一个DeferredImportSelector,生效的时机就是现在.为什么要放到Configuration解析阶段的最后呢?因为自动装配是根据之前解析过的Configuration作为前置条件,必须放到最后.同时自动装配类自身也有前后顺序的关系,通过@AutoConfigureAfter,@AutoConfigureBefore来确定自动装配类的前后关系 再多说一下这个类的逻辑处理:
- 1.获取spring.factories里的所有EnableAutoConfiguration
- 2.获取spring-autoconfigure-metadata.properties,将其作为后续filter的metadata.
- 3.根据EnableAutoConfiguration的exclude配置进行过滤
- 4.取出spring.factories里的所有AutoConfigurationImportFilter准备过滤,默认就三个,OnBeanCondition,OnClassCondition,OnWebApplicationCondition.
- 5.根据第二步取出的metadata,结合三个Filter的getOutcomes方法进行过滤,值得一提的是,由于此时还没有准备好BeanDefinition,所以OnBeanCondition降级成OnClassCondition一样的处理,即判断classpath上存在某个类
为什么这里要过滤呢?个人认为大部分AutoConfiguration都不会被使用到,在类还没加载之时就通过初步的条件筛查,能节省资源
所以说,我们生成自己的自动装配类时,一定要生成spring-autoconfigure-metadata.properties,这才是Spring官方推荐的做法.当然你不生成也不会有大问题,只是这些类都会进入1.2processConfigurationClass处理.
另外,在他的selectImports方法里会对所有自动装配类排序,此时排序会找出所有@AutoConfigureAfter,@AutoConfigureBefore一起参与排序(即便不符合条件的),这样的做法是为了获取更多排序信息,得出准确的排序.
例如A-->B-->C-->D, 但此时B和C不符合条件,只剩下A和D,仅依靠A和D无法确认顺序,必须让B和C一起参与,才能确定A和D的顺序
2 BeanDefinition注册阶段
2.1 loadBeanDefinitions是真正注册的地方
他会根据上文解析出来的configurationClasses,逐个取出configurationClass进行如下处理:
请注意:configurationClass的数据结构是LinkedHashSet,所以他是按解析的先后顺序进行处理
- 1.判断这个Configuration在REGISTER_BEAN阶段是否符合条件,这里补充了1.2缺失的阶段,即便在1.2符合条件,但在REGISTER_BEAN阶段不符合条件,还是会被跳过
- 2.如果这个Configuration是被import的,就将自身注册到BeanFactory
- 3.处理BeanMethods,逐个取出BeanMethod,检查在REGISTER_BEAN阶段是否符合条件,如果符合则生成BeanDefinition,并注册到BeanFactory
- 4.处理importedResources
- 5.处理importBeanDefinitionRegistrars,调用registerBeanDefinitions方法,并注册到BeanFactory
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 1.判断这个Configuration在**REGISTER_BEAN**阶段是否符合条件
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
// 2.如果这个Configuration是被import的,就将自身**注册**到BeanFactory
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// 3.处理BeanMethods,逐个取出BeanMethod,检查在**REGISTER_BEAN**阶段是否符合条件并注册
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 4.处理importedResources
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 5.importBeanDefinitionRegistrars
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
2.2 Condition的生效阶段
SpringBoot中常用到的ConditionOnClass,ConditionOnBean,其实是分为两种生效阶段的.
- PARSE_CONFIGURATION 即Configuration解析阶段,此时ConditionOnBean这些条件不生效,全部认为符合条件
- REGISTER_BEAN 即Configuration解析完成后的BeanDefinition注册阶段,将BeanDefinition注册到BeanFactory,此时ConditionOnBean这类条件生效
3 postProcessBeanFactory
ConfigurationClassPostProcessor也实现了postProcessBeanFactory方法,主要调用了enhanceConfigurationClasses,用来增强Full mode的Configuration,根据官方注释的介绍,Full mode会使用CGLIB运行时对Configuration进行增强,即便在用户代码里调用@Bean方法,也能够获取容器里的实例.
4 为什么Configuration的顺序很重要
在PARSE_CONFIGURATION阶段,将Configuration按顺序放入LinkedHashSet中,在REGISTER_BEAN阶段,按照同样的顺序取出并注册,在这两个阶段都存在条件判断,顺序较后的Configuration依赖顺序靠前的,如果顺序颠倒,可能会导致存在依赖关系的Configuration无法生效.
5 Bean注册的冲突解决
有时我们在启动的时候会遇到Bean冲突,Spring的解决方式具体分以下几种情况
5.1 ClassPathBeanDefinitionScanner#checkCandidate
ClassPathBeanDefinitionScanner是@ComponentScan的扫描器,在他的checkCandidate里会检查是否发生Definition覆盖重写的问题,不会等到DefaultListableBeanFactory#registerBeanDefinition的时候再暴露
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (!this.registry.containsBeanDefinition(beanName)) {
return true;
}
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
// 检查原本已存在的Definition和扫描到的Definition是否兼容
if (isCompatible(beanDefinition, existingDef)) {
return false;
}
// 如果不兼容,直接抛出异常,不会覆盖重写,防止扫描到大量definition发生覆盖重写的问题
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}
5.2 loadBeanDefinitionsForBeanMethod
注意,这里关于@Bean还有一个特别的地方,即@Bean的权限比较高,可以覆盖Scan产生和框架级别的BeanDefinition
protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) {
// 省略...
BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);
// 省略...
// A bean definition resulting from a component scan can be silently overridden
// by an @Bean method, as of 4.2...
// 在这里,@Bean可以覆盖Scan产生的BeanDefinition
if (existingBeanDef instanceof ScannedGenericBeanDefinition) {
return false;
}
// Has the existing bean definition bean marked as a framework-generated bean?
// -> allow the current bean method to override it, since it is application-level
// 应用自定义的BeanDefinition也能覆盖框架的
if (existingBeanDef.getRole() > BeanDefinition.ROLE_APPLICATION) {
return false;
}
// 省略...
return true;
}
5.3 DefaultListableBeanFactory#registerBeanDefinition
registerBeanDefinition是最常用的注册BeanDefinition,对于覆盖重写有这么几个通用处理
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 省略...
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// 是否允许覆盖重写,默认是允许的
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
// 如果是框架类的Definition覆盖了用户的,会打出info级别的提示,这时候开发人员要重点关注
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
// 如果是相同级别的覆盖重写,打印出debug级别的提示
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 如果只是两个等价的Definition覆盖(例如多次相同扫描等),只会打印trace级别,基本不需要关注
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 省略...
}
6 解决问题
现在我们可以来回答一开始提出的三个问题了:
- SpringBoot的自动装配是在哪个阶段生效
答:在DeferredImportSelector的延迟处理时生效,
- @AutoConfigureAfter,@AutoConfigureBefore在什么情况下用到
答:在对自动装配有相互依赖时使用,控制他们的解析顺序
- 为什么要有DeferredImportSelector
答:DeferredImportSelector的一个典型应用就是自动装配,它会依赖其他前置Configuration,所以必须放在最后的延时处理,才有了这个接口