本文,咱們來分享 Spring Boot 自動配置的實現源碼。在故事的開始,咱們先來講兩個事情:html
在這篇文章的開始,艿艿是有點混淆自動配置和自動裝配的概念,後來通過 Google 以後,發現二者是截然不如同的:java
spring-boot-starter-web
以後,就自動引入了 Spring MVC 相關的 jar 包,從而自動配置 Spring MVC 。因此,不要和艿艿同樣愚蠢的搞錯落。git
胖友能夠直接看 《詳解 Spring Boot 自動配置機制》 文章的 「2、Spring Boot 自動配置」 小節,艿艿以爲寫的挺清晰的。github
下面,咱們即開始正式擼具體的代碼實現了。web
org.springframework.boot.autoconfigure.@SpringBootApplication
註解,基本咱們的 Spring Boot 應用,必定會去有這樣一個註解。而且,經過使用它,不只僅能標記這是一個 Spring Boot 應用,並且可以開啓自動配置的功能。這是爲何呢?spring
😈
@SpringBootApplication
註解,它在spring-boot-autoconfigure
模塊中。因此,咱們使用 Spring Boot 項目時,若是不想使用自動配置功能,就不用引入它。固然,咱們貌似不太會存在這樣的需求,是吧~數組
@SpringBootApplication
是一個組合註解。代碼以下:網絡
// SpringBootApplication.java |
下面,咱們來逐個看 @SpringBootApplication
上的每一個註解。app
Java 自帶的註解。ide
java.lang.annotation.@Inherited
註解,使用此註解聲明出來的自定義註解,在使用此自定義註解時,若是註解在類上面時,子類會自動繼承此註解,不然的話,子類不會繼承此註解。
這裏必定要記住,使用 @Inherited
聲明出來的註解,只有在類上使用時纔會有效,對方法,屬性等其餘無效。
不瞭解的胖友,能夠看看 《關於 Java 註解中元註解 Inherited 的使用詳解》 文章。
Spring Boot 自定義的註解
org.springframework.boot.@SpringBootConfiguration
註解,標記這是一個 Spring Boot 配置類。代碼以下:
// SpringBootConfiguration.java |
@Configuration
註解,因此二者功能也一致,能夠將當前類內聲明的一個或多個以 @Bean
註解標記的方法的實例歸入到 Srping 容器中,而且實例名就是方法名。Spring 自定義的註解
org.springframework.context.annotation.@ComponentScan
註解,掃描指定路徑下的 Component(@Componment
、@Configuration
、@Service
等等)。
不瞭解的胖友,能夠看看 《Spring:@ComponentScan 使用》 文章。
Spring Boot 自定義的註解
org.springframework.boot.autoconfigure.@EnableAutoConfiguration
註解,用於開啓自動配置功能,是 spring-boot-autoconfigure
項目最核心的註解。代碼以下:
// EnableAutoConfiguration.java |
org.springframework.boot.autoconfigure.@AutoConfigurationPackage
註解,主要功能自動配置包,它會獲取主程序類所在的包路徑,並將包路徑(包括子包)下的全部組件註冊到 Spring IOC 容器中。代碼以下:
// AutoConfigurationPackage.java |
org.springframework.context.annotation.@Import
註解,可用於資源的導入。狀況比較多,能夠看看 《六、@Import 註解——導入資源》 文章。@Import(AutoConfigurationImportSelector.class)
註解部分,是重頭戲的開始。
org.springframework.context.annotation.@Import
註解,可用於資源的導入。狀況比較多,能夠看看 《六、@Import 註解——導入資源》 文章。org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
,實現 DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口,處理 @EnableAutoConfiguration
註解的資源導入。
#getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到符合條件的配置類的數組。代碼以下:
// AutoConfigurationImportSelector.java |
<1>
處,調用 #getSpringFactoriesLoaderFactoryClass()
方法,得到要從 META-INF/spring.factories
加載的指定類型爲 EnableAutoConfiguration 類。代碼以下:
// AutoConfigurationImportSelector.java |
<1>
處,調用 SpringFactoriesLoader#loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)
方法,加載指定類型 EnableAutoConfiguration 對應的,在 META-INF/spring.factories
裏的類名的數組。看看下圖,胖友相信就明白了:
通常來講,和網絡上 Spring Boot 勇於這塊的源碼解析,咱們就能夠結束了。若是單純是爲了瞭解原理 Spring Boot 自動配置的原理,這裏結束也是沒問題的。由於,拿到 Configuration 配置類後,後面的就是 Spring Java Config 的事情了。不瞭解的胖友,能夠看看 《Spring 教程 —— 基於 Java 的配置》 文章。
😜 可是(「可是」同窗,你趕忙坐下),具備倒騰精神的艿艿,以爲仍是繼續瞅瞅 #getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法是怎麼被調用的。因此,咱們來看看調用它的方法調用鏈,以下圖所示:
#getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法被調用。③ 處,那麼此處,就是問題的關鍵。代碼以下:
艿艿:由於我還沒特別完整的擼完 Spring Java Annotations 相關的源碼,因此下面的部分,咱們更可能是看整個調用過程。😈 剛好,胖友也沒看過,哈哈哈哈。
// ConfigurationClassParser#DeferredImportSelectorGroupingHandler.java |
<1>
處,調用 DeferredImportSelector.Group#process(AnnotationMetadata metadata, DeferredImportSelector selector)
方法,處理被 @Import
註解的註解。<2>
處,調用 DeferredImportSelector.Group#this.group.selectImports()
方法,選擇須要導入的。例如:<1>
和 <2>
處,在 「5.3 AutoConfigurationGroup」 詳細解析。#getImportGroup()
方法,得到對應的 Group 實現類。代碼以下:
// AutoConfigurationImportSelector.java |
艿艿:注意,從這裏開始後,東西會比較難。由於,涉及的東西會比較多。
AutoConfigurationGroup ,是 AutoConfigurationImportSelector 的內部類,實現 DeferredImportSelector.Group、BeanClassLoaderAware、BeanFactoryAware、ResourceLoaderAware 接口,自動配置的 Group 實現類。
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
entries
屬性,AnnotationMetadata 的映射。其中,KEY 爲 配置類的全類名。在後續咱們將看到的 AutoConfigurationGroup#process(...)
方法中,被進行賦值。例如:autoConfigurationEntries
屬性,AutoConfigurationEntry 的數組。
其中,AutoConfigurationEntry 是 AutoConfigurationImportSelector 的內部類,自動配置的條目。代碼以下:
// AutoConfigurationImportSelector#AutoConfigurationEntry.java |
AutoConfigurationGroup#process(...)
方法中,被進行賦值。例如:autoConfigurationMetadata
屬性,自動配置的元數據(Metadata)。
經過 #getAutoConfigurationMetadata()
方法,會初始化該屬性。代碼以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
@ConditionalOnClass({ Bucket.class, ReactiveCouchbaseRepository.class, Flux.class })
註解部分。autoConfigurationMetadata
屬性,用途就是制定配置類(Configuration)的生效條件(Condition)。#process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)
方法,進行處理。代碼以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
annotationMetadata
參數,通常來講是被 @SpringBootApplication
註解的元數據。由於,@SpringBootApplication
組合了 @EnableAutoConfiguration
註解。deferredImportSelector
參數,@EnableAutoConfiguration
註解的定義的 @Import
的類,即 AutoConfigurationImportSelector 對象。<1>
處,調用 AutoConfigurationImportSelector#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata)
方法,得到 AutoConfigurationEntry 對象。詳細解析,見 「5.4 AutoConfigurationEntry」 。由於這塊比較重要,因此先跳過去瞅瞅。<2>
處,添加到 autoConfigurationEntries
中。<3>
處,添加到 entries
中。#selectImports()
方法,得到要引入的配置類。代碼以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
<1>
處,若是爲空,則返回空數組。<2.1>
、<2.2>
、<2.3>
處,得到要引入的配置類集合。😈 比較奇怪的是,上面已經作過一次移除的處理,這裏又作一次。不過,沒多大關係,能夠先無視。<3>
處,處理,返回結果。
<3.1>
處,調用 #sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata)
方法,排序。代碼以下:
// AutoConfigurationImportSelector#AutoConfigurationGroup.java |
@Order
註解。<3.2>
處,建立 Entry 對象。<3.3>
處,轉換成 List 。結果以下圖:艿艿:略微有點艱難的過程。不過回過頭來,其實也沒啥特別複雜的邏輯。是不,胖友~
艿艿:這是一個關鍵方法。由於會調用到,咱們會在 「5.1 getCandidateConfigurations」 的方法。
#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata)
方法,得到 AutoConfigurationEntry 對象。代碼以下:
// AutoConfigurationImportSelector.java |
這裏每一步都是細節的方法,因此會每個方法,都會是引導到對應的小節的方法。
雖然有點長,可是很不復雜。簡單的來講,加載符合條件的配置類們,而後移除須要排除(exclusion)的。
<1>
處,調用 #isEnabled(AnnotationMetadata metadata)
方法,判斷是否開啓。如未開啓,返回空數組。詳細解析,見 「5.4.1 isEnabled」 。<2>
處,調用 #getAttributes(AnnotationMetadata metadata)
方法,得到註解的屬性。詳細解析,見 「5.4.2 getAttributes」 。【重要】<3>
處,調用 #getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到符合條件的配置類的數組。
嘻嘻,到達此書以後,整個細節是否是就串起來了!
<3.1>
處,調用 #removeDuplicates(List<T> list)
方法,移除重複的配置類。代碼以下:
// AutoConfigurationImportSelector.java |
<4>
處,調用 #getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到須要排除的配置類。詳細解析,見 「5.4.3 getExclusions」 。
<4.1>
處,調用 #checkExcludedClasses(List<String> configurations, Set<String> exclusions)
方法,校驗排除的配置類是否合法。詳細解析,見 「5.4.4 checkExcludedClasses」 。<4.2>
處,從 configurations
中,移除須要排除的配置類。<5>
處,調用 #filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata)
方法,根據條件(Condition),過濾掉不符合條件的配置類。詳細解析,見 《精盡 Spring Boot 源碼分析 —— Condition》 文章。
<6>
處,調用 #fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions)
方法,觸發自動配置類引入完成的事件。詳細解析,見 「5.4.5 fireAutoConfigurationImportEvents」 。<7>
處,建立 AutoConfigurationEntry 對象。整個 「5.4 getAutoConfigurationEntry」 看完後,胖友請跳回 「5.3.3 selectImports」 。
#isEnabled(AnnotationMetadata metadata)
方法,判斷是否開啓自動配置。代碼以下:
// AutoConfigurationImportSelector.java |
#getAttributes(AnnotationMetadata metadata)
方法,得到註解的屬性。代碼以下:
// AutoConfigurationImportSelector.java |
getAnnotationClass().getName()
返回的是 @EnableAutoConfiguration
註解,因此這裏返回的註解屬性,只能是 exclude
和 excludeName
這兩個。舉個例子,假設 Spring 應用上的註解以下:
|
#getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法,得到須要排除的配置類。代碼以下:
// AutoConfigurationImportSelector.java |
該方法會調用以下的方法,比較簡單,胖友本身瞅瞅。
// AutoConfigurationImportSelector.java |
#checkExcludedClasses(List<String> configurations, Set<String> exclusions)
方法,校驗排除的配置類是否合法。代碼以下:
// AutoConfigurationImportSelector.java |
exclusions
存在於 classpath 中,可是不存在 configurations
。這樣作的目的是,若是不存在的,就不要去排除啦!#fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions)
方法,觸發自動配置類引入完成的事件。代碼以下:
// AutoConfigurationImportSelector.java |
<1>
處,調用 #getAutoConfigurationImportListeners()
方法,加載指定類型 AutoConfigurationImportListener 對應的,在 META-INF/spring.factories
裏的類名的數組。例如:<2>
處,建立 AutoConfigurationImportEvent 事件。<3>
處,遍歷 AutoConfigurationImportListener 監聽器們,逐個通知。
<3.1>
處,調用 #invokeAwareMethods(Object instance)
方法,設置 AutoConfigurationImportListener 的屬性。代碼以下:
// AutoConfigurationImportSelector.java |
<3.2>
處,調用 AutoConfigurationImportListener#onAutoConfigurationImportEvent(event)
方法,通知監聽器。目前只有一個 ConditionEvaluationReportAutoConfigurationImportListener 監聽器,沒啥邏輯,有興趣本身看哈。
org.springframework.boot.autoconfigure.AutoConfigurationPackages
,自動配置所在的包名。可能這麼解釋有點怪怪的,咱們來看下官方註釋:
Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner). |
@AutoConfigurationPackage
註解的類所在的包(package
),註冊成一個 Spring IoC 容器中的 Bean 。醬紫,後續有其它模塊須要使用,就能夠經過得到該 Bean ,從而得到所在的包。例如說,JPA 模塊,須要使用到。是否是有點神奇,艿艿也以爲。
Registrar ,是 AutoConfigurationPackages 的內部類,實現 ImportBeanDefinitionRegistrar、DeterminableImports 接口,註冊器,用於處理 @AutoConfigurationPackage
註解。代碼以下:
// AutoConfigurationPackages#Registrar.java |
PackageImport 是 AutoConfigurationPackages 的內部類,用於得到包名。代碼以下:
// AutoConfigurationPackages#Registrar.java |
<X>
處,調用 #register(BeanDefinitionRegistry registry, String... packageNames)
方法,註冊一個用於存儲報名(package
)的 Bean 到 Spring IoC 容器中。詳細解析,見 「6.2 register」 。#register(BeanDefinitionRegistry registry, String... packageNames)
方法,註冊一個用於存儲報名(package
)的 Bean 到 Spring IoC 容器中。代碼以下:
// AutoConfigurationPackages.java |
註冊的 BEAN
的類型,爲 BasePackages 類型。它是 AutoConfigurationPackages 的內部類。代碼以下:
// AutoConfigurationPackages#BasePackages.java |
packages
屬性的封裝類。<1>
處,若是已經存在該 BEAN
,則修改其包(package
)屬性。而合併 package
的邏輯,經過 #addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames)
方法,進行實現。代碼以下:
// AutoConfigurationPackages.java |
<2>
處,若是不存在該 BEAN
,則建立一個 Bean
,並進行註冊。#has(BeanFactory beanFactory)
方法,判斷是否存在該 BEAN
在傳入的容器中。代碼以下:
// AutoConfigurationPackages.java |
#get(BeanFactory beanFactory)
方法,得到 BEAN
。代碼以下:
// AutoConfigurationPackages.java |