Spring Boot Starter的自動配置(Auto-configuration)是個很強大的東東,也是重要且巧妙的設計。不過不把它的原理搞清楚,在判斷哪些自動配置會被啓用時,會摸不着頭腦。下面就從源碼的角度來一探究竟。html
Auto-configuration和Spring的Java Configuration不是一回事哦。具體可看Spring Boot文檔中的定義。java
見 參考文章。bash
@SpringBootApplication
註解的定義以下:ide
// 前面略
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
複製代碼
首先看@ComponentScan
部分,使用了一個AutoConfigurationExcludeFilter
做爲exclude filter,從字面上講,就是包掃描排除掉自動配置的包。So,若是Spring判斷某個類是自動配置類,則不會對其進行包掃描(以建立聲明的Bean)。具體邏輯再也不深究。spring-boot
這裏重點要講的是@EnableAutoConfiguration
這個註解:post
// 省略不重要的內容
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
複製代碼
關注@Import(AutoConfigurationImportSelector.class)
這句話。@Import
咱們都很熟悉了,它是XML配置中的<import>
的替代。然而AutoConfigurationImportSelector
這個類並無被@Configuration
修飾,不是個Java配置類。因此就很奇怪了。this
看源碼應該優先看註釋。lua
咱們來看看@Import
的註釋透露了什麼:
Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes
複製代碼
嘿!原來它不只能導入Java配置類,還能處理ImportSelector
和ImportBeanDefinitionRegistrar
。
在講本節的重點ImportSelector
以前,還須要提一下@AutoConfigurationPackage
:
/** * Indicates that the package containing the annotated class should be registered with * {@link AutoConfigurationPackages}. */
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
複製代碼
而AutoConfigurationPackages.Registrar
正好是個ImportBeanDefinitionRegistrar
,它有個registerBeanDefinitions
的接口方法,能夠直接註冊Bean。後面(擴展閱讀)能夠看到,ImportBeanDefinitionRegistrar
是多個滴重要,它是各類"@EnableXX"註解的幕後英雄。
好了,再看看ImportSelector
接口:
// 省略不重要註釋
/** * Interface that determine which @{@link Configuration} class(es) should be imported * based on a given selection criteria, usually one or more annotation attributes. */
public interface ImportSelector {
/** * Select and return the names of which class(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
複製代碼
因此,它是用來決定一個被@Configuration
修飾了的Java配置類,應該被導入哪些配置的。依據是這個Java配置類的全部註解信息(保存在AnnotationMetadata
裏面)。
好,下一步,咱們就來看看AutoConfigurationImportSelector
如何實現。
此類對ImportSelector
接口的實現以下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
複製代碼
其中的getAutoConfigurationEntry
方法又調用了getCandidateConfigurations
方法,該方法以下:
/** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
複製代碼
因此,到這裏,會從包的META-INF/spring.factories
文件中讀取須要導入的自動配置類。更細節的代碼再也不往下貼了,具體可見SpringFactoriesLoader
這個類。
接下來的問題是,selectImports
是被誰調用的呢?說實話,調用的鏈路太深了,很容易被繞暈。下面簡單地描述一下主要流程。
做爲一個Spring的PostProcessor,這個類的功能是處理有 @Configuration 修飾的Java配置類。關注其核心方法:
// 省略非關鍵代碼
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
}
複製代碼
關鍵邏輯就是調用ConfigurationClassParser
類的parse
方法,而後對解析到的全部ConfigurationClass
,解析並加載內部定義的Bean。
parse()方法主要是調用processConfigurationClass
方法。
// 省略非核心代碼
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
複製代碼
此方法作了三件事:
doProcessConfigurationClass()
作具體解析configurationClasses
中而第二步的doProcessConfigurationClass()
具體解析啥呢?其實就是遞歸地遍歷當前Java 配置類的各類@Import、內部類、聲明的@ComponentScan等等,當發現其它Java配置類時,再次調用parse()
方法(或者processConfigurationClass()
方法),解析那個類。最終的結果是把全部能找到的Java配置類都加入了configurationClasses
容器中。
因爲@SpringBootApplication其實也就是一個帶有@Configuration的Java配置類,由Part II可知,它也會被按相同的方式解析。
而@SpringBootApplication所包含的註解最終又導入了AutoConfigurationImportSelector
類,所以這個類將會被調用到。下面是調用到這個類的getCandidateConfigurations
方法(Part I提到過)時的結果截圖:
到此,全部的Spring Boot Starter中定義的自動配置類都會被掃描到,並將被解析。
在Part 2 中,已經提到processConfigurationClass
這個方法的第一行就是在判斷Conditioanal條件是否知足。當須要debug某個自動配置爲什麼生效/不生效時,能夠重點關注這裏。
@Import是用來導入配置類的,而導入方式主要分爲如下三種類型。
@Configuration
修飾的類。ImportSelector
接口的實現類,返回一個配置類名稱的數組,而後再導入這些配置類。ImportBeanDefinitionRegistar
接口的實現類,直接在接口方法中註冊Bean。ImportSelector
接口的一個實現類AutoConfigurationImportSelector
則承包了從ClassPath下各個starter中的META-INF/spring.factories
文件中讀取須要導入的自動配置類的工做。
@SpringBootApplication
註解則間接繼承了AutoConfigurationImportSelector
的功能。
在擴展閱讀中,你將能看到各類@Enable*註解的工做原理。