SpringBoot(三)— 自动配置源码解析
基于 Spring Boot 2.7.x 版本,深入剖析了 @EnableAutoConfiguration 注解的实现原理,重点解析了其背后通过 DeferredImportSelector 实现的自动配置机制。特别对 @AutoConfigureBefore 与 @AutoConfigureAfter 所涉及的 DFS 拓扑排序逻辑 进行了详细的分析,揭示了自动配置类加载顺序的核心控制机制
最后,还分析了 2.7.0 版本新增的 @AutoConfiguration 注解的特殊语义,包括其与新的 .imports SPI 文件的绑定、默认排除扫描机制(AutoConfigurationExcludeFilter)。并给出了自己的使用建议
@EnableAutoConfiguration
Spring Boot 之所以能实现开箱即用,核心在于其自动配置机制,具体体现在 @EnableAutoConfiguration 注解的作用上。该注解会为项目注册大量默认的 Bean,从而简化开发的配置工作
@EnableAutoConfiguration 本身并不直接生效,它是依赖了如下这个元注解(Spring 在处理配置类时会通过 AnnotationUtils递归解析所有注解的元注解,从而将所有元注解的功能集成到一个组合注解上)
@Import(AutoConfigurationImportSelector.class)
通过前面分析的章节,可知@Import注解会在ConfigurationClassPostProcessor解析配置类阶段生效。其注册的AutoConfigurationImportSelector这才是真正的获取自动配置类的核心,它实现了DeferredImportSelector
与其它 @Import 导入的class不同,DeferredImportSelector会在所有常规的配置类(包括 @Configuration 的 full和@Component和lite模式)处理完成之后,再统一处理并解析其导入的相关配置类,这机制确保了自动配置不会抢占开发者自定义的配置解析和注册Bean的顺序,始终遵循用户配置优先原则
DeferredImportSelector
DeferredImportSelector 是 ImportSelector 的一个子接口,它扩展了一个关键方法 getImportGroup(),用于支持按Group延迟导入配置类
getImportGroup()会返回 DeferredImportSelector.Group 对象,这才是真正处理用来DeferredImportSelector逻辑,这个Group包含了如下特征
- Group加载的class可以理解为一个包,其中的类会在一起做过滤、排序等操作
- 最终返回结果交给上层
ConfigurationClassParser统一执行其配置类的解析
到现在2.7.x版本位置,也就只有一个Group为AutoConfigurationImportSelector.AutoConfigurationGroup,是SpringBoot 为自动配置机制专门提供的 Group 实现,用于处理.imports和spring.factories里的自动配置类,并应用 AutoConfigurationImportFilter、排序等核心逻辑
AutoConfigurationImportSelector
其核心方法为getAutoConfigurationEntry,主要逻辑如下。在此之前,可以先看看SPI机制这篇文章
利用SPI机制,加载如下配置类
- META-INF/spring.factories配置文件里的org.springframework.boot.autoconfigure.EnableAutoConfiguration
- 从2.7.0新增的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports获取配置类
去重和排除excluded相关类
应用AutoConfigurationImportFilter逻辑,对自动配置类部分Condition进行校验并过滤(AutoConfigurationImportFilter实例也是配置在spring.factories里的)
核心代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 判断spring.boot.enableautoconfiguration配置是否开启
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 1. 从 META-INF/spring.factories配置文件里获取org.springframework.boot.autoconfigure.EnableAutoConfiguration
// 2. 从2.7.0新增的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports获取配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// apply excluded逻辑
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
/*
通过配置在spring.factories里的AutoConfigurationImportFilter过滤一遍,提前剔除明显不满足条件的配置类,提高启动性能。有如下三个Filter:
1. org.springframework.boot.autoconfigure.condition.OnBeanCondition:apply @ConditionalOnSingleCandidate和@ConditionalOnBean逻辑
2. org.springframework.boot.autoconfigure.condition.OnClassCondition:apply @ConditionalOnClass逻辑
3. org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition:apply @ConditionalOnWebApplication逻辑
*/
configurations = getConfigurationClassFilter().filter(configurations);
// 发布AutoConfigurationImportEvent
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
AutoConfigurationGroup
是自动配置类导入的最终调度执行者,由 ConfigurationClassParser 在配置类解析流程的最后阶段统一触发,调用链如下::
- ConfigurationClassParser#parse
- ConfigurationClassParser.DeferredImportSelectorHandler#process
- ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports
- ConfigurationClassParser.DeferredImportSelectorGrouping#getImports
其主要有两个方法:
AutoConfigurationGroup#process:主要是利用AutoConfigurationImportSelector获取自动配置类,并缓存到其内部的entries中
AutoConfigurationGroup#selectImports:使用AutoConfigurationSorter对自动配置类进行排序,排序的优先级为:
@AutoConfigureBefore和@AutoConfigureAfter > @AutoConfigureOrder
核心代码
private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
@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()));
// 获取自动配置类并缓存到entries里
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
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)
.collect(Collectors.toSet());
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations)
.flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 排除excluded配置类
processedConfigurations.removeAll(allExclusions);
// 关键排序:@AutoConfigureBefore和@AutoConfigureAfter优先级 > @AutoConfigureOrder
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
}
AutoConfigurationSorter
自动配置类的解析顺序是非常重要的,尤其是在使用 @ConditionalOnBean、@ConditionalOnMissingBean 等条件注解时,先加载的配置类有优先对Bean的注册决定权,从而影响后续自动配置类中相同类型Bean的注册。且由于所有自动配置类都统一通过 DeferredImportSelector 延迟导入,因此必须在导入前进行统一排序,保证顺序稳定且可控。
所以,SpringBoot提供了如下三个注解,来决定自动配置类解析的先后顺序
- @AutoConfigureBefore:当前配置类应当 在指定类之前加载
- @AutoConfigureAfter:当前配置类应当 在指定类之后加载
- @AutoConfigureOrder:指定一个整数顺序值,数字越小,优先级越高
@AutoConfigureAfter和@AutoConfigureBefore这两个注解构成了一个类之间的依赖图,SpringBoot 会对这些依赖关系进行拓扑排序。不同于常见的入度统计法,SpringBoot用了深度优先遍历(DFS)+ 判环的方式实现排序。
排序原理
按
@AutoConfigureOrder初步排序 所有配置类根据注解的数值顺序进行一次粗排序,为后续 DFS 提供遍历顺序。
依赖链补齐
收集所有声明在
@AutoConfigureBefore和@AutoConfigureAfter中提到的类(尽管它们可能被excluded),将其一并加入拓扑图,保证排序图完整执行 DFS 拓扑排序(带判环)
对每个配置类递归处理其前置依赖类(**@AutoConfigureAfter直接解析当前配置类就行,但@AutoConfigureBefore需要解析所有的自动配置类。我上一篇讲的starter优化器在这里会发挥作用,获取相关注解的预解析结果,间接提升启动速度),在递归中动态构造有向图,并检测循环依赖**(若存在环则抛出异常)
过滤出最终导入的配置类
拓扑排序后将得到一个全有序列表,SpringBoot 再从中筛选出当前实际需要导入的类,作为最终结果返回。
核心代码
class AutoConfigurationSorter {
List<String> getInPriorityOrder(Collection<String> classNames) {
AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
this.autoConfigurationMetadata, classNames);
List<String> orderedClassNames = new ArrayList<>(classNames);
// Initially sort alphabetically
Collections.sort(orderedClassNames);
// Then sort by order
// 先按@AutoConfigureOrder排序
orderedClassNames.sort((o1, o2) -> {
int i1 = classes.get(o1).getOrder();
int i2 = classes.get(o2).getOrder();
return Integer.compare(i1, i2);
});
// Then respect @AutoConfigureBefore @AutoConfigureAfter
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
private List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) {
// 待排列表
List<String> toSort = new ArrayList<>(classNames);
// 加入所有参与排序的相关类。比如说某个配置类的@AutoConfigureBefore中的value不是配置类或者被excluded了,此时也要加入进来,使拓扑排序的链路更完整
toSort.addAll(classes.getAllNames());
// 已排序列表
Set<String> sorted = new LinkedHashSet<>();
// 处理中(检测循环依赖)
Set<String> processing = new LinkedHashSet<>();
// 开始排序
while (!toSort.isEmpty()) {
doSortByAfterAnnotation(classes, toSort, sorted, processing, null);
}
// 排序完毕,按顺序只保留classNames相关类
sorted.retainAll(classNames);
return new ArrayList<>(sorted);
}
/**
* 拓扑排序的dfs实现方案(非统计入度方式实现)
* 排序规则:
* 1. 若类 A 上标注 @AutoConfigureAfter(B),则 B 必须排在 A 之前
* 2. 若类 A 上标注 @AutoConfigureBefore(B),则 A 必须排在 B 之前
*/
private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<String> toSort, Set<String> sorted,
Set<String> processing, String current) {
if (current == null) {
current = toSort.remove(0);
}
processing.add(current);
// 依次处理每个current类的依赖类(也就是说需要确保current 排在 after 之后)
for (String after : classes.getClassesRequestedAfter(current)) {
// 拓扑排序中判环操作,避免循环依赖
checkForCycles(processing, current, after);
// 只有当该类还没被排序,且在待排序列表中,才递归处理依赖类after
if (!sorted.contains(after) && toSort.contains(after)) {
doSortByAfterAnnotation(classes, toSort, sorted, processing, after);
}
}
processing.remove(current);
// current的依赖都已处理完毕,此时可以放入current了
sorted.add(current);
}
private void checkForCycles(Set<String> processing, String current, String after) {
Assert.state(!processing.contains(after),
() -> "AutoConfigure cycle detected between " + current + " and " + after);
}
private static class AutoConfigurationClasses {
/**
* 获取某个类的所有排序依赖类(即必须排在className之前的类)
*/
Set<String> getClassesRequestedAfter(String className) {
// @AutoConfigureAfter处理
Set<String> classesRequestedAfter = new LinkedHashSet<>(get(className).getAfter());
// @AutoConfigureBefore只能遍历所有的配置类,依次判断是否包含当前的className
this.classes.forEach((name, autoConfigurationClass) -> {
if (autoConfigurationClass.getBefore().contains(className)) {
classesRequestedAfter.add(name);
}
});
return classesRequestedAfter;
}
}
}
@AutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore
@AutoConfigureAfter
public @interface AutoConfiguration {
// ... 省略
}
是2.7.0版本新增的自动配置相关的注解,专门用于声明自动配置类。从定义上看,它是一个组合注解,整合了:
@Configuration:标识该类是一个配置类@AutoConfigureBefore/@AutoConfigureAfter:用于声明自动配置类之间的依赖顺序
但它并不只是简单的注解组合,而是具备了特殊语义
特殊语义
新的SPI
支持新的 SPI 加载机制(.imports 文件),可以从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里加载自动配置类。从另一方面来说,其把自动配置类从spring.factories文件里解耦出来了
AutoConfigurationExcludeFilter
@SpringBootApplication有如下这个元注解,对包扫描器增加了AutoConfigurationExcludeFilter这个过滤器
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
AutoConfigurationExcludeFilter会自动排出满足一下任意条件的类:
- 有@AutoConfiguration注解
- SPI文件里的自动配置类
也就是说:所有使用 @AutoConfiguration 的类将被默认包扫描排除,不会参与常规的组件扫描逻辑
核心代码
private boolean isAutoConfiguration(MetadataReader metadataReader) {
// @AutoConfiguration注解是否存在
boolean annotatedWithAutoConfiguration = metadataReader.getAnnotationMetadata()
.isAnnotated(AutoConfiguration.class.getName());
return annotatedWithAutoConfiguration
|| getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
// spring.factories里的自动配置类
List<String> autoConfigurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader));
// .imports文件里的自动配置类
ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader).forEach(autoConfigurations::add);
this.autoConfigurations = autoConfigurations;
}
return this.autoConfigurations;
}
总结
从源码来反推@AutoConfiguration的使用场景,可以得出一个明确的使用约定:
@AutoConfiguration注解的类必须通过 SPI文件注册,不能依赖常规的包扫描导入。
无论该类是来自第三方 starter 还是当前项目模块,只要标注了 @AutoConfiguration,就不会被@SpringBootApplication 自动注册,而只能走自动配置的延迟导入机制(DeferredImportSelector)流程。
所以,在当前SpringBoot启动项目中,谨慎使用@AutoConfiguration,尽管它内部组合了 @Configuration,但它不等价于普通的配置类