ConfigurationClassPostProcessor源码探究与SpringBoot三大注解铺垫

1,347 阅读9分钟

提出问题

在日常开发中有时会遇到这么几种问题:

  • SpringBoot的自动装配是在哪个阶段生效
  • @AutoConfigureAfter,@AutoConfigureBefore在什么情况下用到
  • 为什么要有DeferredImportSelector
  • 有时被@ComponentSacn扫到的AutoConfiguration不满足@Conditional条件
  • SpringBoot三大注解(@SpringBootConfiguration, @EnableAutoConfiguration, @ComponentScan)如何工作

要清晰理解上述问题,有一个很关键的类绕不过,这就是ConfigurationClassPostProcessor,下文通过分析其源码,探寻前三个问题的答案.至于最后两个问题,会在下一篇SpringBoot三大注解的文章中解答.请注意,要理解SpringBoot三大注解,必须要明白ConfigurationClassPostProcessor的逻辑处理.

ConfigurationClassPostProcessor源码探究

ConfigurationClassPostProcessors是Spring中很很很重要的后置处理器了,为什么这么说?首先他实现了BeanDefinitionRegistryPostProcessors,属于BeanFactoryPP,比一般的BeanFactoryPP优先级还高,其次他实现了PriorityOrdered,这基本是最优先执行的后置处理器了.然后从功能上看,他负责解析@Configuration,@Import等一系列最重要的注解,所以说他是最重要的后置处理器一点也不为过.接下来我们逐步揭开他神秘的面纱.

首先我们要从整体上将它分为两个阶段,分别是

  1. Configuration解析阶段,即找出所有符合的Configuration,包括一层层嵌套的Configuration
  2. 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,所以必须放在最后的延时处理,才有了这个接口