SpringBoot中@SpringBootApplication註解的三體結構解析

精博科技 發佈 2020-03-19T18:14:59+00:00

}對於@EnableAutoConfiguration來說,SpringFactoriesLoader的用途稍微不同一些,其本意是為了提供SPI擴展的場景,而在@EnableAutoConfiguration的場景中,它更多是提供了一種配置查找的功能支持,即根據@EnableAu

@SpringBootApplication 是一個「三體」結構,實際上它是一個複合 Annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScanpublic
@interface
SpringBootApplication{...}

雖然它的定義使用了多個 Annotation 進行元信息標註,但實際上對於 SpringBoot 應用來說,重要的只有三個 Annotation,而「三體」結構實際上指的就是這三個 Annotation:

  • @Configuration
  • @EnableAutoConfiguration
  • @ComponentScan

所以,如果我們使用如下的 SpringBoot 啟動類,整個 SpringBoot 應用依然可以與之前的啟動類功能對等:

@Configuration
@EnableAutoConfiguration
@ComponentScanpublic
class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

但每次都寫三個 Annotation 顯然過於繁瑣,所以寫一個 @SpringBootApplication 這樣的一站式複合 Annotation 顯然更方便些。

@Configuration 創世紀

這裡的 @Configuration 對我們來說並不陌生,它就是 JavaConfig 形式的 Spring IoC 容器的配置類使用的那個 @Configuration,既然 SpringBoot 應用骨子裡就是一個 Spring 應用,那麼,自然也需要加載某個 IoC 容器的配置,而 SpringBoot 社區推薦使用基於 JavaConfig 的配置形式,所以,很明顯,這裡的啟動類標註了 @Configuration 之後,本身其實也是一個 IoC 容器的配置類!很多 SpringBoot 的代碼示例都喜歡在啟動類上直接標註 @Configuration 或者 @SpringBootApplication,對於初接觸 SpringBoot 的開發者來說,其實這種做法不便於理解,如果我們將上面的 SpringBoot 啟動類拆分為兩個獨立的 Java 類,整個形勢就明朗了:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class DemoConfiguration {
    @Bean
    public Controller controller() {
        return new Controller();
    }
}

public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoConfiguration.class, args);
    }
}

所以,啟動類 DemoApplication 其實就是一個標準的 Standalone 類型 Java 程序的 main 函數啟動類,沒有什麼特殊的。而 @Configuration 標註的 DemoConfiguration 定義其實也是一個普通的 JavaConfig 形式的 IoC 容器配置類。

@EnableAutoConfiguration 的功效

@EnableAutoConfiguration 其實也沒啥「創意」,各位是否還記得 Spring 框架提供的各種名字為 @Enable 開頭的 Annotation 定義?比如 @EnableScheduling、@EnableCaching、@EnableMBeanExport 等,@EnableAutoConfiguration 的理念和「做事方式」其實一脈相承,簡單概括一下就是,藉助 @Import 的支持,收集和註冊特定場景相關的 bean 定義:

  • @EnableScheduling 是通過 @Import 將 Spring 調度框架相關的 bean 定義都加載到 IoC 容器。
  • @EnableMBeanExport 是通過 @Import 將 JMX 相關的 bean 定義加載到 IoC 容器。

而 @EnableAutoConfiguration 也是藉助 @Import 的幫助,將所有符合自動配置條件的 bean 定義加載到 IoC 容器,僅此而已!@EnableAutoConfiguration 作為一個複合 Annotation,其自身定義關鍵信息如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}

其中,最關鍵的要屬 @Import(EnableAutoConfigurationImportSelector.class),藉助 EnableAutoConfigurationImportSelector,@EnableAutoConfiguration 可以幫助 SpringBoot 應用將所有符合條件的 @Configuration 配置都加載到當前 SpringBoot 創建並使用的 IoC 容器,就跟一隻「八爪魚」一樣(如圖 1 所示)。

​圖 1 EnableAutoConfiguration得以生效的關鍵組件關係圖

藉助於 Spring 框架原有的一個工具類:SpringFactoriesLoader 的支持,@EnableAutoConfiguration 可以「智能」地自動配置功效才得以大功告成!

SpringFactoriesLoader詳解

SpringFactoriesLoader 屬於 Spring 框架私有的一種擴展方案(類似於 Java 的 SPI 方案 java.util.ServiceLoader),其主要功能就是從指定的配置文件 META-INF/spring.factories 加載配置,spring.factories 是一個典型的 java properties 文件,配置的格式為 Key=Value 形式,只不過 Key 和 Value 都是 Java 類型的完整類名(Fully qualified name),比如:example.MyService=example.MyServiceImpl1,example.MyServiceImpl2 然後框架就可以根據某個類型作為 Key 來查找對應的類型名稱列表了:

public abstract class SpringFactoriesLoader {
    // ...
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        ...
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ...
    }
    // ...
}

對於 @EnableAutoConfiguration 來說,SpringFactoriesLoader 的用途稍微不同一些,其本意是為了提供 SPI 擴展的場景,而在 @EnableAutoConfiguration 的場景中,它更多是提供了一種配置查找的功能支持,即根據 @EnableAutoConfiguration 的完整類名 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作為查找的 Key,獲取對應的一組 @Configuration 類:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
\org.springframework.boot.autoconfigure.admin.SpringApplicationAdmin- JmxAutoConfiguration,
\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,
\org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,
\org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,
\org.springframework.boot.autoconfigure.PropertyPlaceholderAuto- Configuration,
\org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,
\org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,
\org.springframework.boot.autoconfigure.cassandra.CassandraAuto-Configuration,
\org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,
\org.springframework.boot.autoconfigure.context.ConfigurationProperties-AutoConfiguration,
\org.springframework.boot.autoconfigure.dao.PersistenceException-TranslationAutoConfiguration,
\org.springframework.boot.autoconfigure.data.cassandra.Cassandra-DataAutoConfiguration,
\org.springframework.boot.autoconfigure.data.cassandra.Cassandra-RepositoriesAutoConfiguration,
\...

以上是從 SpringBoot 的 autoconfigure 依賴包中的 META-INF/spring.factories 配置文件中摘錄的一段內容,可以很好地說明問題。所以,@EnableAutoConfiguration 自動配置的魔法其實就變成了:從 classpath 中搜尋所有 META-INF/spring.factories 配置文件,並將其中 org.spring-framework.boot.autoconfigure.EnableAutoConfiguration 對應的配置項通過反射(Java Reflection)實例化為對應的標註了 @Configuration 的 JavaConfig 形式的 IoC 容器配置類,然後匯總為一個並加載到 IoC 容器。

可有可無的@ComponentScan

為啥說 @ComponentScan 是可有可無的?因為原則上來說,作為 Spring 框架里的「老一輩革命家」,@ComponentScan 的功能其實就是自動掃描並加載符合條件的組件或 bean 定義,最終將這些 bean 定義加載到容器中。加載 bean 定義到 Spring 的 IoC 容器,我們可以手工單個註冊,不一定非要通過批量的自動掃描完成,所以說 @ComponentScan 是可有可無的。對於 SpringBoot 應用來說,同樣如此,比如我們本章的啟動類:

@Configuration
@EnableAutoConfiguration
@ComponentScanpublic
class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

如果我們當前應用沒有任何 bean 定義需要通過 @ComponentScan 加載到當前 SpringBoot 應用對應使用的 IoC 容器,那麼,除去 @ComponentScan 的聲明,當前 SpringBoot 應用依然可以照常運行,功能對等。

關鍵字: