Spring @Import 機制

Spring @Import 機制

@Import 注解是 Spring 3.0 引入的一個新注解送悔,用于 import Configuration慢显,使用了這個注解爪模,相當于 Java 中的 import 關鍵字,可以導入 Spring 的 bean 到 IOC 容器中荚藻。

Spring Boot 的自動裝配就是基于 @Import 機制屋灌。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    Class<?>[] value();

}

@Import 注解的解析

在簡單的 demo 項目中,查看 @Import 注解的引用应狱,除了作為注解使用共郭,就只有在 ConfigurationClassParser 這個類中使用:

    /**
     * @param sourceClass 需要搜索的 class
       * @param imports 到目前為止已經(jīng)找到的 imports
       * @param visited 去重,可能有些已經(jīng)解析過了
     */
    private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException {
        if (visited.add(sourceClass)) {
            // 遞歸查找當前 class 的注解以及注解的注解中的 @Import
            for (SourceClass annotation : sourceClass.getAnnotations()) {
                String annName = annotation.getMetadata().getClassName();
                if (!annName.equals(Import.class.getName())) {
                    collectImports(annotation, imports, visited);
                }
            }
            // 添加當前 class 的 @Import 注解 import 進來的類
            imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
        }
    }

只有這個方法中對 @Import 注解進行了解析疾呻,內部邏輯就是遞歸查找當前類的注解除嘹,包括當前類的注解的注解(類似于 Java 中的繼承)尉咕,找到所有的 @Import 標注的 class 并且加到 imports 中璃岳。

@Import 的解析流程

查看調用 collectImports 的方法,發(fā)現(xiàn)就是 collectImports 上方的 getImports 方法单芜,而 getImports 方法又在 doProcessConfigurationClass 中被調用犁柜,doProcessConfigurationClass 又被 processConfigurationClass 調用赁温,processConfigurationClass 又在 parse 方法中調用,parseConfigurationClassPostProcessor 類的 processConfigBeanDefinitions 中被調用袜匿,繼續(xù)查看是在 postProcessBeanDefinitionRegistrypostProcessBeanFactory 中被調用稚疹,通過 debug 發(fā)現(xiàn)是在 postProcessBeanDefinitionRegistry 中調用,再往上是 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors怪嫌,而這個方法是在 AbstractApplicationContext.invokeBeanFactoryPostProcessors 中被調用岩灭,整理整個流程如下圖所示:

sequenceDiagram

AbstractApplicationContext ->> AbstractApplicationContext:refresh
AbstractApplicationContext ->> AbstractApplicationContext:invokeBeanFactoryPostProcessors
AbstractApplicationContext ->> PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate ->> PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors
PostProcessorRegistrationDelegate ->> ConfigurationClassPostProcessor :postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor ->> ConfigurationClassPostProcessor:processConfigBeanDefinitions
ConfigurationClassPostProcessor ->> ConfigurationClassParser:parse
ConfigurationClassParser ->> ConfigurationClassParser:processConfigurationClass
ConfigurationClassParser ->> +ConfigurationClassParser:doProcessConfigurationClass
ConfigurationClassParser ->> ConfigurationClassParser:getImports
ConfigurationClassParser ->> -ConfigurationClassParser:processImports

獲取到所有的 @Import 中的 value 之后噪径,再執(zhí)行 processImports 方法:

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 {
      // importStack 用來進行遞歸循環(huán)處理,可能當前處理的 import 中 import 進來了新的 import
            this.importStack.push(configClass);
            try {
        // 遍歷所有被 import 的類梗顺,并根據(jù)所屬類型執(zhí)行對應的操作
        // 如果是 ImportSelector车摄,執(zhí)行 select 操作并遞歸處理
        // 如果是 ImportBeanDefinitionRegistrar,添加到當前 configClass 的 importBeanDefinitionRegistrars 中
        // 否則其他所有的都當做 Configuration 來處理变屁,調用 processConfigurationClass
                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 (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        }
                        else {
              // ImportSelector 返回的是類名數(shù)組敞贡,可能又返回了一個 ImportSelector摄职,所以遞歸處理
                            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 {
                        // 當做 @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();
            }
        }
    }

常見的 @Import 用的 value 都是 ImportSelector 類型的,比如 @EnableAutoConfiguration 就用了 AutoConfigurationImportSelector迫悠,而在 AutoConfigurationImportSelector 中的 selectImports 方法僅僅是從 spring.factories 文件中讀取 org.springframework.boot.autoconfigure.EnableAutoConfiguration 對應的 value 并做簡單處理后返回巩梢,由 Spring 自己去處理這些 value,而開發(fā)者想要有自己的 EnableXxx 注解鞠抑,在 /resources/META-INF/spring.factories 文件中添加如下信息忌警,Spring Boot 就會加載這個 Xxx 類并交給 Spring 處理:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=Xxx

@Import 注解并不是 Spring Boot 獨有的,但是 Spring Boot 借 @Import 機制來自動加載處理自動裝配的類箕速,進而實現(xiàn)自動裝配朋譬。(ps: EnableXxx 模式也不是 Spring Boot 獨有的,Spring Framework 中就已經(jīng)有這樣模式的使用了字柠,Spring Boot 基于此進行擴展)

了解了 @Import 機制后,接下來繼續(xù)深入 Spring Boot 的自動裝配機制扶关。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末数冬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子铜异,更是在濱河造成了極大的恐慌秸架,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚂子,死亡現(xiàn)場離奇詭異食茎,居然都是意外死亡馏谨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門哎媚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喊儡,“玉大人,你說我怎么就攤上這事截珍÷崞樱” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵钱床,是天一觀的道長查牌。 經(jīng)常有香客問我,道長纸颜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任唠倦,我火速辦了婚禮涮较,結果婚禮上,老公的妹妹穿的比我還像新娘候齿。我一直安慰自己闺属,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布润匙。 她就那樣靜靜地躺著,像睡著了一般匠楚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芋簿,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天与斤,我揣著相機與錄音,去河邊找鬼磷支。 笑死食寡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的抵皱。 我是一名探鬼主播辩蛋,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼悼院,長吁一口氣:“原來是場噩夢啊……” “哼据途!你這毒婦竟也來了剑鞍?” 一聲冷哼從身側響起昨凡,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤便脊,失蹤者是張志新(化名)和其女友劉穎光戈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晌杰,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡筷弦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年烂琴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奸绷。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖反症,靈堂內的尸體忽然破棺而出畔派,到底是詐尸還是另有隱情,我是刑警寧澤该酗,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站悔叽,受9級特大地震影響,放射性物質發(fā)生泄漏娇澎。R本人自食惡果不足惜睹晒,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一伪很、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锉试,春花似錦、人聲如沸呆盖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尤筐。三九已至,卻和暖如春叔磷,著一層夾襖步出監(jiān)牢的瞬間奖磁,已是汗流浹背繁疤。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工稠腊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人架忌。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像挠羔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子破加,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內容