該系列文章是筆者在學習 Spring Boot 過程中總結下來的,裡面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼註釋 Spring Boot 原始碼分析 GitHub 地址 進行閱讀
Spring Boot 版本:2.2.x
最好對 Spring 原始碼有一定的瞭解,可以先檢視我的 《死磕 Spring 之 IoC 篇 - 文章導讀》 系列文章
如果該篇內容對您有幫助,麻煩點選一下“推薦”,也可以關注博主,感激不盡~
該系列其他文章請檢視:《精盡 Spring Boot 原始碼分析 - 文章導讀》
概述
現如今,Spring Boot
在許多中大型企業中被普及,想必大家對於 @SpringBootApplication
並不陌生,這個註解通常標註在我們應用的啟動類上面,標記是一個 Spring Boot 應用,同時開啟自動配置的功能,那麼你是否有深入瞭解過該註解呢?沒有的話,或許這篇文章可以讓你對它有一個新的認識。
提示:
@EnableAutoConfiguration
是開啟自動配置功能的模組驅動註解,是 Spring Boot 的核心註解整篇文章主要是對這個註解,也就是 Spring Boot 的自動配置功能進行展述
@SpringBootApplication
org.springframework.boot.autoconfigure.SpringBootApplication
註解在 Spring Boot 的 spring-boot-autoconfigre
子模組下,當我們引入 spring-boot-starter
模組後會自動引入該子模組
該註解是一個組合註解,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 表明該註解定義在某個類上時,其子類會繼承該註解
@SpringBootConfiguration // 繼承 `@Configuration` 註解
@EnableAutoConfiguration // 開啟自動配置功能
// 掃描指定路徑下的 Bean
@ComponentScan( excludeFilters = {
// 預設沒有 TypeExcludeFilter
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
// 排除掉自動配置類
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* 需要自動配置的 Class 類
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* 需要自動配置的類名稱
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* 需要掃描的路徑
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* 需要掃描的 Class 類
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
* 被標記的 Bean 是否進行 CGLIB 提升
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@SpringBootApplication
註解就是一個組合註解,裡面的每個配置都是元註解中對應的屬性,上面已做描述
該註解上面的 @Inherited
元註解是 Java 提供的,標註後表示當前註解定義在某個類上時,其子類會繼承該註解,我們一起來看看其他三個註解
@SpringBootConfiguration
org.springframework.boot.SpringBootConfiguration
註解,Spring Boot 自定義註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
/**
* 被標記的 Bean 是否進行 CGLIB 提升
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
該註解很簡單,上面標註了 @Configuration
元註解,所以作用相同,同樣是將一個類標註為配置類,能夠作為一個 Bean 被 Spring IoC 容器管理
至於為什麼不直接使用 @Configuration
註解呢,我想這應該是 領域驅動設計 中的一種思想,可以使得 Spring Boot 更加靈活,總有它的用武之地
領域驅動設計:Domain-Driven Design,簡稱 DDD。過去系統分析和系統設計都是分離的,這樣割裂的結果導致需求分析的結果無法直接進行設計程式設計,而能夠進行程式設計執行的程式碼卻扭曲需求,導致客戶執行軟體後才發現很多功能不是自己想要的,而且軟體不能快速跟隨需求變化。DDD 則打破了這種隔閡,提出了領域模型概念,統一了分析和設計程式設計,使得軟體能夠更靈活快速跟隨需求變化。
@ComponentScan
org.springframework.context.annotation.ComponentScan
註解,Spring 註解,掃描指定路徑下的標有 @Component
註解的類,解析成 Bean 被 Spring IoC 容器管理
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* 指定的掃描的路徑
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 指定的掃描的路徑
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 指定的掃描的 Class 物件
*/
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
boolean useDefaultFilters() default true;
/**
* 掃描時的包含過濾器
*/
Filter[] includeFilters() default {};
/**
* 掃描時的排除過濾器
*/
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
}
想深入瞭解該註解的小夥伴可以檢視我前面對 Spring IoC 進行原始碼分析的文章:
該註解通常需要和 @Configuration
註解一起使用,因為需要先被當做一個配置類,然後解析到上面有 @ComponentScan
註解後則處理該註解,通過 ClassPathBeanDefinitionScanner 掃描器去掃描指定路徑下標註了 @Component
註解的類,將他們解析成 BeanDefinition(Bean 的前身),後續則會生成對應的 Bean 被 Spring IoC 容器管理
當然,如果該註解沒有通過 basePackages
指定路徑,Spring 會選在以該註解標註的類所在的包作為基礎路徑,然後掃描包下面的這些類
@EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration
註解,Spring Boot 自定義註解,用於驅動 Spring Boot 自動配置模組
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 註冊一個 Bean 儲存當前註解標註的類所在包路徑
@Import(AutoConfigurationImportSelector.class) // Spring Boot 自動配置的實現
public @interface EnableAutoConfiguration {
/**
* 可通過這個配置關閉 Spring Boot 的自動配置功能
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 指定需要排除的自動配置類的 Class 物件
*/
Class<?>[] exclude() default {};
/**
* 指定需要排除的自動配置類的名稱
*/
String[] excludeName() default {};
}
對於 Spring 中的模組驅動註解的實現都是通過 @Import
註解來實現的
模組驅動註解通常需要結合 @Configuration
註解一起使用,因為需要先被當做一個配置類,然後解析到上面有 @Import
註解後則進行處理,對於 @Import
註解的值有三種情況:
-
該 Class 物件實現了
ImportSelector
介面,呼叫它的selectImports(..)
方法獲取需要被處理的 Class 物件的名稱,也就是可以將它們作為一個 Bean 被 Spring IoC 管理- 該 Class 物件實現了
DeferredImportSelector
介面,和上者的執行時機不同,在所有配置類處理完後再執行,且支援@Order
排序
- 該 Class 物件實現了
-
該 Class 物件實現了
ImportBeanDefinitionRegistrar
介面,會呼叫它的registerBeanDefinitions(..)
方法,自定義地往 BeanDefinitionRegistry 註冊中心註冊 BeanDefinition(Bean 的前身) -
該 Class 物件是一個
@Configuration
配置類,會將這個類作為一個 Bean 被 Spring IoC 管理
對於 @Import
註解不熟悉的小夥伴可檢視我前面的 《死磕Spring之IoC篇 - @Bean 等註解的實現原理》 這篇文章
這裡的 @EnableAutoConfiguration
自動配置模組驅動註解,通過 @Import
匯入 AutoConfigurationImportSelector 這個類(實現了 DeferredImportSelector
介面)來驅動 Spring Boot 的自動配置模組,下面會進行分析
@AutoConfigurationPackage
我們注意到 @EnableAutoConfiguration
註解上面還有一個 @AutoConfigurationPackage
元註解,它的作用就是註冊一個 Bean,儲存了當前註解標註的類所在包路徑
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**
* 將當前註解所標註的類所在包名封裝成一個 {@link org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages} 進行註冊
* 例如 JPA 模組的會使用到這個物件(JPA entity scanner)
*/
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage { }
同樣這裡使用了 @Import
註解來實現的,對應的是一個 AutoConfigurationPackages.Registrar
內部類,如下:
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 註冊一個 BasePackages 類的 BeanDefinition,角色為內部角色,名稱為 `org.springframework.boot.autoconfigure.AutoConfigurationPackages`
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
// 將註解元資訊封裝成 PackageImport 物件,對註解所在的包名進行封裝
return Collections.singleton(new PackageImport(metadata));
}
}
比較簡單,這裡直接跳過了
自動配置
在開始之前,我們先來了解一下 Spring Boot 的自動配置,就是通過引入某個功能的相關 jar
包依賴後,Spring Boot 能夠自動配置應用程式,讓我們很方便的使用該功能
-
例如當你引入
spring-boot-starter-aop
後,會自動引入 AOP 相關的jar
包依賴,那麼在spring-boot-autoconfigure
中有一個AopAutoConfiguration
自動配置類會自動驅動整個 AOP 模組 -
例如當你引入
spring-boot-starter-web
後,會自動引入 Spring MVC、Tomcat 相關的jar
包依賴,那麼在spring-boot-autoconfigure
中會有相應的自動配置類會自動配置 Spring MVC
當然,還有許多自動配置類,結合這 Spring Boot 的 Starter 模組,讓許多功能或者第三方 jar
包能夠很簡便的和 Spring Boot 整合在一起使用
現在很多開源框架都提供了對應的 Spring Boot Starter 模組,能夠更好的整合 Spring Boot,當你熟悉自動配置功能後,你也可以很輕鬆的寫一個 Starter 包供他人使用???
這裡先提前劇透一下,自動配置類為什麼在你引入相關
jar
包後會自動配置對應的模組呢?主要就是擴充了 Spring 的 Condition,例如
@ConditionalOnClass
註解,當存在指定的 Class 物件時才注入某個 Bean同時也可以再結合
@EnableXxx
模組註解,通過@Import
註解驅動某個模組具體細節,請繼續往下看
AutoConfigurationImportSelector
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
,實現了 DeferredImportSelector 介面,是 @EnableAutoConfiguration
註解驅動自動配置模組的核心類
直接看到實現的 ImportSelector 介面的方法
1. selectImports 方法
selectImports(AnnotationMetadata)
方法,返回需要注入的 Bean 的類名稱
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// <1> 如果通過 `spring.boot.enableautoconfiguration` 配置關閉了自動配置功能
if (!isEnabled(annotationMetadata)) {
// 返回一個空陣列
return NO_IMPORTS;
}
/**
* <2> 解析 `META-INF/spring-autoconfigure-metadata.properties` 檔案,生成一個 AutoConfigurationMetadata 自動配置類後設資料物件
*
* 說明:引入 `spring-boot-autoconfigure-processor` 工具模組依賴後,其中會通過 Java SPI 機制引入 {@link AutoConfigureAnnotationProcessor} 註解處理器在編譯階段進行相關處理
* 其中 `spring-boot-autoconfigure` 模組會引入該工具模組(不具有傳遞性),那麼 Spring Boot 在編譯 `spring-boot-autoconfigure` 這個 `jar` 包的時候,
* 在編譯階段會掃描到帶有 `@ConditionalOnClass` 等註解的 `.class` 檔案,也就是自動配置類,將自動配置類的資訊儲存至 `META-INF/spring-autoconfigure-metadata.properties` 檔案中
* 例如儲存類 `自動配置類類名.註解簡稱` => `註解中的值(逗號分隔)` 和 `自動配置類類名` => `空字串`
*
* 當然,你自己寫的 Spring Boot Starter 中的自動配置模組也可以引入這個 Spring Boot 提供的外掛
*/
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// <3> 從所有的 `META-INF/spring.factories` 檔案中找到 `@EnableAutoConfiguration` 註解對應的類(需要自動配置的類)
// 會進行過濾處理,然後封裝在一個物件中
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
// <4> 返回所有需要自動配置的類
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
過程如下:
- 如果通過
spring.boot.enableautoconfiguration
配置關閉了自動配置功能,那麼直接返回一個空陣列 - 解析
META-INF/spring-autoconfigure-metadata.properties
檔案,生成一個 AutoConfigurationMetadata 自動配置類後設資料物件 - 呼叫
getAutoConfigurationEntry(..)
方法, 從所有的META-INF/spring.factories
檔案中找到@EnableAutoConfiguration
註解對應的類(需要自動配置的類),會進行過濾處理,然後封裝在一個AutoConfigurationEntry
物件中 - 返回所有需要自動配置的類
上面第 2
步呼叫的方法:
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() {
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// <1> 獲取所有 `META-INF/spring-autoconfigure-metadata.properties` 檔案 URL
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
// <2> 載入這些檔案並將他們的屬性新增到 Properties 中
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// <3> 將這個 Properties 封裝到 PropertiesAutoConfigurationMetadata 物件中並返回
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
static AutoConfigurationMetadata loadMetadata(Properties properties) {
return new PropertiesAutoConfigurationMetadata(properties);
}
}
這一步困惑了我很久,因為在 Spring Boot 工程中根本找不到 META-INF/spring-autoconfigure-metadata.properties
檔案,而我們自己也沒有配置過,但是在我們自己的 Spring Boot 應用依賴的 spring-boot-autoconfigure.jar
包裡面又存在這個檔案,如下:
那麼這是為什麼呢?經過我長時間的 Google,找到了答案
-
在引入
spring-boot-autoconfigure-processor
工具模組依賴後,其中會通過 Java SPI 機制引入 AutoConfigureAnnotationProcessor 註解處理器在編譯階段進行相關處理 -
其中
spring-boot-autoconfigure
模組會引入該工具模組(不具有傳遞性),那麼 Spring Boot 在編譯spring-boot-autoconfigure
這個jar
包的時候,在編譯階段會掃描到帶有@ConditionalOnClass
等註解的.class
檔案,也就是自動配置類,然後將自動配置類的一些資訊儲存至META-INF/spring-autoconfigure-metadata.properties
檔案中 -
檔案中儲存了
自動配置類類名.註解簡稱
-->註解中的值(逗號分隔)
,自動配置類類名
-->空字串
-
當然,你自己寫的 Spring Boot Starter 中的自動配置模組也可以引入這個 Spring Boot 提供的外掛
得到的結論:
至於為什麼這麼做,是因為 Spring Boot 提供的自動配置類比較多,而我們不可能使用到很多自動配置功能,大部分都沒必要,如果每次你啟動應用的過程中,都需要一個一個去解析他們上面的 Conditional 註解,那麼肯定會有不少的效能損耗
這裡,Spring Boot 做了一個優化,通過自己提供的工具,在編譯階段將自動配置類的一些註解資訊儲存在一個 properties
檔案中,這樣一來,在你啟動應用的過程中,就可以直接讀取該檔案中的資訊,提前過濾掉一些自動配置類,相比於每次都去解析它們所有的註解,效能提升不少
2. getAutoConfigurationEntry 方法
getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata)
方法,返回符合條件的自動配置類,如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// <1> 如果通過 `spring.boot.enableautoconfiguration` 配置關閉了自動配置功能
if (!isEnabled(annotationMetadata)) {
// 則返回一個“空”的物件
return EMPTY_ENTRY;
}
// <2> 獲取 `@EnableAutoConfiguration` 註解的配置資訊
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// <3> 從所有的 `META-INF/spring.factories` 檔案中找到 `@EnableAutoConfiguration` 註解對應的類(需要自動配置的類)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// <4> 對所有的自動配置類進行去重
configurations = removeDuplicates(configurations);
// <5> 獲取需要排除的自動配置類
// 可通過 `@EnableAutoConfiguration` 註解的 `exclude` 和 `excludeName` 配置
// 也可以通過 `spring.autoconfigure.exclude` 配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// <6> 處理 `exclusions` 中特殊的類名稱,保證能夠排除它
checkExcludedClasses(configurations, exclusions);
// <7> 從 `configurations` 中將 `exclusions` 需要排除的自動配置類移除
configurations.removeAll(exclusions);
/**
* <8> 從 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportFilter} 對 `configurations` 進行過濾處理
* 例如 Spring Boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.OnClassCondition}
* 在這裡提前過濾掉一些不滿足條件的自動配置類,在 Spring 注入 Bean 的時候也會判斷哦~
*/
configurations = filter(configurations, autoConfigurationMetadata);
/**
* <9> 從 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportListener} 事件監聽器
* 觸發每個監聽器去處理 {@link AutoConfigurationImportEvent} 事件,該事件中包含了 `configurations` 和 `exclusions`
* Spring Boot 中配置了一個 {@link org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener}
* 目的就是將 `configurations` 和 `exclusions` 儲存至 {@link AutoConfigurationImportEvent} 物件中,並註冊到 IoC 容器中,名稱為 `autoConfigurationReport`
* 這樣一來我們可以注入這個 Bean 獲取到自動配置類資訊
*/
fireAutoConfigurationImportEvents(configurations, exclusions);
// <10> 將所有的自動配置類封裝成一個 AutoConfigurationEntry 物件,並返回
return new AutoConfigurationEntry(configurations, exclusions);
}
過程如下:
-
如果通過
spring.boot.enableautoconfiguration
配置關閉了自動配置功能,則返回一個“空”的物件 -
獲取
@EnableAutoConfiguration
註解的配置資訊 -
從所有的
META-INF/spring.factories
檔案中找到@EnableAutoConfiguration
註解對應的類(需要自動配置的類),儲存在configurations
集合中protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 從所有的 `META-INF/spring.factories` 檔案中找到 `@EnableAutoConfiguration` 註解對應的類(需要自動配置的類) List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); // 如果為空則丟擲異常 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
這個 SpringFactoriesLoader 是由 Spring 提供的一個類
-
對所有的自動配置類進行去重
protected final <T> List<T> removeDuplicates(List<T> list) { return new ArrayList<>(new LinkedHashSet<>(list)); }
-
獲取需要排除的自動配置類,可通過
@EnableAutoConfiguration
註解的exclude
和excludeName
配置,也可以通過spring.autoconfigure.exclude
配置 -
處理
exclusions
中特殊的類名稱,保證能夠排除它 -
從
configurations
中將exclusions
需要排除的自動配置類移除 -
呼叫
filter(..)
方法, 目的就是過濾掉一些不符合 Condition 條件的自動配置類,和在 1. selectImports 方法 小節中講到的效能優化有關哦 -
從
META-INF/spring.factories
找到所有的 AutoConfigurationImportListener 事件監聽器,觸發每個監聽器去處理 AutoConfigurationImportEvent 事件,該事件中包含了configurations
和exclusions
Spring Boot 中配置了一個監聽器,目的就是將
configurations
和exclusions
儲存至 AutoConfigurationImportEvent 物件中,並註冊到 IoC 容器中,名稱為autoConfigurationReport
,這樣一來我們可以注入這個 Bean 獲取到自動配置類資訊 -
將所有的自動配置類封裝成一個 AutoConfigurationEntry 物件,並返回
整個過程不復雜,關鍵在於上面的第 3
步和第 8
步,先從所有的 META-INF/spring.factories
檔案中找到 @EnableAutoConfiguration
註解對應的類(需要自動配置的類),然後進行過濾
3. filter 方法
filter(List<String>, AutoConfigurationMetadata)
方法,過濾一些自動配置類
我們得先知道這兩個入參:
- 所有的自動配置類名稱
META-INF/spring-autoconfigure-metadata.properties
檔案儲存的 Spring Boot 的自動配置類的註解元資訊(Sprng Boot 編譯時生成的)
這裡的目的就是根據 2
裡面的註解元資訊,先過濾掉一些自動配置類
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
// <1> 將自動配置類儲存至 `candidates` 陣列中
String[] candidates = StringUtils.toStringArray(configurations);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
/*
* <2> 從 `META-INF/spring.factories` 找到所有的 AutoConfigurationImportFilter 對 `candidates` 進行過濾處理
* 有 OnClassCondition、OnBeanCondition、OnWebApplicationCondition
*/
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
// <2.1> Aware 回撥
invokeAwareMethods(filter);
// <2.2> 對 `candidates` 進行匹配處理,獲取所有的匹配結果
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
// <2.3> 遍歷匹配結果,將不匹配的自動配置類至空
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
candidates[i] = null;
skipped = true;
}
}
}
// <3> 如果沒有不匹配的結果則全部返回
if (!skipped) {
return configurations;
}
// <4> 獲取到所有匹配的自動配置類,並返回
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
return new ArrayList<>(result);
}
過程如下:
-
將自動配置類儲存至
candidates
陣列中 -
從
META-INF/spring.factories
找到所有的AutoConfigurationImportFilter
對candidates
進行過濾處理# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
- Aware 回撥
- 對
candidates
進行匹配處理,獲取所有的匹配結果,注意,這裡傳入了AutoConfigurationMetadata
物件 - 遍歷匹配結果,將不匹配的自動配置類至空
-
如果沒有不匹配的結果則全部返回
-
獲取到所有匹配的自動配置類,並返回
關鍵在於上面的第 2
步,通過 Spring Boot 自己擴充套件的幾個自動配置類過濾器進行過濾,由於這部分內容和 Spring Boot 擴充 Condition 相關,放入下篇文章進行分析
下面我們一起來看看上面 1. selectImports 方法 小節中講到的效能優化,META-INF/spring-autoconfigure-metadata.properties
檔案是如何生成的,檔案的內容又是什麼
AutoConfigureAnnotationProcessor
org.springframework.boot.autoconfigureprocessor.AutoConfigureAnnotationProcessor
,Spring Boot 的 spring-boot-autoconfigure-processor
工具模組中的自動配置類的註解處理器,在編譯階段掃描自動配置類的註解元資訊,並將他們儲存至一個 properties
檔案中
@SupportedAnnotationTypes({ "org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
"org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
"org.springframework.boot.autoconfigure.AutoConfigureBefore",
"org.springframework.boot.autoconfigure.AutoConfigureAfter",
"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
/**
* 生成的檔案
*/
protected static final String PROPERTIES_PATH = "META-INF/spring-autoconfigure-metadata.properties";
/**
* 儲存指定註解的簡稱和註解全稱之間的對應關係(不可修改)
*/
private final Map<String, String> annotations;
private final Map<String, ValueExtractor> valueExtractors;
private final Properties properties = new Properties();
public AutoConfigureAnnotationProcessor() {
// <1> 建立一個 Map 集合
Map<String, String> annotations = new LinkedHashMap<>();
// <1.1> 將指定註解的簡稱和全稱之間的對應關係儲存至第 `1` 步建立的 Map 中
addAnnotations(annotations);
// <1.2> 將 `1.1` 的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給 `annotations`
this.annotations = Collections.unmodifiableMap(annotations);
// <2> 建立一個 Map 集合
Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
// <2.1> 將指定註解的簡稱和對應的 ValueExtractor 物件儲存至第 `2` 步建立的 Map 中
addValueExtractors(valueExtractors);
// <2.2> 將 `2.1` 的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給 `valueExtractors`
this.valueExtractors = Collections.unmodifiableMap(valueExtractors);
}
}
AbstractProcessor 是 JDK 1.6 引入的一個抽象類,支援在編譯階段進行處理,在構造器中做了以下事情:
-
建立一個 Map 集合
-
將指定註解的簡稱和全稱之間的對應關係儲存至第
1
步建立的 Map 中protected void addAnnotations(Map<String, String> annotations) { annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass"); annotations.put("ConditionalOnBean", "org.springframework.boot.autoconfigure.condition.ConditionalOnBean"); annotations.put("ConditionalOnSingleCandidate", "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate"); annotations.put("ConditionalOnWebApplication", "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication"); annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore"); annotations.put("AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureAfter"); annotations.put("AutoConfigureOrder", "org.springframework.boot.autoconfigure.AutoConfigureOrder"); }
-
將
1.1
的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給annotations
-
-
建立一個 Map 集合
-
將指定註解的簡稱和對應的 ValueExtractor 物件儲存至第
2
步建立的 Map 中private void addValueExtractors(Map<String, ValueExtractor> attributes) { attributes.put("ConditionalOnClass", new OnClassConditionValueExtractor()); attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor()); attributes.put("ConditionalOnSingleCandidate", new OnBeanConditionValueExtractor()); attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type")); attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name")); attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name")); attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value")); }
-
將
2.1
的 Map 轉換成不可修改的 UnmodifiableMap 集合,賦值給valueExtractors
-
getSupportedSourceVersion 方法
返回支援的 Java 版本
@Override
public SourceVersion getSupportedSourceVersion() {
// 返回 Java 版本,預設 1.5
return SourceVersion.latestSupported();
}
process 方法
處理過程
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// <1> 遍歷上面的幾個 `@Conditional` 註解和幾個定義自動配置類順序的註解,依次進行處理
for (Map.Entry<String, String> entry : this.annotations.entrySet()) {
// <1.1> 對支援的註解進行處理,也就是找到所有標註了該註解的類,然後解析出該註解的值,儲存至 Properties
// 例如 `類名.註解簡稱` => `註解中的值(逗號分隔)` 和 `類名` => `空字串`,將自動配置類的資訊已經對應註解的資訊都儲存起來
// 避免你每次啟動 Spring Boot 應用都要去解析自動配置類上面的註解,是引入 `spring-boot-autoconfigure` 後可以從 `META-INF/spring-autoconfigure-metadata.properties` 檔案中直接獲取
// 這麼一想,Spring Boot 設計的太棒了,所以你自己寫的 Spring Boot Starter 中的自動配置模組也可以引入這個 Spring Boot 提供的外掛
process(roundEnv, entry.getKey(), entry.getValue());
}
// <2> 如果處理完成
if (roundEnv.processingOver()) {
try {
// <2.1> 將 Properties 寫入 `META-INF/spring-autoconfigure-metadata.properties` 檔案
writeProperties();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to write metadata", ex);
}
}
// <3> 返回 `false`
return false;
}
過程如下:
-
遍歷上面的幾個
@Conditional
註解和幾個定義自動配置類順序的註解,依次進行處理- 呼叫
process(..)
過載方法,對支援的註解進行處理,也就是找到所有標註了該註解的類,然後解析出該註解的值,儲存至 Properties
- 呼叫
-
如果處理完成
-
呼叫
writeProperties()
方法,將 Properties 寫入META-INF/spring-autoconfigure-metadata.properties
檔案private void writeProperties() throws IOException { if (!this.properties.isEmpty()) { FileObject file = this.processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH); try (OutputStream outputStream = file.openOutputStream()) { this.properties.store(outputStream, null); } } }
-
上面的第 1.1
步處理後的 Properties 包含以下內容:
自動配置類的類名.註解簡稱
-->註解中的值(逗號分隔)
自動配置類的類名
-->空字串
通過後續寫入的檔案,避免你每次啟動 Spring Boot 應用都要去解析自動配置類上面的註解,從而提高應用啟動時的效率
這麼一想,Spring Boot 設計的太棒了,所以你自己寫的 Spring Boot Starter 中的自動配置模組也可以引入這個 Spring Boot 提供的外掛
process 過載方法
private void process(RoundEnvironment roundEnv, String propertyKey, String annotationName) {
// <1> 獲取到這個註解名稱對應的 Java 型別
TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName);
if (annotationType != null) {
// <2> 如果存在該註解,則從 RoundEnvironment 中獲取標註了該註解的所有 Element 元素,進行遍歷
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
// <2.1> 獲取這個 Element 元素 innermost 最深處的 Element
Element enclosingElement = element.getEnclosingElement();
// <2.2> 如果最深處的 Element 的型別是 PACKAGE 包,那麼表示這個元素是一個類,則進行處理
if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE) {
// <2.2.1> 解析這個類上面 `annotationName` 註解的資訊,並儲存至 `properties` 中
processElement(element, propertyKey, annotationName);
}
}
}
}
過程如下:
- 獲取到這個註解名稱對應的 Java 型別
- 如果存在該註解,則從 RoundEnvironment 中獲取標註了該註解的所有 Element 元素,進行遍歷
- 獲取這個 Element 元素 innermost 最深處的 Element
- 如果最深處的 Element 的型別是 PACKAGE 包,那麼表示這個元素是一個類,則進行處理
- 呼叫
processElement(..)
方法,解析這個類上面annotationName
註解的資訊,並儲存至properties
中
- 呼叫
processElement 方法
private void processElement(Element element, String propertyKey, String annotationName) {
try {
// <1> 獲取這個類的名稱
String qualifiedName = Elements.getQualifiedName(element);
// <2> 獲取這個類上面的 `annotationName` 型別的註解資訊
AnnotationMirror annotation = getAnnotation(element, annotationName);
if (qualifiedName != null && annotation != null) {
// <3> 獲取這個註解中的值
List<Object> values = getValues(propertyKey, annotation);
// <4> 往 `properties` 中新增 `類名.註解簡稱` => `註解中的值(逗號分隔)`
this.properties.put(qualifiedName + "." + propertyKey, toCommaDelimitedString(values));
// <5> 往 `properties` 中新增 `類名` => `空字串`
this.properties.put(qualifiedName, "");
}
}
catch (Exception ex) {
throw new IllegalStateException("Error processing configuration meta-data on " + element, ex);
}
}
過程如下:
-
獲取這個類的名稱
-
獲取這個類上面的
annotationName
型別的註解資訊private AnnotationMirror getAnnotation(Element element, String type) { if (element != null) { for (AnnotationMirror annotation : element.getAnnotationMirrors()) { if (type.equals(annotation.getAnnotationType().toString())) { return annotation; } } } return null; }
-
獲取這個註解中的值
private List<Object> getValues(String propertyKey, AnnotationMirror annotation) { // 獲取該註解對應的 value 抽取器 ValueExtractor extractor = this.valueExtractors.get(propertyKey); if (extractor == null) { return Collections.emptyList(); } // 獲取這個註解中的值,並返回 return extractor.getValues(annotation); }
-
往
properties
中新增類名.註解簡稱
-->註解中的值(逗號分隔)
-
往
properties
中新增類名
-->空字串
總結
本文分析了 @SpringBootApplication
組合註解,它是 @SpringBootConfiguration
、@ComponentScan
和 @EnableAutoConfiguration
幾個註解的組合註解,對於前兩個註解我想你並不陌生,分析 Spring 原始碼的時候差不多已經講過,最後一個註解則是 Spring Boot 自動配置功能的驅動註解,也是本文講述的一個重點。
@EnableAutoConfiguration
註解的實現原理並不複雜,藉助於 @Import
註解,從所有 META-INF/spring.factories
檔案中找到 org.springframework.boot.autoconfigure.EnableAutoConfiguration
對應的值,例如:
會將這些自動配置類作為一個 Bean 嘗試注入到 Spring IoC 容器中,注入的時候 Spring 會通過 @Conditional
註解判斷是否符合條件,因為並不是所有的自動配置類都滿足條件。當然,Spring Boot 對 @Conditional
註解進行了擴充套件,例如 @ConditionalOnClass
可以指定必須存在哪些 Class 物件才注入這個 Bean。
Spring Boot 會藉助 spring-boot-autoconfigure-processor
工具模組在編譯階段將自己的自動配置類的註解元資訊儲存至一個 properties
檔案中,避免每次啟動應用都要去解析這麼多自動配置類上面的註解。同時會通過幾個過濾器根據這個 properties
檔案過濾掉一些自動配置類,具體怎麼過濾的會在下面文章講到。
好了,總結下來就是 Spring Boot 會從 META-INF/spring.factories
檔案中找到配置的自動配置類,然後根據 Condition 條件進行注入,如果注入的話則可以通過 @EnableXxx
模組驅動註解驅動某個模組,例如 Spring AOP 模組。那麼關於 Spring Boot 對 Spring Condition 的擴充套件在下篇文章進行分析。