前言
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,使用@ConditionalOnClass判斷;
- 該配置類依賴的一些配置Bean,使用@Import導入。
- 可能還有加載順序的控制,如@AutoConfigureAfter,@AutoConfigureOrder等
- 一些Bean的加載,往往通過方法返回Object,加@Bean以及一些條件注解來實現
========================================未完待續(下一篇補充自定義starter會涉及自動配置組成部分)==========================================