【Spring】簡述@Configuration配置類註冊BeanDefinition到Spring容器的過程

概述

本文以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的類繼承關係

從紅框中可以看出ConfigurationClassPostProcessorBeanDefinitionRegistryPostProcessor接口的實現類,即是一個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());
}

思維導圖

請放大觀看

參考

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※高價收購3C產品,價格不怕你比較

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!