1. 程式人生 > >springboot 2.* 自動配置原理探究&demo實現

springboot 2.* 自動配置原理探究&demo實現

springboot-2.1.0 自動配置原理解析&demo實現

前言

最近準備升級線上服務的版本到springboot 2.1.0,所以抽空重新研究學習了下springboot的一些特性、原理。本文的核心就是學習理解下springboot的自動配置原理,版本是目前springboot的最新release版本:2.1.0。部落格包括如下幾個部分:

  1. 以server.port為例,探究自動配置的內部實現流程
  2. 總結下springboot自動配置涉及的知識點
  3. 實現一個自動配置的demo!

自動配置的流程探究

我們知道,對於springboot服務,只需要在application.properties/application.yml裡配置server.port,即可自動繫結web server的訪問埠,今天我們就一起來探究下這個自動配置服務埠號是如何實現的。

  • 環境準備

新建一個springboot 2.1.0專案,可以直接使用 springboot initialize進行生成,很方便,如圖1所示。

appliaction.properties配置一個引數,server.port=8888。然後啟動專案,檢視日誌,可以看到Tomcat服務的訪問埠已經變成了8888。

很顯然,服務埠的自動配置,目前只有一處觸發點,就是註解:@SpringBootApplication。我們需要探究下通過該註解,如何實現自動配置的實現。

  • @SpringBootApplication註解探究

@SpringBootApplication註解內部,依賴多個註解,大部分註解都是java的常規註解,看命名就能發現@EnableAutoConfiguration這個註解應該是我們想要的,所以繼續看這個註解。

@EnableAutoConfiguration註解內部有兩個註解需要引起注意,一個是@AutoConfigurationPackage註解,一個是@import一個類:AutoConfigurationImportSelector.class

@AutoConfigurationPackage註解表示啥意思呢?來,一起谷歌一下:解釋如下:
啟動自動配置,該註解開啟,使用Spring boot啟動專案時,就會把找的jar包,自動配置整合。

ok,很重要的註解。也就是說通過該註解,我們會去找需要需要自動配置的類。那如何確認需要自動配置哪些類呢?我們再看下AutoConfigurationImportSelector.class這個類,看命名就曉得跟自動配置很相關。看下方法,比較多,無從下手。ok看下實現了哪些介面吧,該類一共實現了6個介面,那我們就從這幾個介面來著手,第一個介面@DeferredImportSelector,他還繼承一個父介面:ImportSelect,好的,我們繼續谷歌一下,看看這個接口乾嘛的?

@ImportSelect主要作用是收集需要匯入的配置類,如果該介面的實現類同時實現EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware或者ResourceLoaderAware,那麼在呼叫其selectImports方法之前先呼叫上述介面中對應的方法,如果需要在所有的@Configuration處理完在匯入時可以實現DeferredImportSelector介面。

我們這邊就是實現的@DeferredImportSelector,很棒。那說明通過此介面的方法,可以收集需要匯入的配置類。那我們一起看下實現該介面的方法:selectImports()

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

該方法內部掉用了一個getAutoConfigurationEntry()方法,看方法命名,非常有戲。我們繼續看該方法內部。

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

不要被那麼多行程式碼所迷惑,仔細看會發現,核心在與獲取的configurations,我們會發現List

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }

可以看到,核心在於SpringFactoriesLoader.loadFactoryNames()這個方法。繼續往裡探究下,最終,我們發現是呼叫了下圖這個方法。對此,我們不展開對於這塊程式碼的分析,直接拋結論,這塊程式碼就是找resources/META-INF路徑下的spring.factories裡的自動配置類。

我們可以去依賴裡,找個spring.factories裡探個究竟,看下圖,我們查看了spring-boot-autoconfiguration這個jar內部的spring.factories,可以看到springboot啟動時大部分的自動配置類。

到這裡,基於上述的各類分析,我們簡單總結下:
springboot服務,通過服務入口類的註解,實現自動配置能力,實現的流程時,在啟動時,會去掃描resources/META-INF路徑下的spring.factories裡的自動配置類,從而實現類的自動配置。

ok,知曉了自動配置的大致原理,但我們還是缺少很多資訊,比如,對於前文提到的tomcat埠的配置,到底是如何完成的?

通過上面的原始碼探究,我們知道,每一個自動配置項,都依賴對應的自動配置類,所以這個tomcat埠的自動配置,肯定也依賴一個自動配置類,我們可以在依賴項中找出來。我們在spring-boot-autoconfiguration裡的spring.factories查詢下web server相關的自動配置項,跟著感覺走,看命名類似的,可以找到這個配置類:ServletWebServerFactoryAutoConfiguration。我們一起看下這個配置類的原始碼:

看到方法註釋,瞭然啦!就是這個類實現了web server的自動配置。首先我們分析下這個類的註解,我們會發現裡面有很多Contional開頭的註解,偷偷告訴你,這是精髓。

看方法,發現這個方法就是我們需要的:

@Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

該方法的入參是ServerProperties類,這個類很重要,因為我們發現最上面的註解要求,只有在該類存在的情況下,才注入ServletWebServerFactoryAutoConfiguration這個類。所以我們必須探究下ServerProperties這個類:

哇哦,有沒有看到很熟悉的東西,prefix = "server" 字首等於“server”,這不就是我們最開始在application.properties裡配置的內容嗎?

好吧,原來我們配置的server.*的內容,就是ServerProperties裡定義的屬性哇。然後回到前面的ServletWebServerFactoryAutoConfiguration類,可以看到通過ServerProperties類,構建了TomcatServletWebServerFactoryCustomizer類,而此類的作用就是構建一個 Tomcat Web。

原理總結

現在整體梳理下springboot自動配置的流程,我們通過一個屬性類,配置一堆具備公共字首的屬性,然後新建一個自動配置類,通過一些特定條件,完成自動配置的動作。

再上一層,我們需要將自動配置類,填寫在resources/META-INF路徑下的spring.factories。然後springboot啟動時,會去掃描這裡的所有自動配置資訊,完成類的注入。

以上整個過程,似乎比較簡單,但其實完成起來還是很複雜的。比如我們回到剛剛的ServletWebServerFactoryAutoConfiguration類,我們會發現,Tomcat服務類的注入,有一個依賴條件:@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")。這裡表示的含義是隻有在org.apache.catalina.startup.Tomcat這個類存在時,才注入該類。其實,自動配置能力的實現,主要就歸功於@Conditional註解。

  • @Conditional

對於@Conditional註解的原理實現,這裡不做展開,@Conditional是由Spring 4提供的一個新特性,用於根據特定條件來控制Bean的建立行為。原理很簡單,其實該註解依賴於FilteringSpringBootCondition這個類的match方法。我們這邊介紹下@Conditional開頭的常用的幾個註解的含義,完整的註解 可以去org.springframework.boot.autoconfigure.condition這個package下自己研究下。

@ConditionalOnBean,表示當具體的bean存在時建立該bean。
@ConditionalOnClass,表示當具體的class存在時
@ConditionalOnMissingBean,表示缺失具體bean時。
@ConditionalOnWebApplication(type = Type.SERVLET), 是當該應用是基於Servlet的Web應用時。
@ConditionalOnMissingBean,是當Spring容器中不存在某個Bean時。
@ConditionalOnProperty,指定的屬性是否有。如果沒有(IfMissing),設為true。

  • 總結
  1. 利用@ConfigurationProperties註解,完成公共字首配置的類建立
  2. 利用@Condional開頭的註解,來實現特定條件下自動配置類
  3. 實現一個服務類,作為自動配置類的配置物件
  4. 利用springboot啟動註解中依賴的@EnableAutoConfiguration註解,在resources/META-INF路徑下的spring.factories裡新增上需要自動配置的類
  5. done。

demo實現

上面討論了一大堆原始碼分析和原理說明,下面直接以實現一個demo作為本文結束

我們實現一個demo,來進行springboot服務的作者資訊的自動配置。實現的效果,就是我們在application.properties裡增加對應的配置項,即可完成類的注入。

自動配置類的實現

首先我們需要建立一個專案,後續作為我們springboot專案的依賴。具體需要做的工作總結如下:

  1. 建立你一個maven專案,引入依賴:spring-boot-autoconfigure

  2. 建立一個屬性類,包裝需要自動配置的屬性,該屬性類上增加@ConfigurationProperties註解,在此註解上配置對應的字首

  3. 建立一個Bean,作為自動配置完成後注入的bean

  4. 建立一個自動配置類,完成獲取屬性,封裝例項bean的工作.這裡我門用到ConditionOnClass註解,要求只有在AutherServer類存在時候,該類才注入。

  5. 在resources目錄下,新建META-INF/spring.factories檔案,配置需要進行自動配置的配置類,格式如下

  6. maven本地install

測試

  1. 我們建立一個新的springboot專案,引入之前建立專案的依賴
        <dependency>
            <groupId>com.trey</groupId>
            <artifactId>auther-autoconfigure</artifactId>
            <version>1.0.0</version>
        </dependency>
  1. application.properties裡增加對應的配置,我們這邊增加一個配置:auther.name=doudou

  2. 編寫測試controller,啟動服務測試

  3. 訪問測試頁面127.0.0.1:8888/auther,測試結果如下

demo程式碼分享

對於上述的demo程式碼,我已上傳至github,github專案地址為:https://github.com/trey-tao/springboot-learn,可以下載學習。

這個專案主要是分享一些springboot的demo練習,會不定期更新,歡迎一起學習。