SpringBoot自動配置分析

一、注解

@SpringBootApplication 注解

@SpringBootApplication 注解實際上是一個組合注解唉锌,它由三個注解組合而成,分別是 @SpringBootConfiguration弄砍、@EnableAutoConfiguration 和 @ComponentScan

@ComponentScan 注解

@ComponentScan 注解不是 Spring Boot 引入的新注解披坏,而是屬于 Spring 容器管理的內(nèi)容。@ComponentScan 注解就是掃描基于 @Component 等注解所標注的類所在包下的所有需要注入的類馏慨,并把相關 Bean 定義批量加載到容器中埂淮。

@ComponentScan注解的處理邏輯在Configuration解析時的ConfigurationClassParser中進行處理

#調(diào)用棧
doProcessConfigurationClass:281, ConfigurationClassParser (org.springframework.context.annotation)
processConfigurationClass:245, ConfigurationClassParser (org.springframework.context.annotation)
parse:202, ConfigurationClassParser (org.springframework.context.annotation)
parse:170, ConfigurationClassParser (org.springframework.context.annotation)
processConfigBeanDefinitions:331, ConfigurationClassPostProcessor (org.springframework.context.annotation)
postProcessBeanDefinitionRegistry:233, ConfigurationClassPostProcessor (org.springframework.context.annotation)
invokeBeanDefinitionRegistryPostProcessors:303, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:106, PostProcessorRegistrationDelegate (org.springframework.context.support)
invokeBeanFactoryPostProcessors:739, AbstractApplicationContext (org.springframework.context.support)
refresh:536, AbstractApplicationContext (org.springframework.context.support)
// @ComponentScan注解處理
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
            throws IOException {
    ......
    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) {
            // The config class is annotated with @ComponentScan -> perform the scan immediately
            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
            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());
                }
            }
        }
    }
    ......
}

使用ClassPathBeanDefinitionScanner掃描basePackage下的@Component注解類,遞歸解析成BeanDefinition并通過 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry) 添加到 DefaultListableBeanFactory - beanDefinitionMap 中(這個時候bean并沒有進行實例化写隶,而是進行了注冊倔撞。具體的實例化在finishBeanFactoryInitialization方法中執(zhí)行)。

@SpringBootConfiguration 注解

@SpringBootConfiguration 注解比較簡單慕趴,只是使用了 Spring 中的 @Configuration 注解痪蝇。@Configuration 注解比較常見,提供了 JavaConfig 配置類實現(xiàn)秩贰。具體實現(xiàn)在 ConfigurationClassParser#parse 中霹俺,涉及對 @PropertySources、@ComponentScan毒费、@Import丙唧、@ImportResource、@Bean觅玻、@DeferredImportSelector 注解的處理想际。

@EnableAutoConfiguration 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

這里我們關注兩個新注解,@AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage 注解

@AutoConfigurationPackage 注解主要就是引用@Import(AutoConfigurationPackages.Registrar.class)

@Import 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

在 @Import 注解的屬性中可以設置需要引入的類名溪厘,例如 @AutoConfigurationPackage 注解上的 @Import(AutoConfigurationPackages.Registrar.class)胡本。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
        Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    if (importCandidates.isEmpty()) {
        return;
    }
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {
                    // Candidate class is an ImportSelector -> delegate to it to determine imports
                    Class<?> candidateClass = candidate.loadClass();
                    ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            selector, this.environment, this.resourceLoader, this.registry);
                    if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectors.add(
                                new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                    }
                    else {
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                        processImports(configClass, currentSourceClass, importSourceClasses, false);
                    }
                }
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // Candidate class is an ImportBeanDefinitionRegistrar ->
                    // delegate to it to register additional bean definitions
                    Class<?> candidateClass = candidate.loadClass();
                    ImportBeanDefinitionRegistrar registrar =
                            BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                    ParserStrategyUtils.invokeAwareMethods(
                            registrar, this.environment, this.resourceLoader, this.registry);
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                }
                else {
                    // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                    // process it as an @Configuration class
                    this.importStack.registerImport(
                            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    processConfigurationClass(candidate.asConfigClass(configClass));
                }
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                    "Failed to process import candidates for configuration class [" +
                    configClass.getMetadata().getClassName() + "]", ex);
        }
        finally {
            this.importStack.pop();
        }
    }
}

根據(jù)該類的不同類型,Spring 容器針對 @Import 注解有以下四種處理方式:

  • 如果該類實現(xiàn)了 ImportSelector 接口畸悬,調(diào)用其 selectImports 方法侧甫,返回spring.factories 中配置的類,然后遞歸調(diào)用 processImports 進行處理,最終會被保存在 configurationClasses 中披粟;
  • 如果該類實現(xiàn)了 DeferredImportSelector 接口咒锻,則 Spring 容器也會實例化該類并調(diào)用其 selectImports方法。DeferredImportSelector 繼承了 ImportSelector守屉,區(qū)別在于DeferredImportSelector 實例先保存在 deferredImportSelectors 中惑艇,要等到 @Configuration 注解中相關的業(yè)務全部都處理完了才會通過 processDeferredImportSelectors 調(diào)用 selectImports 方法;
  • 如果該類實現(xiàn)了 ImportBeanDefinitionRegistrar 接口拇泛,會把Metadata 保存importBeanDefinitionRegistrars 中滨巴,在解析完Configuration 后通過 this.reader.loadBeanDefinitions(configClasses),最后在loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()) 中調(diào)用其 registerBeanDefinitions 方法俺叭;
  • 如果該類沒有實現(xiàn)上述三種接口中的任何一個恭取,把這個類當成是@Configuration注解修飾的類遞歸重頭開始解析這個類;

關于注解就到這里绪颖,下面重點分析下 @Import 的兩個類 AutoConfigurationPackages.Registrar 和 AutoConfigurationImportSelector

二秽荤、AutoConfigurationPackages.Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            register(registry, new PackageImport(metadata).getPackageName());
        }
 
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }
}

可以看到這個 Registrar 類實現(xiàn)了前面第三種情況中提到的 ImportBeanDefinitionRegistrar 接口并重寫了 registerBeanDefinitions 方法,該方法中調(diào)用 AutoConfigurationPackages 自身的 register 方法:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition
                    .getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0,
                    addBasePackages(constructorArguments, packageNames));
        }
        else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
                    packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
}

這個方法的邏輯是先判斷整個 Bean 有沒有被注冊柠横,如果已經(jīng)注冊則獲取 Bean 的定義窃款,通過 Bean 獲取構造函數(shù)的參數(shù)并添加參數(shù)值;如果沒有牍氛,則創(chuàng)建一個新的 Bean 的定義晨继,設置 Bean 的類型為 AutoConfigurationPackages 類型并進行 Bean 的注冊,最終保存在 DefaultListableBeanFactory 的 beanDefinitionMap / beanDefinitionNames中搬俊。

三紊扬、AutoConfigurationImportSelector

AutoConfigurationImportSelector 類實現(xiàn)了 @Import 注解第二種情況中的 DeferredImportSelector 接口,所以會執(zhí)行如下所示的 selectImports 方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
 
        //獲取 configurations 集合
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return StringUtils.toStringArray(configurations);
}

這段代碼的核心是通過 getCandidateConfigurations 方法獲取 configurations 集合并進行過濾唉擂。getCandidateConfigurations 方法如下所示:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
}

AutoConfigurationImportSelector 所依賴的最關鍵組件就是 SpringFactoriesLoader餐屎。SpringFactoriesLoader 以服務接口命名的文件是放在 META-INF/spring.factories 文件夾下,對應的 Key 為 EnableAutoConfiguration玩祟。SpringFactoriesLoader 會查找所有 META-INF/spring.factories 文件夾中的配置文件腹缩,并把 Key 為 EnableAutoConfiguration 所對應的配置項通過反射實例化為配置類并加載到容器中。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
 
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
}

以下就是 spring-boot-autoconfigure 工程中所使用的 spring.factories 配置文件片段空扎,可以看到 EnableAutoConfiguration 項中包含了各式各樣的配置項藏鹊,這些配置項在 Spring Boot 啟動過程中都能夠通過 SpringFactoriesLoader 加載到運行時環(huán)境,從而實現(xiàn)自動化配置:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
…

以上就是 Spring Boot 中基于 @SpringBootApplication 注解實現(xiàn)自動配置的基本過程和原理转锈。當然盘寡,@SpringBootApplication 注解也可以基于外部配置文件加載配置信息〈榭基于約定優(yōu)于配置思想竿痰,Spring Boot 在加載外部配置文件的過程中大量使用了默認配置脆粥。
---------over---------

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市菇曲,隨后出現(xiàn)的幾起案子冠绢,更是在濱河造成了極大的恐慌,老刑警劉巖常潮,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異楷力,居然都是意外死亡喊式,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門萧朝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岔留,“玉大人,你說我怎么就攤上這事检柬∠琢” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵何址,是天一觀的道長里逆。 經(jīng)常有香客問我,道長用爪,這世上最難降的妖魔是什么原押? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮偎血,結果婚禮上诸衔,老公的妹妹穿的比我還像新娘。我一直安慰自己颇玷,他們只是感情好笨农,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帖渠,像睡著了一般谒亦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阿弃,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天诊霹,我揣著相機與錄音,去河邊找鬼渣淳。 笑死脾还,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的入愧。 我是一名探鬼主播鄙漏,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼嗤谚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怔蚌?” 一聲冷哼從身側響起巩步,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桦踊,沒想到半個月后椅野,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡籍胯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年竟闪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杖狼。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡炼蛤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蝶涩,到底是詐尸還是另有隱情理朋,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布绿聘,位于F島的核電站嗽上,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斜友。R本人自食惡果不足惜炸裆,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲜屏。 院中可真熱鬧石景,春花似錦够掠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禽车。三九已至既绕,卻和暖如春腋颠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背忆嗜。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工己儒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捆毫。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓闪湾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绩卤。 傳聞我的和親對象是個殘疾皇子途样,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容