Spring-@Configuration註解簡析

半夏之沫發表於2022-11-24

前言

Spring中的@Configuration註解修飾的類被稱為配置類,透過配置類可以向容器註冊bean以及匯入其它配置類,本篇文章將結合例子和原始碼對@Configuration註解原理進行學習,並引出對Spring框架在處理配置類過程中起重要作用的ConfigurationClassPostProcessor的討論。

Springboot版本:2.4.1

正文

一. @Configuration註解簡析

基於@Configuration註解可以實現基於JavaConfig的方式來宣告Spring中的bean,與之作為對比的是基於XML的方式來宣告bean。由@Configuration註解標註的類中所有由@Bean註解修飾的方法返回的物件均會被註冊為Spring容器中的bean,使用舉例如下。

@Configuration
public class TestBeanConfig {

    @Bean
    public TestBean testBean() {
        return new TestBean();
    }

}

如上所示,Spring容器會將TestBean註冊為Spring容器中的bean。由@Configuration註解修飾的類稱為Spring中的配置類,Spring中的配置類在Spring啟動階段會被先載入並解析為ConfigurationClass,然後會基於每個配置類對應的ConfigurationClass物件為容器註冊BeanDefinition,以及基於每個配置類中由@Bean註解修飾的方法為容器註冊BeanDefinition,後續Spring也會基於這些BeanDefinition向容器註冊bean。關於BeanDefinition的概念,可以參見Spring-BeanDefinition簡析

在詳細分析由@Configuration註解修飾的配置類是如何被解析為ConfigurationClass以及最終如何被註冊為BeanDefinition前,得先探究一下Springboot的啟動類,因為後續的分析會以Springboot的啟動為基礎,所以有必要先了解一下Springboot中的啟動類。

Springboot的啟動類由@SpringBootApplication註解修飾,@SpringBootApplication註解的功能主要由@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan實現,後兩者與Springboot中的自動裝配有關,關於Springboot實現自動裝配,會在後續文章中學習,在這裡主要關心@SpringBootConfiguration註解。實際上,@SpringBootConfiguration註解其實就是@Configuration註解,@SpringBootConfiguration註解的簽名如下所示。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    ......
}

既然@SpringBootConfiguration註解等同於@Configuration註解,那麼相應的Springboot的啟動類就是一個配置類,Springboot的啟動類對應的BeanDefinition會在準備Springboot容器階段就註冊到容器中,將斷點打到SpringApplication#run()方法中呼叫refreshContext()方法這一行程式碼,而已知refreshContext()這一行程式碼用於初始化容器,執行到refreshContext()方法時容器已經完成了準備,此時看一下容器的資料,如下所示。

此時Springboot容器持有的DefaultListableBeanFactory中的beanDefinitionMap中已經存在了Springboot啟動類對應的BeanDefinition,在初始化Springboot容器階段,Springboot啟動類對應的BeanDefinition會首先被處理,透過處理Springboot啟動類對應的BeanDefinition才會引入對其它配置類的處理。關於Springboot啟動類,暫時瞭解到這裡,下面再給出一張處理配置類的呼叫鏈,以供後續閱讀參考。

本篇文章後續將從ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()方法開始,對由@Configuration註解修飾的配置類的處理進行說明。

二. ConfigurationClassPostProcessor處理配置類

透過第一節中的呼叫鏈可知,在Springboot啟動時,初始化容器階段會呼叫到ConfigurationClassPostProcessor來處理配置類,即由@Configuration註解修飾的類。ConfigurationClassPostProcessor是由Spring框架提供的bean工廠後置處理器,類圖如下所示。

可知ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor介面,同時BeanDefinitionRegistryPostProcessor介面又繼承於BeanFactoryPostProcessor,所以ConfigurationClassPostProcessor本質上就是一個bean工廠後置處理器。ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor介面定義的postProcessBeanDefinitionRegistry()方法,在ConfigurationClassPostProcessor中對該方法的註釋如下。

Derive further bean definitions from the configuration classes in the registry.

直譯過來就是:從登錄檔中的配置類派生進一步的bean定義。那麼這裡的登錄檔指的就是容器持有的DefaultListableBeanFactory,而Springboot框架在容器準備階段就將Springboot的啟動類對應的BeanDefinition註冊到了DefaultListableBeanFactorybeanDefinitionMap中,所以登錄檔中的配置類指的就是Springboot的啟動類(前文已知Springboot的啟動類就是一個配置類),而派生進一步的bean定義,就是將Springboot啟動類上@EnableAutoConfiguration@ComponentScan等註解載入的配置類解析為BeanDefinition並註冊到DefaultListableBeanFactorybeanDefinitionMap中。暫時不清楚在Springboot啟動流程中,ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()方法註釋中提到的配置類是否會有除了Springboot啟動類之外的配置類,歡迎留言討論。

即現在知道,ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry()方法主要處理目標就是Springboot的啟動類,透過處理Springboot啟動類引出對其它配置類的處理,下面跟隨原始碼,進行學習。postProcessBeanDefinitionRegistry()方法如下所示。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
                "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
                "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    //記錄已經處理過的登錄檔id
    this.registriesPostProcessed.add(registryId);

    processConfigBeanDefinitions(registry);
}

postProcessBeanDefinitionRegistry()方法會記錄已經處理過的登錄檔id,防止同一登錄檔被重複處理。實際的處理邏輯在processConfigBeanDefinitions()中,由於processConfigBeanDefinitions()方法比較長,所以這裡先把processConfigBeanDefinitions()方法的處理流程進行一個梳理,如下所示。

  • 先把Springboot啟動類的BeanDefinition從登錄檔(這裡指DefaultListableBeanFactory,後續如果無特殊說明,登錄檔預設指DefaultListableBeanFactory)的beanDefinitionMap中獲取出來;
  • 建立ConfigurationClassParser,解析Springboot啟動類的BeanDefinition,即解析@PropertySource@ComponentScan@Import@ImportResource@Bean等註解並生成ConfigurationClass,最後快取在ConfigurationClassParserconfigurationClasses中;
  • 建立ConfigurationClassBeanDefinitionReader,解析所有ConfigurationClass,基於ConfigurationClass建立BeanDefinition並快取到登錄檔的beanDefinitionMap中。

processConfigBeanDefinitions()方法原始碼如下。

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();

    //從登錄檔中把Springboot啟動類對應的BeanDefinition獲取出來
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
            }
        }
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }

    //如果未獲取到Springboot啟動類對應的BeanDefinition,則直接返回
    if (configCandidates.isEmpty()) {
        return;
    }

    configCandidates.sort((bd1, bd2) -> {
        int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
        int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
        return Integer.compare(i1, i2);
    });

    SingletonBeanRegistry sbr = null;
    if (registry instanceof SingletonBeanRegistry) {
        sbr = (SingletonBeanRegistry) registry;
        if (!this.localBeanNameGeneratorSet) {
            BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
                    AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
            if (generator != null) {
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
    }

    if (this.environment == null) {
        this.environment = new StandardEnvironment();
    }

    //建立ConfigurationClassParser以解析Springboot啟動類及其引出的其它配置類
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
        //ConfigurationClassParser開始執行解析
        parser.parse(candidates);
        parser.validate();

        //將ConfigurationClassParser解析得到的ConfigurationClass拿出來
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        //建立ConfigurationClassBeanDefinitionReader,以基於ConfigurationClass建立BeanDefinition
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        //開始建立BeanDefinition並註冊到登錄檔中
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);
        processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

        candidates.clear();
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            for (String candidateName : newCandidateNames) {
                if (!oldCandidateNames.contains(candidateName)) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                            !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    }
    while (!candidates.isEmpty());

    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }

    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
}

processConfigBeanDefinitions()方法中,ConfigurationClassPostProcessor將解析Sprngboot啟動類以得到ConfigurationClass的任務委託給了ConfigurationClassParser,將基於ConfigurationClass建立BeanDefinition並註冊到登錄檔的任務委託給了ConfigurationClassBeanDefinitionReader,所以下面會對這兩個步驟進行分析。首先是ConfigurationClassParser解析Springboot啟動類,其parse()方法如下所示。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                //Springboot啟動類對應的BeanDefinition為AnnotatedGenericBeanDefinition
                //AnnotatedGenericBeanDefinition實現了AnnotatedBeanDefinition介面
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }
    
    //延遲處理DeferredImportSelector
    this.deferredImportSelectorHandler.process();
}

由於Springboot啟動類對應的BeanDefinitionAnnotatedGenericBeanDefinition,而AnnotatedGenericBeanDefinition實現了AnnotatedBeanDefinition介面,所以繼續看parse(AnnotationMetadata metadata, String beanName)方法,如下所示。

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

繼續看processConfigurationClass()方法,如下所示。

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }

    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    if (existingClass != null) {
        if (configClass.isImported()) {
            if (existingClass.isImported()) {
                existingClass.mergeImportedBy(configClass);
            }
            return;
        }
        else {
            this.configurationClasses.remove(configClass);
            this.knownSuperclasses.values().removeIf(configClass::equals);
        }
    }

    SourceClass sourceClass = asSourceClass(configClass, filter);
    do {
        //實際開始處理配置類
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    }
    while (sourceClass != null);

    this.configurationClasses.put(configClass, configClass);
}

processConfigurationClass()方法中會呼叫doProcessConfigurationClass()方法來實際的處理配置類的@ComponentScan@Import@Bean等註解。在本節的論述中,其實一直是將Springboot啟動類與其它配置類分開的,因為筆者認為Springboot啟動類是一個特殊的配置類,其它配置類的掃描和載入均依賴Springboot啟動類上的一系列註解(@ComponentScan@Import等)。上述processConfigurationClass()方法是一個會被遞迴呼叫的方法,第一次該方法被呼叫時,處理的配置類是Springboot的啟動類,處理Springboot啟動類時就會載入進來許多其它的配置類,那麼這些配置類也會呼叫processConfigurationClass()方法來處理,因為其它配置類上可能也會有一些@Import@Bean等註解。這裡只討論第一次呼叫,即處理Springboot啟動類的情況。doProcessConfigurationClass()方法原始碼如下所示。

@Nullable
protected final SourceClass doProcessConfigurationClass(
        ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
        throws IOException {

    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        processMemberClasses(configClass, sourceClass, filter);
    }

    //處理@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.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                    "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

    //處理@ComponentScan註解
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
        for (AnnotationAttributes componentScan : componentScans) {
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            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());
                }
            }
        }
    }

    //處理@Import註解
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    //處理@ImportResource註解
    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);
        }
    }

    //處理由@Bean註解修飾的方法
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    processInterfaces(configClass, sourceClass);

    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (superclass != null && !superclass.startsWith("java") &&
                !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            return sourceClass.getSuperClass();
        }
    }

    return null;
}

doProcessConfigurationClass()方法中對於每種註解的處理會在後續文章中介紹,本文暫時不討論。在processConfigurationClass()方法中處理完Springboot啟動類之後,實際上此時只會將自定義bean(由@Component@Controller@Service等註解修飾的類)對應的ConfigurationClass,自定義配置類(由@Configuration註解修飾的類)對應的ConfigurationClass新增到ConfigurationClassParserconfigurationClasses中,那麼最為關鍵的各種starter中的配置類對應的ConfigurationClass是在哪裡新增的呢,回到ConfigurationClassParserparse()方法,下面再給出其原始碼,如下所示。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                //這裡處理完,ConfigurationClassParser的configurationClasses中只會有自定義bean和自定義配置類對應的ConfigurationClass
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }

    //這裡處理完,starter中的配置類對應的ConfigurationClass才會新增到ConfigurationClassParser的configurationClasses中
    this.deferredImportSelectorHandler.process();
}

因為Springboot掃描starter並處理其配置類是依賴啟動類上的@EnableAutoConfiguration註解,@EnableAutoConfiguration註解的功能由@Import(AutoConfigurationImportSelector.class)實現,其中AutoConfigurationImportSelector實現了DeferredImportSelector介面,而DeferredImportSelector表明需要被延遲處理,所以Springboot需要延遲處理AutoConfigurationImportSelector,延遲處理的地方就在上述parse()方法的最後一行程式碼,關於@Import註解,後續文章中會對其進行分析,這裡暫時不討論。現在定義一個TestBeanConfig配置類,在其中向容器註冊TestBean,同時再定義一個由@Component註解修飾的TestComponent,程式碼如下所示。

@Configuration
public class TestBeanConfig {

    @Bean
    public TestBean testBean() {
        return new TestBean();
    }

}
public class TestBean {

    public TestBean() {
        System.out.println("Initialize TestBean.");
    }

}
@Component
public class TestComponent {

    public TestComponent() {
        System.out.println("Initialize TestComponent.");
    }

}

現在在ConfigurationClassParserparse()方法的this.deferredImportSelectorHandler.process();這一行程式碼打斷點,程式執行到這裡時,ConfigurationClassParserconfigurationClasses如下所示。

可見此時configurationClasses中沒有starter中的配置類對應的ConfigurationClass,往下執行一行,此時ConfigurationClassParserconfigurationClasses如下所示。

可見此時starter中的配置類對應的ConfigurationClass已經被載入,至此ConfigurationClassParser解析Springboot啟動類分析完畢。

現在分析ConfigurationClassBeanDefinitionReader解析所有ConfigurationClass,並基於ConfigurationClass建立BeanDefinition並快取到登錄檔的beanDefinitionMap中。首先是ConfigurationClassBeanDefinitionReaderloadBeanDefinitions()方法,如下所示。

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}

loadBeanDefinitions()方法中遍歷每一個ConfigurationClass並呼叫了loadBeanDefinitionsForConfigurationClass()方法,繼續看loadBeanDefinitionsForConfigurationClass()方法,如下所示。

private void loadBeanDefinitionsForConfigurationClass(
        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    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;
    }

    if (configClass.isImported()) {
        //基於ConfigurationClass自身建立BeanDefinition並快取到登錄檔中
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        //基於ConfigurationClass中由@Bean註解修飾的方法建立BeanDefinition並快取到登錄檔中
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }

    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

上述loadBeanDefinitionsForConfigurationClass()方法中,除了將自身建立為BeanDefinition外,還會將所有由@Bean註解修飾的方法(如果有的話)建立為BeanDefinition,所有建立的BeanDefinition最後都會註冊到登錄檔中,即快取到DefaultListableBeanFactorybeanDefinitionMap中。至此,ConfigurationClassBeanDefinitionReader解析所有ConfigurationClass的大致流程也分析完畢。

總結

@Configuration註解修飾的配置類結合@Bean註解可以實現向容器註冊bean的功能,同時也可以藉助@ComponentScan@Import等註解將其它配置類掃描到容器中。Springboot的啟動類就是一個配置類,透過ConfigurationClassPostProcessor處理Springboot啟動類,可以實現將自定義的bean,自定義的配置類和各種starter中的配置類掃描到容器中,以達到自動裝配的效果。

相關文章