Spring Boot自動配置原理與實踐(一)

JJian發表於2019-05-13

前言

  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會涉及自動配置組成部分)==========================================

 

相關文章