前言
Spring Boot眾所周知是為了簡化Spring的配置,省去XML的複雜化配置(雖然Spring官方推薦也使用Java配置)採用Java+Annotation方式配置。如下幾個問題是我剛開始接觸Spring Boot的時候經常遇到的一些疑問,現在總結出來希望能幫助到更多的人理解Spring Boot,當然這只是個人的理解,稍微顯得膚淺但易懂!當時我明白了以下幾個問題後,覺得Spring Boot也不過如此,沒有啥花裡胡哨的,希望能幫到大家!
本博文主要由兩個部分組成:第一篇通過原始碼等形式介紹自動配置的原理與組成部分,第二篇通過實現自定義的starter實現自動配置加深對自動配置的理解。
注:創作不易啊,雖然寫只寫了5個小時左右,但是整個準備過程很早就開始了,當然這樣的記錄方式主要是督促自己對Spring Boot要深入理解,順帶希望能幫到更多人
問題一:那麼Spring Boot是靠什麼樣的手段簡化配置呢?
從廣義上講,從軟體設計正規化來說。Spring Boot提供一種“約定優於配置”來簡化配置;減少開發人員去開發沒必要的配置模組時間,採用約定,並且對不符合約定的部分採用靈活配置滿足即可!
問題二:那麼什麼叫約定優於配置呢?
比如:我們知道Spring Boot都是內嵌Tomcat、Jetty等伺服器的,其中預設的Tomcat伺服器,相應的監聽埠也預設繫結,這些預設即為約定。大部分情況下我們都會使用預設預設的Spring Boot自帶的內嵌的預設自動配置約定,不會大量去自定義配置,即這就是我理解的約定優於配置。那麼問題三來了。當我們覺得不符合我們實際生產環境的時候才會去改變預設配置,那麼需要很麻煩的手段嗎?
問題三:更改預設的埠或者伺服器(即上述提到的約定)需要很麻煩的程式碼編寫嗎?甚至需要更改原始碼嗎?
答案肯定是否定的,只需要更改application.yml/properties(application-*.yml/properties)配置檔案即可。當然也可以從源頭上開始重新建立自己需要的模組(準確來說可以是starter),需要的註冊的配置類Bean。從而達到靈活的自動配置目的,這就是本篇要介紹的Spring Boot的自動配置。
明白以上三個問題後,其實就能理解為什麼Spring Boot相對於Spring更加靈活,方便,簡單的原因了========>無非就是大量使用預設自動配置+靈活的自動配置
一、Spring Boot自動配置的原理介紹
主要從spring boot是如何啟動後自動檢測自動配置與spring boot如何靈活處理自動配置兩個方面講解,前者主要分析原始碼,從簡單的註解入手到最後的讀取spring.facoties檔案。後者主要舉例RedisAutoConfiguration自動配置來說明spring boot對於相關的配置類bean的通過註解的靈活處理。
1、Spring Boot是如何發現自動配置的?
都知道Spring Boot程式入口處(xxxApplication.java)都會有 @SpringBootApplication 的註解。這個註解表明該程式是主程式入口,也是SpringBoot應用
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
檢視 @SpringBootApplication 註解,不難發現是一個組合註解,包括了 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 。換句話說這三個註解可以替代 @SpringBootApplication
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} )
其中註解 @EnableAutoConfiguration 就是實現SpringBoot自動配置的關鍵所在,值得注意的是@EnableXXXX註解並不是SpringBoot特有,在Spring 3.x中就已經引出,比如:@EnableWebMvc用來啟用Spring MVC而不需要XML配置,@EnableTransactionManagement註釋用來宣告事務管理而不需要XML配置。如此可見,@EnableXXXX是用來開啟某一個特定的功能,從而使用Java來配置,省去XML配置。在SpringBoot中自動配置的開啟就是依靠註解 @EnableAutoConfiguration ,繼續檢視註解介面 @EnableAutoConfiguration
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
其中關鍵是 @Import({AutoConfigurationImportSelector.class}) ,表示選擇所有SpringBoot的自動配置。廢話不多說,還得繼續看該選擇器原始碼,其中最主要的方法是 getAutoConfiguationEntry 方法,該方法的主要功能是:
獲得自動配置---->刪除重複自動配置----->刪除排除掉的自動配置---->刪除過濾掉的自動配置------>最終的自動配置
這裡我們主要關注如何獲取自動配置,即圖中紅圈部分,即 getCandidateConfigurations 方法,該方法通過SpringFactoresLoader.loadFactoryNames方法讀取某個classpath下的檔案獲取所有的configurations
注意到: No auto configuration classes found in META-INF/spring.factories..... ,它應該是從META-INF/spring.factories檔案中下讀取configurations,我們再往下檢視方法 SpringFactoresLoader.loadFactoryNames ,該方法果然是讀取META-INF/spring.factories檔案的,從中獲取自動配置鍵值對。
最後,我們找到META-INF/spring.factories檔案(位於spring-boot-autoconfigure下)
檢視其內容:
... # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ ...
這時候才恍然大悟,原來如此。簡單總結起來就是,當SpringBoot應用啟動讀取自動配置之後,從上到下的相關呼叫棧(文字簡單描述)
- @EnableAutoConfiguration開啟啟動配置功能
- 通過@Import匯入AutoConfigurationImportSelector選擇器發現(註冊)自動配置
- getAutoConfigurationEntry方法獲取並處理所有的自動配置
- SpringFactoriesLoader.loadFactoryNames讀取spring.factories檔案並載入自動配置鍵值對。
2、Spring Boot是如何靈活處理自動配置的?
從spring.factories檔案中我們選取RedisAutoConfiguration舉例說明,至於為什麼選這個呢?這是因為之前我有寫過關於Spring Boot整合Redis的相關博文。所以好入手!
首先,還是一樣進入RedisAutoConfiguration(Idea大法好,點選進入即可檢視程式碼)
原始碼如下,主要關注以下幾個註解:
1)@ConditionalOnClass:在當前classpath下是否存在指定類,若是則將當前的配置註冊到spring容器。可見其靈活性。相關的@ConditionalXXX註解是spring 4.x推出的靈活註冊bean的註解,稱之為“條件註解”。有如下一系列條件註解:
- @ConditionalOnMissingBean: 當spring容器中不存在指定的bean時,才會註冊該Bean。
- @ConditionalOnMissingClass:當不存在指定的類時,才會註冊該bean。
- @ConditionalOnProperty:根據配置檔案中某個屬性來決定是否註冊該Bean,相對於其它條件註解較為複雜。主要有以下兩種情況應用:
一種是@ConditionalOnProperty(prefix = “mysql”, name = “enable”, havingValue = “true”)表示以為mysql為字首的mysql.enable屬性如果為空或者不是true時不會註冊指定Bean,只有mysql.enable屬性為true時候才會註冊該指定Bean
一種是@ConditionalOnProperty(prefix = “mysql”, name = “enable”, havingValue = “true”, matchIfMissing = true)matchIfMissing表示如果沒有以mysql為字首的enable屬性,則為true表示符合條件,可以註冊該指定Bean,預設為false。
- @ConditionalOnJava:是否是Java應用
- @ConditionalOnWebApplication:是否是Web應用
- @ConditionalOnExpression:根據表示式判斷是否註冊Bean
...........剩下的還有很多,在autoconfigure.condition包下...........
2)@EnableConfigurationProperties:讓 @ConfigurationProperties 註解的properties類生效並使用。如讓RedisProperties生效並註冊到Spring容器中,並且使用!
3)@ConditionalOnMissingBean:當前Spring容器中沒有該bean(name或者class指定),則註冊該bean到spring容器。
@Configuration @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
由此可見Spring Boot對於自動配置的靈活性,可以傳遞給我們一種資訊,Spring Boot是如何靈活處理自動配置的?
往大了說,就是你可以選擇約定使用大量預設配置簡化開發過程,你也可以自定義開發過程中需要的自動配置。往細了說,就是通過各式各樣的條件註解註冊需要的Bean來實現靈活配置。
那麼實現一個自動配置有哪些重要的環節呢?
繼續往下看第二部分!
二、Spring Boot自動配置的重要組成部分
上面只是對自動配置中幾個重要的註解做了簡單介紹,但是並沒有介紹要開發一個簡單的自動配置需要哪些環節(部分),同樣地我們還是以RedisAutoConfiguration與RabbitAutoConfiguration為例,主要檢視註解頭:
RedisAutoConfiguration:
@Configuration @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
RabbitAutoConfiguration:
@Configuration @ConditionalOnClass({RabbitTemplate.class, Channel.class}) @EnableConfigurationProperties({RabbitProperties.class}) @Import({RabbitAnnotationDrivenConfiguration.class})
你會發現通常的一般的AutoConfiguration都有@Configuration,@ConditionalOnClass,@EnableConfigurationProperties,@Import 註解:
- @Configuration:自然不必多說,表示是配置類,是必須存在的!
- @ConditionalOnClass:表示該配置依賴於某些Class是否在ClassPath中,如果不存在那麼這個配置類是毫無意義的。也可以說是該class在配置類中充當重要的角色,是必須存在的!。在後面的自定義spring boot starter中我們可以簡單統稱為服務類
- @EnableConfigurationProperties:讀取配置檔案,需要讓相關的Properties生效,每個自動配置都離不開properties屬性,是必須存在的!
- @Import:匯入配置類中需要用到的bean,通常是一些配置類。表示此時的自動配置類需要基於一些配置類而實現自動配置功能。當然不是必須的,有些是沒有這個註解的,是不需要基於某些配置類的!
那麼,可以簡單總結得到一個自動配置類主要有以下幾部分組成(單純是根據個人理解總結出來的,沒有官方說明)
- properties bean配置屬性:用來讀取spring配置檔案中的屬性,@EnableConfigurationProperties與@ConfigurationProperties結合使用,具體請看下一篇實踐stater例子。
- 該配置類依賴的一些Class,使用@ConditionalClass判斷;
- 該配置類依賴的一些配置Bean,使用@Import匯入。
- 可能還有載入順序的控制,如@AutoConfigureAfter,@AutoConfigureOrder等
- 一些Bean的載入,往往通過方法返回Object,加@Bean以及一些條件註解來實現
========================================未完待續(下一篇補充自定義starter會涉及自動配置組成部分)==========================================