SpringBoot 自動裝配原理,一文掌握!
本文詳細講解了 SpringBoot 自動裝配原理,可以直接拉到最後看總結。由於 Spring 源碼比較複雜,是需要一些基礎的。
Spring Boot 一個很大的特點就是極大的簡化了原來在 Spring 中複雜的 XML 文件配置過程,讓我們對 Spring 應用對搭建和開發變得極其簡單。既然可以簡化配置,那就意味着很多配置都是需要默認的,這也使其提出了約定大於配置和自動裝配的思想。一些通用的配置會默認設置好,整個組件需要的時候直接載入,不需要的時候可以整個卸載。
通過 Spring Boot 我們可以很方便的引入新的組件,只需要在依賴文件中加入對應的 xxx-starter 即可,然後把一些必要的配置比如 url 信息做個簡單的設置,或者增加一個 @EnableXXX,就可以開始使用了。
這裏以 Feign 爲例:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@SpringBootApplication
@EnableFeignClients
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
只需要這樣,就可以將 Feign 引入項目中了,接下來根據自己的需要定義對應的 Feign client 即可,是不是非常簡單。
Spring Boot 到底是如何做到的呢?
自動裝配原理
自動裝配的入口
自動裝配的基礎,是 Spring 從 4.x 版本開始支持 JavaConfig,讓開發者可以免去繁瑣的 xml 配置形式,而是使用熟悉的 Java 代碼加註解,通過 @Configuration、@Bean 等註解可以直接向 Spring 容器注入 Bean 信息。
那麼就有種設想,如果我把一些必須的 Bean 以 Java 代碼方式準備好呢,只需要引入對應的配置類,相應的 Bean 就會被加載到 Spring 容器中。所以,有了這個基礎 Spring Boot 就有了實現自動裝配的可能。
還是以 Feign 爲例,FeignAutoConfiguration 這個類就是一個 Feign 的自動裝配類,我們來探究一下他是如何生效的。
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
@Autowired(required = false)
// 注入了一堆FeignClientSpecification
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
}
在每個 Spring Boot 的啓動類上,都會有這樣一個複合註解 @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) })
public @interface SpringBootApplication {
}
……省略其他註解
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
上面四個都是一些通用註解,關鍵在於下面的 @EnableAutoConfiguration
註解,從名字就可以看出來,它可以開啓自動配置。在之前一篇文章我們已經講過,通過 @Import
註解我們可以導入一些自定義的 BeanDefination 信息,或者導入一些配置類。在 EnableAutoConfiguration 內部,它使用 @Import 標註了 AutoConfigurationImportSelector。
可以看出,它實現了 ImportSelector 接口,之前我們已經在講結果 @Import 註解是如何生效的。
下面,先對後面涉及的一些類和文件做一個簡單介紹。
ImportSelector
看名字就可以知道,這是用於導入的選擇器。String[] selectImports()
方法會返回需要導入的配置類的全路徑名。在 Spring 容器啓動的過程中會調用 invokeBeanFactoryPostProcessors
,然後會執行一個重要的後置處理器 ConfigurationClassPostProcessor
,完成配置類的解析,這裏會處理 ImportSelector 返回的這些類,將其加載到容器中。
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
DeferredImportSelector
DeferredImportSelector 繼承了 ImportSelector,它的作用是用於延遲導入。在所有的需要處理的配置類解析過程中,繼承此接口的解析排在最後,並且在有多個 DeferredImportSelector 實現類的情況下,可以繼承 Ordered 實現排序的效果。
public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class<? extends Group> getImportGroup() {
return null;
}
interface Group {
}
}
繼續之前的內容,AutoConfigurationImportSelector 實現了 DeferredImportSelector 接口,所
在執行 ConfigurationClassParser.processImports()
方法的時候,最終會調用到下面這段邏輯。一般繼承 ImportSelector 會執行其 selectImport
方法。但是這裏不同的是,它還繼承了 DeferredImportSelector
接口,對 ImportSelector 只是間接繼承。在 processImports()
方法中有這樣的額外判斷,如果是 DeferredImportSelector 的子類,將會執行 deferredImportSelectorHandler.handle()
,最終會回調 AutoConfigurationImportSelector
的 process
方法。具體的調用過程請見下圖。
Spring Boot 2.x 的版本與 1.x 有所不同,1.x 是回調 selectImports 方法。
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
……
if (candidate.isAssignable(ImportSelector.class)) {
……
// 執行這段邏輯
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
……
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
……
}
讀取配置
在 process()
方法中主要做了一件事,讀取並解析 spring.factories 配置文件中的信息,將這些配置文件對應的全路徑類名都放入 AutoConfigurationEntry 集合中。接下來詳細解釋相關邏輯。
getAutoConfigurationMetadata()
方法讀取並解析了 spring-autoconfigure-metadata.properties 文件,用於控制自動裝配條件。關於這個路徑信息,追蹤方法可以找到,比較簡單。
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
getAutoConfigurationEntry()
方法會獲取需要自動裝配的類和需要排除的類,讀取的文件是 META-INF/spring.factories
。
關於這個文件路徑是怎麼指定的,可以在下面方法中看到。一直深入追蹤,在 loadSpringFactories
方法中,會加載 META-INF/spring.factories 路徑下的配置內容,並且這個路徑是硬編碼寫死的。在全部讀取完畢之後,會放在一個 Map 中,key 爲類名,value 爲對應的自定義配置類。getSpringFactoriesLoaderFactoryClass()
方法會固定返回 EnableAutoConfiguration.class,所以這裏只會返回 EnableAutoConfiguration 對應的配置內容,配置文件內容如下圖。
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
……
// getAutoConfigurationMetadata() 方法讀取並解析了 spring-autoconfigure-metadata.properties 文件,用於控制自動裝配條件
// AutoConfigurationEntry 方法會獲取需要自動裝配的類和需要排除的類
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
// 添加到 AutoConfigurationEntry集合中等待加載
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
// 返回自動配置的類名,加載Spring.factories中的配置信息
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// loadFactoryNames 會讀取對應的配置文件,位置在META-INF/spring.factories中
// getSpringFactoriesLoaderFactoryClass 返回 EnableAutoConfiguration.class
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
……
return configurations;
}
具體執行方法的調用鏈路如下:
加載配置
到這裏我們就明白,spring.properties 文件中的配置類是如何加載的,但是問題來了,他什麼時候註冊到 Spring 容器中呢?
回到之前執行過程中的processGroupImports
方法(在前面的圖片已用紅框標註了出來),這裏會調用 getImports
拿到配置類信息,然後再次調用類信息,然後遞歸調用 processImports
,這個方法之前的文章已經解釋過了,如果是配置類會解析並註冊 Spring 的 Bean 信息,具體請自行查看之前文章。
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
// getImports 得到解析後的類名
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
// 再次調用 processImports 遞歸處理,對配置類解析,註冊爲 Bean
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
catch (BeanDefinitionStoreException ex) {
……
}
});
}
}
另外,再額外說一下 getImports 方法。之前 process 方法並沒有返回值,而是把配置信息都保存在了 autoConfigurationEntries 中,所以在執行完 process 之後會緊接着執行 selectImports()
。它的功能主要是排除需要排除的類信息,並且在這裏按照 spring-autoconfigure-metadata.properties 中指定的順序排序,然後再返回類信息。
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
// 調用 process 邏輯
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 獲取所有需要排除的類集合
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
// 獲取所有需要裝配的類集合
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 移除所有排除類
processedConfigurations.removeAll(allExclusions);
// 將需要加載的類排序返回,排序規則按照 spring-autoconfigure-metadata.properties 中指定的順序
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
總結
最後,我們再次總結一下整個自動裝配的過程。
-
引入 META-INF/spring.factories 配置文件,在 EnableAutoConfiguration 對應的 value 中配置需要引入的配置類。
-
啓動類增加 @EnableAutoConfiguration 註解,@SpringBootApplication 已經自帶。
-
@EnableAutoConfiguration 註解中通過 @Import 標註了 AutoConfigurationImportSelector 類。
-
AutoConfigurationImportSelector 繼承了 DeferredImportSelector 接口,在 Spring 生命週期處理 BeanFactoryPostProcessors 的時候會對配置信息進行後置處理,這是會調用到 AutoConfigurationImportSelector.process 方法。
-
process 方法中會讀取 META-INF/spring.factories 配置文件中的內容爲 Key-Value 形式,讀取完後值返回 key = EnableAutoConfiguration 對應的配置類信息,保存到 autoConfigurationEntries 中。
-
AutoConfigurationGroup.selectImports 方法返回排序、篩選後的配置類信息,然後依次遍歷,遞歸調用 processImports, 根據這些配置類的全路徑名讀取並註冊在 Spring 容器中。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/4WKp5QcDHxr-qvNaeg88yQ