您的位置:首页 > 编程语言 > Java开发

【Spring】简述@Configuration配置类注册BeanDefinition到Spring容器的过程

2019-11-26 11:21 1531 查看

概述

本文以SpringBoot应用为基础,尝试分析基于注解

@Configuration
的配置类是如何向Spring容器注册BeanDefinition的过程

其中主要分析了

ConfigurationClassPostProcessor
这个
BeanDefinitionRegistryPostProcessor
即Bean定义注册后置处理器,在Spring启动过程中对@Configuration配置类的处理,主要体现在 解析并发现所有配置类,处理配置类的相关逻辑(如配置类上的@ComponentScan、@Import、@Bean注解等),注册其中的BeanDefinition

SpringBoot版本:2.0.9.RELEASE

Spring版本:5.0.13.RELEASE


ConfigurationClassPostProcessor如何被引入

首先看一下

ConfigurationClassPostProcessor的类继承关系

从红框中可以看出

ConfigurationClassPostProcessor
BeanDefinitionRegistryPostProcessor
接口的实现类,即是一个Bean定义注册的后置处理器,会在Spring容器启动时被调用,具体时机为

// 调用链
AbstractApplicationContext.refresh()
=> invokeBeanFactoryPostProcessors()
=> PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors()

invokeBeanFactoryPostProcessors()
会先调用所有的
BeanDefinitionRegistryPostProcessor
之后,再调用所有的
BeanFactoryPostProcessor


ConfigurationClassPostProcessor又是如何被引入Spring的呢??

SpringBoot应用会在ApplicationContext应用上下文被创建的构造函数中

new AnnotatedBeanDefinitionReader
这个用于注册基于注解的BeanDefinition的Reader,在其构造中又会调用
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
使用工具类向Spring容器中注册一些所谓的注解配置处理器,其中就包含
ConfigurationClassPostProcessor

// ConfigurationClassPostProcessor被注册
AnnotationConfigServletWebServerApplicationContext构造
=> new AnnotatedBeanDefinitionReader(registry)
=> AnnotationConfigUtils.registerAnnotationConfigProcessors(registry)
=> 注册ConfigurationClassPostProcessor到Spring容器


ConfigurationClassPostProcessor处理过程简述

首先,ConfigurationClassPostProcessor后置处理器的处理入口为

postProcessBeanDefinitionRegistry()
方法。其主要使用了
ConfigurationClassParser
配置类解析器解析
@Configuration
配置类上的诸如
@ComponentScan
@Import
@Bean
等注解,并尝试发现所有的配置类;还使用了
ConfigurationClassBeanDefinitionReader
注册所发现的所有配置类中的所有Bean定义;结束执行的条件是所有配置类都被发现和处理,相应的bean定义注册到容器

大致流程如下:

1、通过BeanDefinitionRegistry查找当前Spring容器中所有BeanDefinition

2、通过

ConfigurationClassUtils.checkConfigurationClassCandidate()
检查BeanDefinition是否为 “完全配置类”“简化配置类”,并对配置类做标记,放入集合待后续处理

Spring配置类的分类可以 参考

3、通过

ConfigurationClassParser解析器
parse解析配置类集合,尝试通过它们找到其它配置类

4、使用

ConfigurationClassBeanDefinitionReader
注册通过所发现的配置类中找到的所有beanDefinition

5、处理完一轮配置类后,查看BeanDefinitionRegistry中是否存在新加载的且还未被处理过的 “完全配置类”“简化配置类”,有的话继续上面步骤

其中第3、4步后面重点分析


ConfigurationClassParser#parse():解析构建配置类

对于SpringBoot应用来说,参与解析的种子配置文件即为SpringBoot的Application启动类

解析构建配置类流程

通过ConfigurationClassParser解析器parse解析配置类集合,尝试通过它们找到其它配置类

  • 循环解析所有配置类 ConfigurationClassParser#processConfigurationClass()

    根据@Conditional的ConfigurationPhase.PARSE_CONFIGURATION阶段条件判断是否跳过配置类

    注意:有些@Conditional是在当前这个PARSE_CONFIGURATION解析配置阶段使用的,有些是在REGISTER_BEAN注册beanDefinition阶段使用的

  • 【重点】调用ConfigurationClassParser#doProcessConfigurationClass()循环解析配置类,直到不存在未处理过的父类

    1、处理配置类的成员内部类: 检查其是否为“完全/简化配置类”,是则对其继续分析处理并将其放入分析器的属性configurationClasses
  • 2、处理@PropertySource: 将找到的PropertySource添加到environment的PropertySource集合
  • 3、处理@ComponentScan: 扫描到的@Component类BeanDefinition就直接注册到Spring容器;如果组件为配置类,继续分析处理并将其放入分析器的属性configurationClasses
  • 4、处理@Import: (1)处理ImportSelector: 如果是DeferredImportSelector,如SpringBoot的自动配置导入,添加到deferredImportSelectors,延迟进行processImports();其它通过ImportSelector找到的类,继续调用processImports(),要么是@Configuration配置类继续解析,要么是普通组件导入Spring容器
  • (2)处理ImportBeanDefinitionRegistrar: 调用当前配置类的addImportBeanDefinitionRegistrar(),后面委托它注册其它bean定义
  • (3)其它Import:调用processConfigurationClass()继续解析,最终要么是配置类放入configurationClasses,要么是普通组件导入Spring容器
  • 5、处理@ImportResource: 添加到配置类的importedResources集合,后续ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()时再使用这些导入的BeanDefinitionReader读取Resource中的bean定义并注册
  • 6、处理@Bean: 获取所有@Bean方法,并添加到配置类的beanMethods集合
  • 7、处理配置类接口上的default methods
  • 8、检查是否有未处理的父类: 如果配置类有父类,且其不在解析器维护的knownSuperclasses中,对其调用doProcessConfigurationClass()重复如上检查,直到不再有父类或父类在knownSuperclasses中已存在
  • processDeferredImportSelectors():处理推迟的ImportSelector集合,其实就是延迟调用了processImports()

    SpringBoot的自动配置类就是被DeferredImportSelector推迟导入的


  • 解析构建配置类源码分析

    ConfigurationClassParser#processConfigurationClass()

    包含了处理单个配置类的大体流程,先根据

    ConfigurationPhase.PARSE_CONFIGURATION
    解析配置阶段的
    @Conditional
    条件判断当前配置类是否应该解析,之后调用
    ConfigurationClassParser#doProcessConfigurationClass()
    循环解析配置类,直到不存在未处理过的父类

    /**
    * 解析单个配置类
    * 解析的最后会将当前配置类放到configurationClasses
    */
    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    /**
    * 根据@Conditional条件判断是否跳过配置类
    * 注意:当前这个PARSE_CONFIGURATION解析配置阶段只会使用这个阶段的@Conditional条件,有些REGISTER_BEAN注册beanDefinition阶段的条件不会在此时使用
    */
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
    }
    
    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    // 如果configClass在已经分析处理的配置类记录中已存在
    if (existingClass != null) {
    //如果配置类是被@Import注册的,return
    if (configClass.isImported()) {
    if (existingClass.isImported()) {
    existingClass.mergeImportedBy(configClass);
    }
    // Otherwise ignore new imported config class; existing non-imported class overrides it.
    return;
    }
    // 否则,清除老的记录,在来一遍
    else {
    // Explicit bean definition found, probably replacing an import.
    // Let's remove the old one and go with the new one.
    this.configurationClasses.remove(configClass);
    this.knownSuperclasses.values().removeIf(configClass::equals);
    }
    }
    
    // Recursively process the configuration class and its superclass hierarchy.
    /**
    * 递归处理配置类及其超类层次结构
    * 从当前配置类configClass开始向上沿着类继承结构逐层执行doProcessConfigurationClass,直到遇到的父类是由Java提供的类结束循环
    */
    SourceClass sourceClass = asSourceClass(configClass);
    /**
    * 循环处理配置类configClass直到sourceClass变为null,即父类为null
    * doProcessConfigurationClass的返回值是其参数configClass的父类
    * 如果该父类是由Java提供的类或者已经处理过,返回null
    */
    do {
    sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);
    
    this.configurationClasses.put(configClass, configClass);
    }


    ConfigurationClassParser#doProcessConfigurationClass():真正解析配置类

    通过解析配置类上的注解、内部成员类和方法构建一个完整的ConfigurationClass配置类,过程中如果发现了新的配置类可以重复调用此方法

    真正解析过程中会处理

    成员内部类
    @PropertySource
    @ComponentScan
    @Import
    @ImportSource
    @Bean方法
    等,流程如下:

    • 1、处理配置类的成员内部类: 检查其是否为“完全/简化配置类”,是则对其继续分析处理并将其放入分析器的属性configurationClasses
    • 2、处理@PropertySource: 将找到的PropertySource添加到environment的PropertySource集合
    • 3、处理@ComponentScan: 扫描到的@Component类BeanDefinition就直接注册到Spring容器;如果组件为配置类,继续分析处理并将其放入分析器的属性configurationClasses
    • 4、处理@Import: (1)处理ImportSelector: 如果是DeferredImportSelector,如SpringBoot的自动配置导入,添加到deferredImportSelectors,延迟进行processImports();其它通过ImportSelector找到的类,继续调用processImports(),要么是@Configuration配置类继续解析,要么是普通组件导入Spring容器
    • (2)处理ImportBeanDefinitionRegistrar: 调用当前配置类的addImportBeanDefinitionRegistrar(),后面委托它注册其它bean定义
    • (3)其它Import: 调用processConfigurationClass()继续解析,最终要么是配置类放入configurationClasses,要么是普通组件导入Spring容器
  • 5、处理@ImportResource: 添加到配置类的importedResources集合,后续ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()时再使用这些导入的BeanDefinitionReader读取Resource中的bean定义并注册
  • 6、处理@Bean: 获取所有@Bean方法,并添加到配置类的beanMethods集合
  • 7、处理配置类接口上的default methods
  • 8、检查是否有未处理的父类: 如果配置类有父类,且其不在解析器维护的knownSuperclasses中,对其调用doProcessConfigurationClass()重复如上检查,直到不再有父类或父类在knownSuperclasses中已存在
  • /**
    * Apply processing and build a complete {@link ConfigurationClass} by reading the
    * annotations, members and methods from the source class. This method can be called
    * multiple times as relevant sources are discovered.
    * @param configClass the configuration class being build
    * @param sourceClass a source class
    * @return the superclass, or {@code null} if none found or previously processed
    */
    @Nullable
    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    throws IOException {
    
    // Recursively process any member (nested) classes first
    /**
    * 1、处理配置类的成员类(配置类内嵌套定义的类)
    * 内部嵌套类也可能是配置类,遍历这些成员类,检查是否为"完全/简化配置类"
    * 有的话,调用processConfigurationClass()处理它们,最终将配置类放入configurationClasses集合
    */
    processMemberClasses(configClass, sourceClass);
    
    // Process any @PropertySource annotations
    /**
    * 2、处理 @PropertySource
    * 将找到的PropertySource添加到environment的PropertySource集合
    */
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), PropertySources.class,
    org.springframework.context.annotation.PropertySource.class)) {
    if (this.environment instanceof ConfigurableEnvironment) {
    processPropertySource(propertySource);
    }
    else {
    logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
    "]. Reason: Environment must implement ConfigurableEnvironment");
    }
    }
    
    // Process any @ComponentScan annotations
    /**
    * 3、处理 @ComponentScan
    * 处理用户手工添加的@ComponentScan,SpringBoot创建ApplicationContext时的ClassPathBeanDefinitionScanner是为了扫描启动类下的包
    * 为的是找到满足条件的@ComponentScan,即@Component相关的组件,先扫描一下,扫描到的就注册为BeanDefinition
    * 看其中是否还有配置类,有的话parse()继续分析处理,配置类添加到configurationClasses集合
    */
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    // 如果当前配置类上有@ComponentScan,且使用REGISTER_BEAN注册beanDefinition的条件判断也不跳过的话
    if (!componentScans.isEmpty() &&
    !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    for (AnnotationAttributes componentScan : componentScans) {
    // The config class is annotated with @ComponentScan -> perform the scan immediately
    // 立即扫描,扫描到的就注册为BeanDefinition,并获得扫描到的所有beanDefinition
    // 在处理SpringBoot启动类上的@ComponentScan时,虽然指指定了excludeFilters,但会根据启动类所在包推测basePackage,就会扫描到SpringBoot启动类包以下的Bean并注册
    Set<BeanDefinitionHolder> scannedBeanDefinitions =
    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    
    // Check the set of scanned definitions for any further config classes and parse recursively if needed
    // 检查扫描到的beanDefinition中是否有配置类,有的话parse()继续分析处理,,配置类添加到configurationClasses集合
    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    if (bdCand == null) {
    bdCand = holder.getBeanDefinition();
    }
    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    parse(bdCand.getBeanClassName(), holder.getBeanName());
    }
    }
    }
    }
    
    // Process any @Import annotations
    /**
    * 4、处理 @Import
    * (1)处理ImportSelector
    * 如果是DeferredImportSelector,如SpringBoot的自动配置导入,添加到deferredImportSelectors,延迟进行processImports()
    * 其它通过ImportSelector找到的类,继续调用processImports(),要么是@Configuration配置类继续解析,要么是普通组件导入Spring容器
    * (2)处理ImportBeanDefinitionRegistrar
    * 调用当前配置类的addImportBeanDefinitionRegistrar(),后面委托它注册其它bean定义
    * (3)其它
    * 调用processConfigurationClass()继续解析,最终要么是配置类放入configurationClasses,要么是普通组件导入Spring容器
    */
    processImports(configClass, sourceClass, getImports(sourceClass), true);
    
    // Process any @ImportResource annotations
    /**
    * 5、处理 @ImportResource
    * 添加到配置类的importedResources集合,后续loadBeanDefinitions()加载bean定义时再让这些导入BeanDefinitionReader自行读取bean定义
    */
    AnnotationAttributes importResource =
    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if (importResource != null) {
    String[] resources = importResource.getStringArray("locations");
    Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    for (String resource : resources) {
    String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    configClass.addImportedResource(resolvedResource, readerClass);
    }
    }
    
    // Process individual @Bean methods
    /**
    * 6、处理个别@Bean方法
    * 获取所有@Bean方法,并添加到配置类的beanMethods集合
    */
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
    
    // Process default methods on interfaces
    /**
    * 7、处理配置类接口上的default methods
    */
    processInterfaces(configClass, sourceClass);
    
    // Process superclass, if any
    /**
    * 8、检查父类是否需要处理,如果父类需要处理返回父类,否则返回null
    * 如果存在父类,且不在knownSuperclasses已经分析过的父类列表里,返回并继续分析
    */
    if (sourceClass.getMetadata().hasSuperClass()) {
    String superclass = sourceClass.getMetadata().getSuperClassName();
    if (superclass != null && !superclass.startsWith("java") &&
    !this.knownSuperclasses.containsKey(superclass)) {
    this.knownSuperclasses.put(superclass, configClass);
    // Superclass found, return its annotation metadata and recurse
    return sourceClass.getSuperClass();
    }
    }
    
    // No superclass -> processing is complete
    return null;
    }


    ConfigurationClassBeanDefinitionReader#loadBeanDefinitions():读取配置类,基于配置信息注册BeanDefinition

    读取配置类,基于配置信息注册BeanDefinition流程

    在上面解析配置类的过程中,除了构建了一个完整的ConfigurationClass配置类,其实已经向

    BeanDefinitionRegistry
    中添加了一些beanDefinition了,比如在处理
    @ComponentScan
    时,扫描到的
    @Component相关组件
    就已经注册了

    ConfigurationClassBeanDefinitionReader
    会继续读取已经构建好的ConfigurationClass配置类中的成员变量,从而注册beanDefinition

    构建好的ConfigurationClass配置类中在本阶段可用的成员变量包括:

    1. Set<BeanMethod> beanMethods
      : @Bean的方法
    2. Map<String, Class<? extends BeanDefinitionReader>> importedResources
      :配置类上@ImportResource注解的类存入此集合,会使用BeanDefinitionReader读取Resource中的BeanDefinition并注册
    3. Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars
      :ImportBeanDefinitionRegistrar集合

    通过构建好的配置类的配置信息,使用ConfigurationClassBeanDefinitionReader注册所有能够读取到的beanDefinition:

    • 根据

      ConfigurationPhase.REGISTER_BEAN阶段
      条件判断配置类是否需要跳过

      循环判断配置类以及导入配置类的类,使用ConfigurationPhase.REGISTER_BEAN阶段条件判断是否需要跳过只要配置类或导入配置类的类需要跳过即返回跳过​

    • 如果configClass.isImported(),将配置类自身注册为beanDefinition

    • 注册配置类所有

      @Bean方法
      为beanDefinition

    • 注册由

      @ImportedResources
      来的beanDefinition,即通过其它类型Resource的
      BeanDefinitionReader
      读取BeanDefinition并注册,如xml格式的配置源
      XmlBeanDefinitionReader

    • 注册由

      ImportBeanDefinitionRegistrars
      来的beanDefinition


    读取配置类,基于配置信息注册BeanDefinition源码分析

    /**
    * Read a particular {@link ConfigurationClass}, registering bean definitions
    * for the class itself and all of its {@link Bean} methods.
    * 读取特定配置类,根据配置信息注册bean definitions
    */
    private void loadBeanDefinitionsForConfigurationClass(
    ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    
    /**
    * 根据ConfigurationPhase.REGISTER_BEAN阶段条件判断配置类是否需要跳过
    * 循环判断配置类以及导入配置类的类,使用ConfigurationPhase.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;
    }
    
    // 1、如果当前配置类是通过内部类导入 或 @Import导入,将配置类自身注册为beanDefinition
    if (configClass.isImported()) {
    registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    
    // 2、注册配置类所有@Bean方法为beanDefinition
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    
    // 3、注册由@ImportedResources来的beanDefinition
    // 即通过其它类型Resource的BeanDefinitionReader读取BeanDefinition并注册
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    
    // 4、注册由ImportBeanDefinitionRegistrars来的beanDefinition
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }


    思维导图

    请放大观看


    参考

    Spring BeanDefinitionRegistryPostProcessor:ConfigurationClassPostProcessor

    Spring 工具类 ConfigurationClassParser 分析得到配置类

    Spring 配置类的分类

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: