springboot2.0.3源碼篇 - 自動配置的實現,發現也不是那麼複雜

前言

  開心一刻   html

    女兒: 「媽媽,你這麼漂亮,當年怎麼嫁給了爸爸呢?」
    媽媽: 「當年你爸不是窮嘛!‘
    女兒: 「窮你還嫁給他!」
    媽媽: 「那時候剛剛畢業參加工做,領導對我說,他是個人扶貧對象,我年輕理解錯了,就嫁給他了!」
    女兒......java

@Import註解應用

  應用開發中,當咱們的功能模塊比較多時,每每會按模塊或類別對Spring的bean配置文件進行管理,使配置文件模塊化,更容易維護;spring3.0以前,對Spring XML bean文件進行拆分, 例如git

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
 
    <import resource="config/user.xml"/>
    <import resource="config/role.xml"/>
    <import resource="config/permission.xml"/>
 
</beans>

  spring3.0及以後,引入了@Import註解,提供與Spring XML中的<import />元素等效的功能;spring4.2以前,@Import只支持導入配置類(@Configuration修飾的類、ImportSelector實現類和ImportBeanDefinitionRegistrar實現類),而spring4.2及以後不只支持導入配置類,同時也支持導入常規的java類(如普通的User類)spring

  示例地址:spring-boot-autoconfig,四種都有配置,不用down下來運行,看一眼具體如何配置便可數組

  運行測試用例,結果以下springboot

  能夠看到,Dog、Cat、Role、User、Permission的實例都已經註冊到了spring容器,也就是說上述講的@Import的4種方式都是可以將實例註冊到spring容器的ide

@Import註解原理

  @Import何以有如此強大的功能,背後確定有某個團隊在運做,而這個團隊是誰了,就是spring;spring容器確定在某個階段有對@Import進行了處理,至於spring是在何時對@Import進行了怎樣的處理,咱們來跟一跟源碼;ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor,那麼它會在spring啓動的refresh階段被應用,咱們從refresh的invokeBeanFactoryPostProcessors方法開始模塊化

  ConfigurationClassPostProcessor

    注意此時spring容器中的bean定義與bean實例,數量很是少,你們能夠留心觀察下spring-boot

    一路跟下來,咱們來到processConfigBeanDefinitions方法,該方法會建立一個ConfigurationClassParser對象,該對象會分析全部@Configuration註解的配置類,產生一組ConfigurationClass對象,而後從這組ConfigurationClass對象中加載bean定義測試

  ConfigurationClassParser

    主要是parse方法

public void parse(Set<BeanDefinitionHolder> configCandidates) {    
    this.deferredImportSelectors = new LinkedList<>();

    // 一般狀況下configCandidates中就一個BeanDefinitionHolder,關聯的是咱們的啓動類
    // 示例中是:com.lee.autoconfig.AutoConfigApplication
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            // 被@Configuration註解修飾的類會被解析爲AnnotatedGenericBeanDefinition,AnnotatedGenericBeanDefinition實現類AnnotatedBeanDefinition接口
            if (bd instanceof 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);
        }
    }

    // 處理延遲的ImportSelector,這裏本文的重點:自動配置的入口
    processDeferredImportSelectors();
}
View Code

    從啓動類(示例中是com.lee.autoconfig.AutoConfigApplication)開始,遞歸解析配置類以及配置類的父級配置類;邊跟邊注意beanFactory中beanDefinitionMap的變化,ConfigurationClassParser對象有beanFactory的引用,屬性名叫registry;咱們能夠仔細看下doProcessConfigurationClass方法

/**
 * 經過從源類中讀取註解、成員和方法來構建一個完整的配置類:ConfigurationClass
 * 注意返回值,是父級類或null(null包含兩種狀況,沒找到父級類或以前已經處理完成)
 */
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
        throws IOException {

    // 遞歸處理配置類內置的成員類
    processMemberClasses(configClass, sourceClass);

    // 處理配置類上全部@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");
        }
    }

    // 處理配置類上全部的@ComponentScan註解,包括@ComponentScans和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) {
            // 當即掃描@ComponentScan修飾的配置類,
            // 一般是從啓動類所在的包(示例中是com.lee.autoconfig)開始掃描,掃描配置類(被@Configuration修飾的類)
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                    this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // 進一步檢查經過配置類掃描獲得的bean定義集,並在須要時遞歸解析
            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註解
    // 包括@Import支持的4種類型:ImportSelector、ImportBeanDefinitionRegistrar、@Configuration和普通java類
    // 普通java類會被按@Configuration方式處理
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // 處理配置類上全部的@ImportResource註解,xml方式的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);
        }
    }

    // 處理配置類中被@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);
            // Superclass found, return its annotation metadata and recurse
            return sourceClass.getSuperClass();
        }
    }

    // No superclass -> processing is complete
    return null;
}
View Code

    上述代碼中寫了相關注釋,有興趣的同窗能夠更進一步的去跟,這裏我只跟下processImports方法,由於這個與自動配置息息相關

    起始的ConfigurationClass包括:一、工程中全部咱們自定義的被@Configuration修飾的類,示例中就只有AnimalConfig;二、應用的啓動類,示例中是:AutoConfigApplication。

    咱們自定義的ConfigurationClass通常不會包含多級父級ConfigurationClass,例如AnimalConfig,就沒有父級ConfigurationClass,解析就比較簡單,咱們無需關注,但AutoConfigApplication就不同了,他每每會被多個註解修飾,而這些註解會牽扯出多個ConfigurationClass,須要遞歸處理全部的ConfigurationClass;上圖中,咱們跟到了一個比較重要的類:AutoConfigurationImportSelector,實例化以後封裝成了DeferredImportSelectorHolder對象,存放到了ConfigurationClassParser的deferredImportSelectors屬性中

自動配置源碼解析

  有人可能有這樣的疑問:哪來的AutoConfigurationImportSelector,它有什麼用? 客觀莫急,咱們慢慢往下看

  咱們的應用啓動類被@SpringBootApplication,它是個組合註解,詳情以下

  相信你們都看到@Import(AutoConfigurationImportSelector.class)了,ConfigurationClassParser就是今後解析到的AutoConfigurationImportSelector,至於AutoConfigurationImportSelector有什麼用,立刻揭曉;咱們回到ConfigurationClassParser的parse方法,裏面還有個很重要的方法:processDeferredImportSelectors,值得咱們詳細跟下

  processDeferredImportSelectors

    說的簡單點,從類路徑下的全部spring.facoties文件中讀取所有的自動配置類(spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration的值),而後篩選出知足條件的配置類,封裝成ConfigurationClass,存放到ConfigurationClassParser的configurationClasses屬性中

    說的詳細點,分兩個方法進行說明

      selectImports方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 從類路徑下的spring.factories文件中讀取全部配置類(org.springframework.boot.autoconfigure.EnableAutoConfigurationd的值)
    // 獲得全部配置類的全路徑類名的集合 - 數組
    // 此時獲得的是類名,至於該類存不存在,還須要在下面步驟中進行檢驗
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    // 去重重複的
    configurations = removeDuplicates(configurations);
    // 獲取須要排除的配置類,@SpringBootApplication exclude和excludeName的值
    // 以及配置文件中spring.autoconfigure.exclude的值
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 驗證排除的配置類是否存在 - 類路徑下是否存在該類
    checkExcludedClasses(configurations, exclusions);
    // 剔除須要排除的配置類
    configurations.removeAll(exclusions);
    // 進行過濾 - 經過配置類的條件註解(@ConditionalOnClass、@ConditionalOnBean等)來判斷配置類是否符合條件
    configurations = filter(configurations, autoConfigurationMetadata);
    // 觸發自動配置事件 - ConditionEvaluationReportAutoConfigurationImportListener
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回@Import方式 全部知足條件的配置類
    return StringUtils.toStringArray(configurations);
}
View Code

        從類路徑下的全部spring.facoties文件中讀取org.springframework.boot.autoconfigure.EnableAutoConfiguration的全部值,此時獲取的是全路徑類名的數組,而後進行篩選過濾,一、先去重處理,由於多個spring.factories中可能存在重複的;二、而後剔除咱們配置的須要排除的類,包括@SpringBootApplication註解的exclude、excludeName,以及配置文件中的spring.autoconfigure.exclude;三、條件過濾,過濾出知足本身條件註解的配置類。最終獲取全部知足條件的自動配置類,示例中有24個。

        條件註解更詳細的信息請查看:spring-boot-2.0.3源碼篇 - @Configuration、Condition與@Conditional,讀取spring.facoties文件的詳細信息請查看:spring-boot-2.0.3啓動源碼篇一 - SpringApplication構造方法

      processImports方法

        這個方法在解析ConfigurationClassParser的parse方法的時候已經用到過了,只是沒有作說明,它其實就是用來處理配置類上的@Import註解的;上述selectImports方法解析出來的配置類,每一個配置類都會通過processImports方法處理,遞歸處理@Import註解,就與遞歸處理咱們的啓動類的@Import註解同樣,從而獲取全部的自動配置類;springboot的自動配置就是這樣實現的。

  此時還只是獲取了知足條件的自動配置類,配置類中的bean定義加載尚未進行,咱們回到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,其中有以下代碼

// 各類方式的配置類的解析,包括springboot的自動配置 - @Import、AutoConfigurationImportSelector
parser.parse(candidates);
parser.validate();

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content
if (this.reader == null) {
    this.reader = new ConfigurationClassBeanDefinitionReader(
            registry, this.sourceExtractor, this.resourceLoader, this.environment,
            this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);    // 將配置類中的bean定義加載到beanFactory

  至此,springboot的自動配置源碼解析就完成了,有興趣的能夠更近一步的深刻

總結

  一、各個方法之間的調用時序圖以下,結合這個時序圖看上面的內容,更好看懂

  二、springboot自動配置底層依賴的是SpringFactoriesLoader和AutoConfigurationImportSelector;@EnableAutoConfiguration註解就像一個八爪魚,抓取全部知足條件的配置類,而後讀取其中的bean定義到spring容器,@EnableAutoConfiguration得以生效的關鍵組件關係圖以下

相關文章
相關標籤/搜索