Spring Boot 自動配置原理
Spring Boot是由Pivotal團隊提供的全新框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置(applicationContext.xml
)。其中自動配置可以說是Spring Boot中最令人愉悅的功能之一,它使得開發人員遠離了繁瑣配置(任何東西多了都會變得繁瑣)。
1、實現思路
Spring Boot提供了自動配置機制,它是通過 類路徑、系統環境、外部配置引數、邏輯表示式、spring上下文 等角度去判斷當前系統是否需要啟用某個技術框架,如果需要則進行自動配置。也就是說還是得配置,只不過Spring Boot把這個配置過程變得智慧了一點。這裡需要說明的是Spring Boot只是提供了自動配置機制,而具體要什麼類需要加入到Spring容器是各個框架需要自己實現的(自定義starter),當然Spring Boot內部有一部分實現。
我們先看一個非Spring Boot專案環境中要使用Mybatis作為專案裡的orm框架,我們需要在applicationContext.xml
裡配置Mybatis的相關的類(SqlSessionFactoryBean
),這一過程是我們主動告知Spring我需要配置什麼類(Spring Boot也是主動告知,只不過告知這個動作是程式碼幫我們完成的)。
如果使用Spring Boot的話,主動告知這一過程將是Spring Boot自動配置機制幫我們完成,這裡實際上是mybatis-spring-boot-starter實現的
2、基於什麼實現
(一)Java配置
Spring Boot是基於Spring3.0以上版本中的Java配置特性來實現的,具體使用規則這裡不作說明,如不清楚可以先了解相關知識。Java配置使我們可以用Java類來代替Xml配置檔案,於是我們可以想假如把applicationContext.xml
裡的所有配置都通過Java配置來實現的話,那就實現了我們消滅配置的第一步,如下圖:
Xml配置 | Java配置 |
---|---|
(二)關鍵的類和介面
Spring3.x中提供的一系列類、介面、註解構成了自動配置的基石,我們甚至可以利用它們自己搞一個Spring Boot出來,先來看下主要的類:
名稱 | 說明 |
---|---|
ImoprtSelector |
是一個介面,提供了一個方法用於收集需要匯入的配置類 |
@Conditional |
可以根據條件Condition 裝載不同的Bean |
Condition |
是一個介面,提供一個方法用於返回是否匹配,如果匹配則配置,反之 |
@Import |
匯入配置類、普通Java類、實現ImoprtSelector 的類 |
@SpringFactoriesloader |
用於載入spring.factories 中配置的內容 |
3、原始碼分析
(一)@SpringBootApplication
註解
先從Spring Boot應用的入口開始,即@SpringBootApplication
註解,如圖
可以看到@SpringBootApplication
上新增了@EnableAutoConfiguration
,這個註解從名稱上看就知其意 (啟用自動配置),點進@EnableAutoConfiguration
,如圖
在@EnableAutoConfiguration
上新增了@Import
,前文中提過@Import
的功能是匯入配置類、普通Java類、實現ImoprtSelector
的類,這裡匯入的是EnableAutoConfigurationImportSelector
類,它繼承AutoConfigurationImportSelector
類,而AutoConfigurationImportSelector
實現了ImoprtSelector
介面,但EnableAutoConfigurationImportSelector
已經新增了@Deprecated
說明不推薦使用了。如圖
ImoprtSelector
這裡簡單說明下實現ImoprtSelector
介面的作用,ImoprtSelector
有個selectImports
方法(這兩個名稱簡直讓人頭暈)如圖
可以看到selectImports()
返回值為應該匯入的配置類類名集合,到底應該匯入哪些配置類?你自己去決定和實現啦。方法的引數是AnnotationMetadata
,它是訪問註解後設資料的介面,意思是什麼類上新增了@Import(ImoprtSelectorImpl.class)
註解,那AnnotationMetadata
裡封裝的就是那個類的所有註解資訊。
(二)AutoConfigurationImportSelector
類
AutoConfigurationImportSelector
中方法selectImports()
的原始碼如下:
首先呼叫AutoConfigurationMetadataLoader.loadMetadata()
載入自動配置後設資料,它載入了 spring-boot-autoconfigure-1.5.7.RELEASE.jar 下的META-INF/spring-autoconfigure-metadata.properties
檔案,最後返回了AutoConfigurationMetadata
(把它當作一個properties
),檔案的內容如下:
我們發現這個properties
檔案的key比較複雜,它格式是一個完整類名(F)+另一個簡單類名(S),這樣做的目的從原始碼可以推斷應該是想達到名稱空間的效果,其實就是為了方便取值。等號右邊配置的東西具體用於什麼地方需要根據(S)來確定,就上圖中ConditionOnClass
等號右邊值配的是類名,以,
號隔開,用於判斷 是否存在於類路徑。
接著呼叫了getCandidateConfigurations()
方法
getCandidateConfigurations()
方法會去獲取所有自動配置類的名稱,原始碼如下:
利用SpringFactoriesLoader
載入 spring-boot-autoconfigure-1.5.7.RELEASE.jar 下的META-INF/spring.factories
檔案,檔案是properties
格式如圖:
可以看到配在org.springframework.boot.autoconfigure.EnableAutoConfiguration
下的全是自動配置類,這些類以字串陣列的形式返回,也就是getCandidateConfigurations()
的方法返回值。
繼續看方法selectImports()
中的程式碼
得到自動配置類名稱集合後呼叫了removeDuplicates()
(刪除重複的配置類),接著sort()
(排序),接著去掉使用exclude
或者excludeName
所排除的類,然後呼叫filter()
,最後觸發所有註冊的回撥。其中filter()
方法是自動配置的關鍵,原始碼如下:
上面程式碼中呼叫getAutoConfigurationImportFilters()
方法返回了一個AutoConfigurationImportFilter
介面,真實執行時它是實現類OnClassCondition
,然後呼叫match()
方法,該方法的返回值是boolean[]
,如果是flase
說明不匹配則跳過自動配置。細節將在下一節說明
。
(三)OnClassCondition
類
上節中提到是根據match()
方法返回的boolean[]
來判斷到底需不需要匯入自動配置(注意boolean[]
值的意義是是否需要匯入自動配置,而不是開始配置),那返回boolean[]
到底有何意義?我們看方法match()
的原始碼:
方法getOutcomes()
返回的是是否匹配的結果,這個是否匹配需要結合META-INF/spring.factories
和META-INF/spring-autoconfigure-metadata.properties
來看,前者指定了需要自動配置的類,後者指定了以自動配置類為key,多個類名為值,通過這些資訊就可以判斷是否匹配了。ConditionOutcome
類則匹配結果的封裝,它包含了是否匹配、條件訊息等資訊,可以把它當作領域物件。方法getOutcomes()
的原始碼如下:
多個類名:這裡的類是用於判斷 是否存在於類路徑
將需要自動配置的類二分處理,一半開啟執行緒進行處理,另一半標準處理,最後合併結果。有意思的地方是原始碼作者在註釋中寫到 //More threads make things worse
更多的執行緒會變得更糟(為什麼不是分3次或者4次呢?疑問臉)。
最後我們忽略中間的層層封裝直達底層,究竟OnClassCondition
是怎麼判斷需不需要匯入自動配置類,看如下原始碼:
通過上面程式碼可以知道它是通過當前系統類路徑下是否存在className來判斷的,如果存在就匯入這個自動配置類,反之忽略。到此Spring Boot幫我們從類路徑的角度去判斷了需不需要啟用自動配置。
className:
META-INF/spring-autoconfigure-metadata.properties
中等號右邊的值
(四)其他角度
Spring Boot提供了自動配置機制,它是通過 類路徑、系統環境、外部配置引數、邏輯表示式、spring上下文 等角度去判斷當前系統是否需要啟用某個技術框架,目前我們分析了從 類路徑 的角度去判斷的原始碼,其他的角度思路一致,只是具體實現不一樣,它們都繼承自SpringBootCondition
。
SpringBootCondition
中的抽象方法getMatchOutcome()
便是子類去實現是否匹配的,那OnClassCondition
中為什麼使用的match()
方法去實現的?實際上OnClassCondition
有兩套實現,一個是match()
,另一個就是getMatchOutcome()
,前者是Spring Boot強制的通過類路徑去推斷了一次,後者是使用@ConditionalOnClass
註解的時候呼叫
以此類推其他的@ConditionalOn*
都是通過@Conditional
註解來實現的,下面列舉了常用的條件判斷註解
@Conditional 擴充套件 |
作用 |
---|---|
@ConditionalOnJava |
系統中的jdk版本是否符合要求 |
@ConditionalOnExpression |
滿足SpEL表示式 |
@ConditionalOnBean |
Spring容器中存在指定的Bean |
@ConditionalOnMissingBean |
Spring容器中不存在指定的Bean |
@ConditionalOnClass |
某個類在類路徑中 |
@ConditionalOnMissingClass |
某個類不在類路徑中 |
@ConditionalOnJndi |
存在JNDI路徑 |
@ConditionalOnNotWebApplication |
當前系統不是Web環境 |
@ConditionalOnWebApplication |
當前系統是Web環境 |
@ConditionalOnProperty |
指定的屬性是否有指定的值 |
@ConditionalOnResource |
類路徑下是否存在指定的資原始檔 |
(五)一個例子
我們以MybatisAutoConfiguration
自動配置類為例,原始碼如下:
可以看到類上新增了@ConditionalOnClass({SqlSessionFactory.class,SqlSessionFactoryBean.class})
註解,說明當存在SqlSessionFactory
、SqlSessionFactoryBean
類的時候啟動自動配置,接著新增了@ConditionalOnBean(DataSource.class)
,說明當存在DataSource
例項時會啟動自動配置。
sqlSessionFactory()
方法上新增了@ConditionalOnMissingBean
註解,說明當不存在SqlSessionFactory
例項的時候會建立一個SqlSessionFactory
例項。
@EnableConfigurationProperties(MybatisProperties.class)
作用是指定一個類用來接收外部配置檔案引數.
3、總結
Spring Boot自動配置原理總體還是基於Spring提供的特性,牢固掌握Spring核心的重要性不言而喻。