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*注解的工做原理。