SpringBoot中 AutoConfiguration 是如何被加載并注冊的?

本文基于
Spring 5.1.7.release
SpringBoot 2.1.5.release

在之前的文章 SpringBoot 中的 Bean 定義是如何被注冊的攻人? 中已經(jīng)講過 bean 定義的加載和注冊過程,但你可能有個疑惑 SpringBoot 的 AutoConfiguration 配置類是如何被加載的呢距芬?這篇文章就帶你揭開這個面紗。

用過 SpringBoot 的朋友都知道循帐,SpringBoot 有一個重要的注解就是 @SpringBootApplication框仔。奧秘就在這個注解的元注解 @EnableAutoConfiguration 上,看下 @EnableAutoConfiguration 的結(jié)構(gòu):

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default {};

}

可以看到它的上面有個 @Import 的注解拄养,value = AutoConfigurationImportSelector.class离斩,它是一個 DeferredImportSelector(ImportSelector),如果你看過了 SpringBoot 中的 Bean 定義是如何被注冊的瘪匿? 你就應(yīng)該知道跛梗,它會在 ConfigurationClassParser#doProcessConfigurationClass#processImports 中被解析。另外它是一個 DeferredImportSelector 表示它是一個延后處理的 ImportSelector棋弥。

調(diào)用過程

1. ConfigurationClassParser#parse(Set<BeanDefinitionHolder> configCandidates)

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            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核偿,也就是實現(xiàn)了 DeferredImportSelector 的類
    this.deferredImportSelectorHandler.process();
}

2. DeferredImportSelectorHandler#process

public void process() {
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    try {
        if (deferredImports != null) {
            DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
            deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
            // 處理
            deferredImports.forEach(handler::register);
            handler.processGroupImports();
        }
    }
    finally {
        this.deferredImportSelectors = new ArrayList<>();
    }
}

3. DeferredImportSelectorGroupingHandler#register

public void register(DeferredImportSelectorHolder deferredImport) {
    // 獲取 group class類。AutoConfigurationImportSelector 返回的是一個 AutoConfigurationImportSelector&AutoConfigurationGroup.class
    Class<? extends Group> group = deferredImport.getImportSelector()
            .getImportGroup();
    // 放入 groupings顽染,記住這個 groupings漾岳,后面會用到
    // 如果 group == null,則使用默認(rèn)的 group DefaultDeferredImportSelectorGroup
    DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
            (group != null ? group : deferredImport),
            key -> new DeferredImportSelectorGrouping(createGroup(group)));
    grouping.add(deferredImport);
    this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getConfigurationClass());
}

4. DeferredImportSelectorGroupingHandler#processGroupImports

public void processGroupImports() {
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        // grouping.getImports()  加載 AutoConfiguration 
        grouping.getImports().forEach(entry -> {
            ConfigurationClass configurationClass = this.configurationClasses.get(
                    entry.getMetadata());
            try {
                // 處理 import
                processImports(configurationClass, asSourceClass(configurationClass),
                        asSourceClasses(entry.getImportClassName()), false);
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                                configurationClass.getMetadata().getClassName() + "]", ex);
            }
        });
    }
}

這里是加載和注冊 AutoConfiguration 最重要的一步粉寞,所有的奧秘都在 grouping.getImports() 這個方法中尼荆。
grouping.getImports() 獲取所有的 AutoConfiguration 類,processImports 處理唧垦。

5. DeferredImportSelectorGrouping#getImports

public Iterable<Group.Entry> getImports() {
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        // 處理所有的 AutoConfiguration
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getImportSelector());
    }
    // 獲取
    return this.group.selectImports();
}

這里的 group 是 AutoConfigurationGroup

6. AutoConfigurationGroup#process

@Override
public void process(AnnotationMetadata annotationMetadata,
        DeferredImportSelector deferredImportSelector) {
    Assert.state(
            deferredImportSelector instanceof AutoConfigurationImportSelector,
            () -> String.format("Only %s implementations are supported, got %s",
                    AutoConfigurationImportSelector.class.getSimpleName(),
                    deferredImportSelector.getClass().getName()));
    // 這一步獲取所有的 EnableAutoConfiguration
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
            .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                    annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

private AutoConfigurationMetadata getAutoConfigurationMetadata() {
    if (this.autoConfigurationMetadata == null) {
        this.autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
    }
    return this.autoConfigurationMetadata;
}

注意:getAutoConfigurationMetadata() 會獲取 spring-autoconfigure-metadata.properties 中的內(nèi)容用于過濾 AutoConfiguration

AutoConfigurationMetadataLoader#loadMetadata
// PATH = "META-INF/spring-autoconfigure-metadata.properties"
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}

7. AutoConfigurationImportSelector#getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    // 判斷是否開啟了自動配置捅儒,可以在屬性里配置
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 獲取需要排除加載的類,在 @EnableAutoConfiguration 中配置
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 使用 SpringFactoriesLoader 加載 META-INF/spring.factories 文件中 EnableAutoConfiguration 的類
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    // 使用 Set 去重
    configurations = removeDuplicates(configurations);
    //  獲取需要排除加載的類名
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 校驗排除類是否是 auto-configuration 類业崖,如果不是則報錯野芒。
    // 也就是說排除的類必須是 META-INF/spring.factories 文件中 EnableAutoConfiguration 的類
    checkExcludedClasses(configurations, exclusions);
    // 去除排除類
    configurations.removeAll(exclusions);
    // 通過 Condition 過濾掉不符合條件的類,減少后續(xù)的操作双炕,加快啟動速度
    configurations = filter(configurations, autoConfigurationMetadata);
    // 把符合條件和不符合條件的 config 放入 ConditionEvaluationReport
    // 題外話:ConditionEvaluationReport 會在 ConditionEvaluationReportLoggingListener#ConditionEvaluationReportListener 監(jiān)聽器中被觸發(fā)
    // ConditionEvaluationReportLoggingListener 的初始化在 SpringApplication 構(gòu)造方法中
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

private List<String> filter(List<String> configurations,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    // SpringFactoriesLoader 加載 AutoConfigurationImportFilter 的實現(xiàn)狞悲,一般至少有三個,分別是
    // OnClassCondition妇斤,
    // OnWebApplicationCondition摇锋,
    // OnBeanCondition
    // 使用 Condition 過濾,這里詳細(xì)的不說的站超,有興趣的朋友自己看下吧荸恕。
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        invokeAwareMethods(filter);
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    // 如果沒有要跳過的則直接返回
    if (!skipped) {
        return configurations;
    }
    // 有跳過的
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                + " ms");
    }
    return new ArrayList<>(result);
}

Conclusion

  1. 通過以上,我們也可以定義自己的 AutoConfiguration死相,步驟也很簡單融求,只需要在 spring.factories 文件中如下定義:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\

如果你還想配置條件的話,可以在 spring-autoconfigure-metadata.properties 文件中配置如下:

org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration.ConditionalOnClass=org.springframework.security.crypto.encrypt.TextEncryptor

ConditionalOnClass 及以后的內(nèi)可以根據(jù)需要替換

  1. 關(guān)閉 AutoConfiguration :只要在系統(tǒng)屬性中配置 spring.boot.enableautoconfiguration=false 就可以了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末算撮,一起剝皮案震驚了整個濱河市生宛,隨后出現(xiàn)的幾起案子县昂,更是在濱河造成了極大的恐慌,老刑警劉巖陷舅,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倒彰,死亡現(xiàn)場離奇詭異,居然都是意外死亡莱睁,警方通過查閱死者的電腦和手機待讳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仰剿,“玉大人创淡,你說我怎么就攤上這事∷肘桑” “怎么了辩昆?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旨袒。 經(jīng)常有香客問我汁针,道長,這世上最難降的妖魔是什么砚尽? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任施无,我火速辦了婚禮,結(jié)果婚禮上必孤,老公的妹妹穿的比我還像新娘猾骡。我一直安慰自己,他們只是感情好敷搪,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布兴想。 她就那樣靜靜地躺著,像睡著了一般赡勘。 火紅的嫁衣襯著肌膚如雪嫂便。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天闸与,我揣著相機與錄音毙替,去河邊找鬼。 笑死践樱,一個胖子當(dāng)著我的面吹牛厂画,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拷邢,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼袱院,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坑填,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤抛人,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后脐瑰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡廷臼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年苍在,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荠商。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡寂恬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出莱没,到底是詐尸還是另有隱情初肉,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布饰躲,位于F島的核電站牙咏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嘹裂。R本人自食惡果不足惜妄壶,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寄狼。 院中可真熱鬧丁寄,春花似錦、人聲如沸泊愧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽删咱。三九已至屑埋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腋腮,已是汗流浹背雀彼。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留即寡,地道東北人徊哑。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像聪富,于是被迫代替她去往敵國和親莺丑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351