上篇分析了SpringBoot
应用的启动流程,说到过自动装配发生在加载配置类并解析出对应的BeanDefinition
这一阶段,同时我们也简要说明了@EnableAutoConfiguration
是通过@Import
元注解引入的AutoConfigurationImportSelector
来实现的,今天就来分析下吧(基于spring-boot-2.1.6.RELEASE
)。
Preface
典型的
SpringBoot
应用大多有着类似上图的包结构,我们知道@SpringBootApplication
是一个复合注解,它包括:
@Configuration
@ComponentScan
@EnableAutoConfiguration
这就意味着左侧Application
这个类可以作为配置类来定义组件,比如添加@Bean
标注的方法;同时它所在的包com.anyoptional
作为组件扫描的根包,所有定义在其下及其子包下的组件都会被Spring IoC
容器管理;最后是通过自动装配机制导入的外部配置。
再提一嘴@Import
元注解,它支持的导入类型有三种:
-
@Configuration
配置类,直接向容器导入组件 -
ImportSelector
实现类,返回@Configuration
配置类,间接向容器导入组件 -
ImportBeanDefinitionRegistrar
实现类,携带BeanDefinitionRegistry
,支持直接向容器中注册组件
How
AutoConfigurationImportSelector
实现了DeferredImportSelector
接口——ImportSelector
的一个变种,它的运行时机在所有@Configuration
配置类被处理完毕之后。注意这个时间点,此时所有用户自定义的组件已经得到解析,这不正是条件注解@Conditional
生效的好时机吗?大家比较熟悉的@ConditionalOnMissingBean
、@ConditionalOnProperty
均是@Conditional
元注解衍生出来的。spring-boot-autoconfigure
虽然大量依赖@Conditional
来实现条件注入,不过这部分逻辑却是在spring-context
中实现的,有兴趣的话大家可以翻翻ConfigurationClassPostProcessor
的源码。
selectImports(...)
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 查看配置项 spring.boot.enableautoconfiguration
// 看看是否允许自动装配
if (!isEnabled(annotationMetadata)) {
// 不允许的话返回空数组了事
return NO_IMPORTS;
}
// 解析由 auto-configure annotation processor 生成的元数据
// 默认位于 META-INF/spring-autoconfigure-metadata.properties
// 里面的信息主要有两部分:
// 1. 自动配置类的导入条件,用来做过滤,优点是不用实例化配置类再做判断
// 2. 自动配置类的相对顺序,用来调整加载顺序
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// AutoConfigurationEntry 包含需要导入的自动配置类和需要排除的自动配置类
// 这一步便是通过 Spring SPI 机制加载所有的自动配置类,当然这里面还包含
// 过滤、排序等一系列操作
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
// 返回需要由容器加载的自动配置类
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
AutoConfigurationMetadataLoader
AutoConfigurationMetadataLoader
的源码并不复杂,默认情况下它读取并解析位于META-INF
下的spring-autoconfigure-metadata.properties
文件。这个文件由AutoConfigureAnnotationProcessor
解析class file
生成(位于包spring-boot-autoconfigure-processor
),它主要包含以下两个方面的内容:
- 自动配置类的导入条件
- 自动配置类的相对顺序
# 截取自 spring-boot-autoconfifure 下的 META-INF/spring-autoconfigure-metadata.properties
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnSingleCandidate=javax.sql.DataSource
对于上面这个例子,说明:
-
JdbcTemplateAutoConfiguration
需要在DataSourceAutoConfiguration
之后加载 -
JdbcTemplateAutoConfiguration
是否生效取决于容器中是否存在唯一的类型为javax.sql.DataSource
的Bean
这其实很好理解,因为JdbcTemplate
操作数据库的能力来源于标准的javax.sql.DataSource
,而javax.sql.DataSource
默认情况下由DataSourceAutoConfiguration
提供。
// AutoConfigureAnnotationProcessor 的声明
@SupportedAnnotationTypes({ "org.springframework.context.annotation.Configuration",
"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 {
// omitted...
}
翻一下AutoConfigureAnnotationProcessor
的源码,可以清楚地看到它支持的注解类型。因为AutoConfigureAnnotationProcessor
解析的是class file
,所以可以实现在不实例化自动配置类(比如上例的JdbcTemplateAutoConfiguration
)的情况下判断它是否可以被容器加载,算是对内存的一点节约吧。
AutoConfigurationEntry
protected static class AutoConfigurationEntry {
// 需要被加载的自动配置类
private final List<String> configurations;
// 不需要被加载的自动配置类
private final Set<String> exclusions;
// rest are omitted...
}
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取 @EnableAutoConfiguration 注解 属性 -> 属性值 的映射
// 其实就是声明的 exclude/excludeName
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 通过 Spring SPI 机制,获取 spring.factories 文件中所有以
// @EnableAutoConfiguration 注解的全限定名为 key 的值,这些值对应着自动配置类的名称
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 通过 Set 去重
configurations = removeDuplicates(configurations);
// 提取由 @EnableAutoConfiguration#exclude/excludeName 和 spring.autoconfigure.exclude
// 指定的自动配置类的名称,这些配置类不需要被容器加载
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查这些待排除的自动配置类是否存在,并且它们是否真的属于自动配置类的一员
checkExcludedClasses(configurations, exclusions);
// 从自动配置类集合中排除这些不需要加载的
configurations.removeAll(exclusions);
// 根据 AutoConfigurationMetadata 进行过滤
configurations = filter(configurations, autoConfigurationMetadata);
// 通过 SPI 机制加载 AutoConfigurationImportListener(s) 进行回调
fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回结果
return new AutoConfigurationEntry(configurations, exclusions);
}
getAutoConfigurationEntry(...)
主要做了下面几件事:
- 汇总需要排除的自动配置类
- 通过Spring SPI机制提取所有待加载的自动配置类
- 过滤第
2
步得到的集合,剔除不符合要求或不需要进行加载的 - 回调
AutoConfigurationImportListener
,监听器同样由Spring SPI
机制加载
其中第1
、2
和第4
步都比较简单,大家自己看看就可以了,我们重点来说说第3
步——过滤。
private List<String> filter(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
// 对应 configurations 中自动配置类的下标,标记其是否需要被排除
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
// 通过 SPI 机制加载 AutoConfigurationImportFilter 来进行过滤
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
invokeAwareMethods(filter);
// 判定
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
// 未通过的话进行记录
if (!match[i]) {
skip[i] = true;
// 清掉数据,如此下一个 Filter 就不用再判定了
candidates[i] = null;
skipped = true;
}
}
}
// 都有效直接返回即可,否则...
if (!skipped) {
return configurations;
}
// 就需要遍历一次,剔除不需要的
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return new ArrayList<>(result);
}
可以看到对自动配置类的过滤工作是通过代理给AutoConfigurationImportFilter
来完成的,这些过滤器同样是通过Spring SPI
机制进行加载的,默认注册的有:
# 截取自 spring-boot-autoconfigure 包下的 META-INF/spring.factories
# 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
都是好东西,挑一挑吧。那就挑OnClassCondition
来看看吧。
OnClassCondition
翻开OnClassCondition
的源码,可以看到AutoConfigurationImportFilter#match(...)
是其父类实现的,然后代理给了模板方法getOutcomes(...)
,我们直接看这个方法就行。
// 实际工作又代理给了 OutcomesResolver, 注意这里有个并行的优化
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 天下自动配置类千千万
int split = autoConfigurationClasses.length / 2;
// 你一半
OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
autoConfigurationMetadata);
// 我一半
OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
// 合并两个 OutcomesResolver 的处理结果
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}
只能说大牛写的代码都比较绕吧,getOutcomes(...)
也是个代理商,实际的工作都交给OutcomesResolver
去完成了。注意这里分而治之的思想,待处理的自动配置类被一分为二,由两个线程并行完成。那继续看看StandardOutcomesResolver
是怎么处理的呗。
private final class StandardOutcomesResolver implements OutcomesResolver {
// fields、ctor are omitted...
@Override
public ConditionOutcome[] resolveOutcomes() {
return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for (int i = start; i < end; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
// 获取当前 autoConfigurationClass 的 ConditionalOnClass
// 比如 example.MyConfig.ConditionalOnClass=example.YourConfig
// 这里就会返回 example.YourConfig,注意这个值是 CSV 格式的
String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
// 判定 candidates 代表的类是否存在于 classpath 下
outcomes[i - start] = getOutcome(candidates);
}
}
}
return outcomes;
}
// 对 candidates 的格式进行处理
private ConditionOutcome getOutcome(String candidates) {
try {
if (!candidates.contains(",")) {
return getOutcome(candidates, this.beanClassLoader);
}
// CSV 格式分解
for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
if (outcome != null) {
return outcome;
}
}
}
catch (Exception ex) {
// We'll get another chance later
}
return null;
}
// 最终的判定逻辑
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
// ClassNameFilter 无非是通过类加载机制来进行判定
// 比如 classLoader.loadClass(className) 或者 classLoader 为空则是 Class.forName(className)
if (ClassNameFilter.MISSING.matches(className, classLoader)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class").items(Style.QUOTE, className));
}
return null;
}
}
前面分析了AutoConfigurationMetadata
的加载,这里就利用上了这部分元数据——由autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass")
体现,剩下的只需要通过类加载机制判定一下这些类是否存在就可以了。注意整个判定过程中自动配置类并没有实例化,仅仅使用了它的全限定名。
getImportGroup()
前面对selectImports(...)
的分析已经可以解释SpringBoot
的自动装配机制了,不过这里还有一个问题,那就是没有处理自动配置类的加载顺序。我们知道自动配置类可以使用@AutoConfigureBefore
或@AutoConfigureAfter
来指定相对顺序,使用AutoConfigureOrder
来指定绝对顺序,不过这在selectImports(...)
中一点儿也没有得到体现。答案就在getImportGroup()
,它返回的AutoConfigurationGroup
处理了这个遗留问题。
@Override
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}
AutoConfigurationGroup
private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
// fields、*Aware are omitted...
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 上一节分析过了,这里就得到了待加载和被排除的自动配置类集合
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 自动装配类的类名 -> 标注 @EnableAutoConfiguration 类的注解元信息
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
@Override
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
Set<String> allExclusions = this.autoConfigurationEntries.stream()
// 待排除的自动配置类集合
.map(AutoConfigurationEntry::getExclusions)
// 降维
.flatMap(Collection::stream)
// 收集成 Set 去重
.collect(Collectors.toSet());
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
// 待加载的自动配置类集合
.map(AutoConfigurationEntry::getConfigurations)
// 降维
.flatMap(Collection::stream)
// 收集成 Set 去重并保持顺序
.collect(Collectors.toCollection(LinkedHashSet::new));
// 剔除明确不需要加载的
processedConfigurations.removeAll(allExclusions);
// 对剩下的进行排序并转换成Entry
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
// getAutoConfigurationMetadata() omitted...
private List<String> sortAutoConfigurations(Set<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
// 使用 AutoConfigurationSorter 进行排序
return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
.getInPriorityOrder(configurations);
}
// 如果 spring boot starter 未引入 spring-boot-autoconfigure-processor.jar
// 那就不会生成 spring-autoconfigure-metadata.properties
// 这样的话就需要 MetadataReaderFactory 创建 MetadataReader 来读取
// 和自动配置顺序相关的注解了,比如 @AutoConfigureBefore
private MetadataReaderFactory getMetadataReaderFactory() {
try {
return this.beanFactory.getBean(SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
MetadataReaderFactory.class);
} catch (NoSuchBeanDefinitionException ex) {
return new CachingMetadataReaderFactory(this.resourceLoader);
}
}
}
AutoConfigurationGroup
确实只加入了对自动配置类顺序的处理,其它逻辑是相同的,我们的重心自然也转到AutoConfigurationSorter
。
AutoConfigurationSorter
public List<String> getInPriorityOrder(Collection<String> classNames) {
AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
this.autoConfigurationMetadata, classNames);
List<String> orderedClassNames = new ArrayList<>(classNames);
// 1. 按字母表顺序排序
Collections.sort(orderedClassNames);
// 2. 按 @AutoConfigureOrder 指定的优先级排序
orderedClassNames.sort((o1, o2) -> {
int i1 = classes.get(o1).getOrder();
int i2 = classes.get(o2).getOrder();
return Integer.compare(i1, i2);
});
// 3. 最后根据 @AutoConfigureBefore @AutoConfigureAfter 来进行微调
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
鉴于本篇的篇幅已经很长了,这里仅仅标注了一下AutoConfigurationSorter
进行排序的大体步骤,具体细节各位看官自己去扣一扣吧。
End
今天和大家分享了SpringBoot
自动装配机制的原理,相信大家看完后可以开心地编排自定义的starter
了,完。