Spring Boot 自動配置原理

ThinkerJ發表於2018-09-16

Spring Boot 自動配置原理

  官方說明:spring.io/projects/sp…

  Spring Boot是由Pivotal團隊提供的全新框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置(applicationContext.xml)。其中自動配置可以說是Spring Boot中最令人愉悅的功能之一,它使得開發人員遠離了繁瑣配置(任何東西多了都會變得繁瑣)。   

1、實現思路

  Spring Boot提供了自動配置機制,它是通過 類路徑系統環境外部配置引數邏輯表示式spring上下文 等角度去判斷當前系統是否需要啟用某個技術框架,如果需要則進行自動配置。也就是說還是得配置,只不過Spring Boot把這個配置過程變得智慧了一點。這裡需要說明的是Spring Boot只是提供了自動配置機制,而具體要什麼類需要加入到Spring容器是各個框架需要自己實現的(自定義starter),當然Spring Boot內部有一部分實現。

Spring Boot 自動配置原理

  我們先看一個非Spring Boot專案環境中要使用Mybatis作為專案裡的orm框架,我們需要在applicationContext.xml裡配置Mybatis的相關的類(SqlSessionFactoryBean),這一過程是我們主動告知Spring我需要配置什麼類(Spring Boot也是主動告知,只不過告知這個動作是程式碼幫我們完成的)。

Spring Boot 自動配置原理

  如果使用Spring Boot的話,主動告知這一過程將是Spring Boot自動配置機制幫我們完成,這裡實際上是mybatis-spring-boot-starter實現的

Spring Boot 自動配置原理

2、基於什麼實現

(一)Java配置

  Spring Boot是基於Spring3.0以上版本中的Java配置特性來實現的,具體使用規則這裡不作說明,如不清楚可以先了解相關知識。Java配置使我們可以用Java類來代替Xml配置檔案,於是我們可以想假如把applicationContext.xml裡的所有配置都通過Java配置來實現的話,那就實現了我們消滅配置的第一步,如下圖:

Xml配置 Java配置
Spring Boot 自動配置原理
Spring Boot 自動配置原理

(二)關鍵的類和介面

  Spring3.x中提供的一系列類、介面、註解構成了自動配置的基石,我們甚至可以利用它們自己搞一個Spring Boot出來,先來看下主要的類:

名稱 說明
ImoprtSelector 是一個介面,提供了一個方法用於收集需要匯入的配置類
@Conditional 可以根據條件Condition 裝載不同的Bean
Condition 是一個介面,提供一個方法用於返回是否匹配,如果匹配則配置,反之
@Import 匯入配置類、普通Java類、實現ImoprtSelector的類
@SpringFactoriesloader 用於載入spring.factories中配置的內容

3、原始碼分析

(一)@SpringBootApplication註解

  先從Spring Boot應用的入口開始,即@SpringBootApplication註解,如圖

Spring Boot 自動配置原理

  可以看到@SpringBootApplication上新增了@EnableAutoConfiguration,這個註解從名稱上看就知其意 (啟用自動配置),點進@EnableAutoConfiguration,如圖

Spring Boot 自動配置原理

  在@EnableAutoConfiguration上新增了@Import,前文中提過@Import的功能是匯入配置類、普通Java類、實現ImoprtSelector的類,這裡匯入的是EnableAutoConfigurationImportSelector類,它繼承AutoConfigurationImportSelector類,而AutoConfigurationImportSelector實現了ImoprtSelector介面,但EnableAutoConfigurationImportSelector已經新增了@Deprecated說明不推薦使用了。如圖

Spring Boot 自動配置原理


  • ImoprtSelector

  這裡簡單說明下實現ImoprtSelector介面的作用,ImoprtSelector有個selectImports方法(這兩個名稱簡直讓人頭暈)如圖

Spring Boot 自動配置原理

  可以看到selectImports()返回值為應該匯入的配置類類名集合,到底應該匯入哪些配置類?你自己去決定和實現啦。方法的引數是AnnotationMetadata,它是訪問註解後設資料的介面,意思是什麼類上新增了@Import(ImoprtSelectorImpl.class)註解,那AnnotationMetadata裡封裝的就是那個類的所有註解資訊。


(二)AutoConfigurationImportSelector

Spring Boot 自動配置原理

  AutoConfigurationImportSelector中方法selectImports()的原始碼如下:

Spring Boot 自動配置原理

  首先呼叫AutoConfigurationMetadataLoader.loadMetadata()載入自動配置後設資料,它載入了 spring-boot-autoconfigure-1.5.7.RELEASE.jar 下的META-INF/spring-autoconfigure-metadata.properties檔案,最後返回了AutoConfigurationMetadata(把它當作一個properties),檔案的內容如下:

Spring Boot 自動配置原理

  我們發現這個properties檔案的key比較複雜,它格式是一個完整類名(F)+另一個簡單類名(S),這樣做的目的從原始碼可以推斷應該是想達到名稱空間的效果,其實就是為了方便取值。等號右邊配置的東西具體用於什麼地方需要根據(S)來確定,就上圖中ConditionOnClass等號右邊值配的是類名,以,號隔開,用於判斷 是否存在於類路徑

  接著呼叫了getCandidateConfigurations()方法

Spring Boot 自動配置原理

  getCandidateConfigurations()方法會去獲取所有自動配置類的名稱,原始碼如下:

Spring Boot 自動配置原理

  利用SpringFactoriesLoader載入 spring-boot-autoconfigure-1.5.7.RELEASE.jar  下的META-INF/spring.factories檔案,檔案是properties格式如圖:

Spring Boot 自動配置原理

  可以看到配在org.springframework.boot.autoconfigure.EnableAutoConfiguration下的全是自動配置類,這些類以字串陣列的形式返回,也就是getCandidateConfigurations()的方法返回值。

  繼續看方法selectImports()中的程式碼

Spring Boot 自動配置原理

  得到自動配置類名稱集合後呼叫了removeDuplicates()(刪除重複的配置類),接著sort()(排序),接著去掉使用exclude或者excludeName所排除的類,然後呼叫filter(),最後觸發所有註冊的回撥。其中filter()方法是自動配置的關鍵,原始碼如下:

Spring Boot 自動配置原理

  上面程式碼中呼叫getAutoConfigurationImportFilters()方法返回了一個AutoConfigurationImportFilter介面,真實執行時它是實現類OnClassCondition,然後呼叫match()方法,該方法的返回值是boolean[],如果是flase說明不匹配則跳過自動配置。細節將在下一節說明 。

(三)OnClassCondition

Spring Boot 自動配置原理
  上節中提到是根據match()方法返回的boolean[]來判斷到底需不需要匯入自動配置(注意boolean[]值的意義是是否需要匯入自動配置,而不是開始配置),那返回boolean[]到底有何意義?我們看方法match()的原始碼:

Spring Boot 自動配置原理

  方法getOutcomes()返回的是是否匹配的結果,這個是否匹配需要結合META-INF/spring.factoriesMETA-INF/spring-autoconfigure-metadata.properties來看,前者指定了需要自動配置的類,後者指定了以自動配置類為key多個類名為值,通過這些資訊就可以判斷是否匹配了。ConditionOutcome類則匹配結果的封裝,它包含了是否匹配、條件訊息等資訊,可以把它當作領域物件。方法getOutcomes()的原始碼如下:

多個類名:這裡的類是用於判斷  是否存在於類路徑

Spring Boot 自動配置原理

  將需要自動配置的類二分處理,一半開啟執行緒進行處理,另一半標準處理,最後合併結果。有意思的地方是原始碼作者在註釋中寫到 //More threads make things worse 更多的執行緒會變得更糟(為什麼不是分3次或者4次呢?疑問臉)。

  最後我們忽略中間的層層封裝直達底層,究竟OnClassCondition是怎麼判斷需不需要匯入自動配置類,看如下原始碼:

Spring Boot 自動配置原理

  通過上面程式碼可以知道它是通過當前系統類路徑下是否存在className來判斷的,如果存在就匯入這個自動配置類,反之忽略。到此Spring Boot幫我們從類路徑的角度去判斷了需不需要啟用自動配置。

className:META-INF/spring-autoconfigure-metadata.properties中等號右邊的值

(四)其他角度

  Spring Boot提供了自動配置機制,它是通過 類路徑系統環境外部配置引數邏輯表示式spring上下文 等角度去判斷當前系統是否需要啟用某個技術框架,目前我們分析了從 類路徑 的角度去判斷的原始碼,其他的角度思路一致,只是具體實現不一樣,它們都繼承自SpringBootCondition

Spring Boot 自動配置原理

  SpringBootCondition中的抽象方法getMatchOutcome()便是子類去實現是否匹配的,那OnClassCondition 中為什麼使用的match()方法去實現的?實際上OnClassCondition 有兩套實現,一個是match(),另一個就是getMatchOutcome(),前者是Spring Boot強制的通過類路徑去推斷了一次,後者是使用@ConditionalOnClass註解的時候呼叫

Spring Boot 自動配置原理

  以此類推其他的@ConditionalOn*都是通過@Conditional註解來實現的,下面列舉了常用的條件判斷註解

@Conditional擴充套件 作用
@ConditionalOnJava 系統中的jdk版本是否符合要求
@ConditionalOnExpression 滿足SpEL表示式
@ConditionalOnBean Spring容器中存在指定的Bean
@ConditionalOnMissingBean Spring容器中不存在指定的Bean
@ConditionalOnClass 某個類在類路徑中
@ConditionalOnMissingClass 某個類不在類路徑中
@ConditionalOnJndi 存在JNDI路徑
@ConditionalOnNotWebApplication 當前系統不是Web環境
@ConditionalOnWebApplication 當前系統是Web環境
@ConditionalOnProperty 指定的屬性是否有指定的值
@ConditionalOnResource 類路徑下是否存在指定的資原始檔

(五)一個例子

  我們以MybatisAutoConfiguration自動配置類為例,原始碼如下:

Spring Boot 自動配置原理

  可以看到類上新增了@ConditionalOnClass({SqlSessionFactory.class,SqlSessionFactoryBean.class})註解,說明當存在SqlSessionFactorySqlSessionFactoryBean類的時候啟動自動配置,接著新增了@ConditionalOnBean(DataSource.class),說明當存在DataSource例項時會啟動自動配置。

  sqlSessionFactory()方法上新增了@ConditionalOnMissingBean註解,說明當不存在SqlSessionFactory例項的時候會建立一個SqlSessionFactory例項。

  @EnableConfigurationProperties(MybatisProperties.class)作用是指定一個類用來接收外部配置檔案引數.

3、總結

  Spring Boot自動配置原理總體還是基於Spring提供的特性,牢固掌握Spring核心的重要性不言而喻。

相關文章