本节目标:讲清 Spring Boot 4 在启动阶段如何发现、过滤并给自动配置类排序,让你能读懂
--debug报告里的顺序,也能写出行为可预测的自定义自动配置。
适用版本:Spring Boot 4.1.x(Java 21)
实战卷解决的是「加哪个 starter、配哪个属性」;本节解决的是「这些 starter 里的自动配置类,凭什么按某个顺序被加载」。顺序不是学术问题:当两个自动配置类都想声明同名的 Bean 时,谁先注册决定了谁会被 @ConditionalOnMissingBean 让位。
注册文件:spring.factories 让位给 AutoConfiguration.imports
自动配置类本身只是普通的配置类,真正让它们「自动生效」的是被某个机制登记在册。Spring Boot 4 里这份登记册是 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,一行一个全限定类名。
# 本机 4.1.1 实测:核心 autoconfigure 模块里同时存在两个文件
unzip -l ~/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/4.1.1/spring-boot-autoconfigure-4.1.1.jar \
| rg 'AutoConfiguration.imports|spring.factories'
1529 02-01-1980 00:00 META-INF/spring.factories
921 02-01-1980 00:00 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
关键点在于:spring.factories 里已经没有 org.springframework.boot.autoconfigure.EnableAutoConfiguration 这个 key 了。把 4.1.1 的 spring.factories 整个打印出来,剩下的只有这些用途:
unzip -p ~/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/4.1.1/spring-boot-autoconfigure-4.1.1.jar \
META-INF/spring.factories
# ApplicationContext Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializingApplicationListener
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# 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
# Background Preinitializers
org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializer=\
org.springframework.boot.autoconfigure.preinitialize.CharsetsBackgroundPreinitializer,\
org.springframework.boot.autoconfigure.preinitialize.ConversionServiceBackgroundPreinitializer
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.ssl.BundleContentNotWatchableFailureAnalyzer
这次演进有三个明确节点,官方 release notes 记录得很清楚:
| 版本 | 注册方式 | 状态 |
|---|---|---|
| 2.6 及更早 | 只认 spring.factories 的 EnableAutoConfiguration= key | 已废弃 |
| 2.7 | 新增 AutoConfiguration.imports,同时保留 spring.factories 兼容 | 过渡 |
| 3.0 | 移除 spring.factories 注册自动配置的能力 | 只剩 imports |
| 4.x | 只有 AutoConfiguration.imports;spring.factories 只服务 initializer/listener/filter | 现行 |
spring.factories 用 EnableAutoConfiguration= 加逗号分隔的类名,一行能塞几十个;AutoConfiguration.imports 改成一行一个类名、文件路径与注解同名。这个改动的动机是可读性与工具友好:自动配置清单从此可以被独立解析、diff、被 IDE 索引,而不必去解析一个混合了各种 key 的 properties 文件。
4.x 模块化:清单被拆到每个模块里
4.0 最大的破坏性变更是模块化重构:自动配置类不再全部堆在 spring-boot-autoconfigure 一个 jar 里,而是跟着各自的模块走。核心 spring-boot-autoconfigure-4.1.1.jar 的 imports 只剩 12 行,都是框架级、与技术无关的自动配置:
unzip -p ~/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/4.1.1/spring-boot-autoconfigure-4.1.1.jar \
'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports'
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
其余模块各自携带自己的清单与自己的包名。这是本节最值得记住的「包路径对照」:
| 关注点 | 3.x 的类与包 | 4.1.1 的类与包 |
|---|---|---|
| Web MVC | o.s.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration | o.s.boot.webmvc.autoconfigure.WebMvcAutoConfiguration |
| 内嵌 Tomcat | o.s.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer | o.s.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration |
| Jackson | o.s.boot.autoconfigure.jackson.JacksonAutoConfiguration | o.s.boot.jackson.autoconfigure.JacksonAutoConfiguration |
# 实测:每个模块各带一份同名 imports 文件
unzip -p ~/.m2/repository/org/springframework/boot/spring-boot-webmvc/4.1.1/spring-boot-webmvc-4.1.1.jar \
'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports'
org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration
org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration
org.springframework.boot.webmvc.autoconfigure.WebMvcObservationAutoConfiguration
org.springframework.boot.webmvc.autoconfigure.actuate.endpoint.web.WebMvcHealthEndpointExtensionAutoConfiguration
org.springframework.boot.webmvc.autoconfigure.actuate.web.mappings.WebMvcMappingsAutoConfiguration
org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration
对读者最直接的影响:在 4.x 里写自定义自动配置,包名要放在自己的模块命名空间下(org.springframework.boot.<technology>.autoconfigure),清单文件放在本模块的 src/main/resources/META-INF/spring/。聚合器(spring-boot-autoconfigure)不再替你登记。
@AutoConfiguration 的语义
清单里的类用 @AutoConfiguration 标注,而不是裸 @Configuration。用 javap 看它的成员:
export JAVA_HOME=/tmp/springboot_book/jdk-21.0.12.1+1/Contents/Home
AC=~/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/4.1.1/spring-boot-autoconfigure-4.1.1.jar
"$JAVA_HOME/bin/javap" -cp "$AC" org.springframework.boot.autoconfigure.AutoConfiguration
public interface org.springframework.boot.autoconfigure.AutoConfiguration extends java.lang.annotation.Annotation {
public abstract java.lang.String value();
public abstract java.lang.Class<?>[] before();
public abstract java.lang.String[] beforeName();
public abstract java.lang.Class<?>[] after();
public abstract java.lang.String[] afterName();
}
从 javap -v 的常量池可以看到它被 @Configuration(proxyBeanMethods = false)、@AutoConfigureBefore、@AutoConfigureAfter 元注解修饰,before()/after() 通过 @AliasFor 映射到后两者。也就是说:
- 它本质是一个
@Configuration,但proxyBeanMethods = false——自动配置类不会被 CGLIB 代理,内部@Bean方法互调不会走容器,因此不能靠方法调用共享单例。这是刻意的:自动配置类数量大,逐个代理会拖慢启动。 before()/after()接受Class或String(beforeName/afterName),后者用于避免对另一个类的编译期依赖——你只想声明相对顺序,不想把对方 jar 拉进编译 classpath。
一个真实例子(javap -v 读出的类级注解):
@AutoConfiguration(
after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class },
afterName = "org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration")
@ConditionalOnWebApplication(type = SERVLET)
@ConditionalOnClass({ jakarta.servlet.Servlet.class, DispatcherServlet.class })
public final class WebMvcAutoConfiguration { ... }
注意 afterName 指向的 ValidationAutoConfiguration 在 spring-boot-validation 模块里:Web MVC 需要在校验器就绪之后再装配,但它并不想为此硬依赖校验模块,于是用字符串声明顺序。
AutoConfigurationSorter:三级排序
候选清单收集完只是「一堆类名」,顺序由 AutoConfigurationSorter 决定。它是包级可见类:
"$JAVA_HOME/bin/javap" -cp "$AC" org.springframework.boot.autoconfigure.AutoConfigurationSorter
class org.springframework.boot.autoconfigure.AutoConfigurationSorter {
org.springframework.boot.autoconfigure.AutoConfigurationSorter(
org.springframework.core.type.classreading.MetadataReaderFactory,
org.springframework.boot.autoconfigure.AutoConfigurationMetadata,
java.util.function.UnaryOperator<java.lang.String>);
java.util.List<java.lang.String> getInPriorityOrder(java.util.Collection<java.lang.String>);
}
getInPriorityOrder 内部对每个类构造一个 AutoConfigurationClass(读取它的 @AutoConfigureOrder、@AutoConfigureBefore、@AutoConfigureAfter),然后按下面的优先级排:
| 优先级 | 依据 | 语义 | 谁用 |
|---|---|---|---|
| 1 | @AutoConfigureOrder(int) | 绝对顺序,值小者靠前 | 极少数需要压过一切的类 |
| 2 | @AutoConfigureBefore / @AutoConfigureAfter | 相对顺序,声明「我在谁之前/之后」 | 绝大多数自动配置 |
| 3 | 全限定类名的字典序 | 兜底 | 没有声明任何相对关系的类 |
第三级兜底经常被忽略,但它非常重要:AutoConfigureBefore/After 是偏序,一堆互不相关的自动配置之间没有边,拓扑排序结果本来是不确定的。若不做兜底,同样的 jar 在不同 JVM 上可能得到不同的加载顺序,条件评估(尤其 @ConditionalOnMissingBean)的结果就会漂移。用全限定名做最终 tie-break,把「未声明」变成「确定」,让构建与运行可复现。
AutoConfigurationSorter 的实现要点(从 javap -c 的调用点可见)是:先 sortByAnnotation 处理绝对顺序,再 doSortByAfterAnnotation 递归展开 after 关系并借 getClassesRequestedAfter 检测环,最后用 Comparator.comparing(...) 收尾。环检测是必要的——@AutoConfigureAfter 写错方向(A after B、B after A)会直接抛异常而不是静默死循环。
从候选清单到生效:AutoConfigurationImportSelector
@EnableAutoConfiguration(被 @SpringBootApplication 组合)真正干活的是一段 @Import:
@Import(AutoConfigurationImportSelector.class) // 见 @EnableAutoConfiguration 的元注解
AutoConfigurationImportSelector 的身份很关键:
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered
两个点值得展开:
- 它是
DeferredImportSelector(延迟导入选择器)。普通@Import在解析当前配置类时立即处理,而延迟导入会被攒到所有配置类解析完之后、由ConfigurationClassParser的DeferredImportSelectorGrouping统一处理。这正是自动配置「最后装配、用户配置优先」的机制根源:用户自己的@Bean先被登记,自动配置的@ConditionalOnMissingBean才可能在它之后评估并退让。 - 它的
getOrder()返回Integer.MAX_VALUE - 1(实测javap -c显示常量2147483646),即最低优先级。多个DeferredImportSelector同时存在时,自动配置永远排在最后。
"$JAVA_HOME/bin/javap" -c -p -cp "$AC" org.springframework.boot.autoconfigure.AutoConfigurationImportSelector \
| rg -A3 'public int getOrder'
public int getOrder();
Code:
0: ldc_w #421 // int 2147483646
3: ireturn
getImportGroup() 返回 AutoConfigurationImportSelector$AutoConfigurationGroup,由它统一 process(...) 收集候选、去重、排除,再 selectImports() 交回容器。收集候选的实现是 getCandidateConfigurations(...),它读取的正是各模块 imports 文件里的类名。
在「候选 → 最终导入」之间还有两道关卡:
- 排除:
getExclusions(...)汇总@EnableAutoConfiguration(exclude/excludeName)与属性spring.autoconfigure.exclude(见 2.3)。 - 过滤:
getAutoConfigurationImportFilters()取出注册在spring.factories里AutoConfigurationImportFilterkey 下的三个条件类——OnClassCondition、OnBeanCondition、OnWebApplicationCondition。它们以批量方式对候选做快速筛选(FilteringSpringBootCondition.match(String[], ...)),目的是在真正解析配置类之前,就把 classpath 上明显不成立的自动配置剔掉,避免昂贵的类元数据读取。
你能做什么
读 --debug 报告。--debug 会打开 ConditionEvaluationReportLogger,把条件评估结果按 Positive matches: / Negative matches: / Exclusions: / Unconditional classes: 四段打印。看顺序时关注 Positive 段的先后:它就是 AutoConfigurationSorter 排出来的实际顺序。
java -jar app.jar --debug
# 或只开这一个 logger,避免全量 debug 日志刷屏
java -jar app.jar --logging.level.org.springframework.boot.autoconfigure=DEBUG
ConditionEvaluationReportLogger 的源码约束(实测字符串):日志级别只接受 INFO 或 DEBUG,否则报 'logLevel' must be INFO or DEBUG。所以别把它设成 TRACE。
验证顺序。写一个测试,注入 ConfigurableListableBeanFactory,通过 ConditionEvaluationReport.get(...) 拿报告,再断言两个自动配置类的 getConditionAndOutcomesBySource() 里的出现顺序;或直接在自定义自动配置上打日志、看启动时谁先打印。
写自己的自动配置时:
- 类放
org.springframework.boot.<yourtech>.autoconfigure包,加@AutoConfiguration(不要裸@Configuration)。 - 在本模块
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里登记全限定名。 - 需要顺序时优先用
after = XxxAutoConfiguration.class;若目标类不在你的编译 classpath,用afterName = "全限定名"。 - 不要用
@AutoConfigureOrder去抢绝对顺序,除非你确实要压过框架自己的编排;它容易被上游的调整打破。
常见坑:
- 把自动配置类写成裸
@Configuration:仍可能生效(只要被 imports 登记),但会失去before/after的排序语义与proxyBeanMethods=false的启动优化。 - 指望 imports 文件里的书写顺序就是加载顺序:文件顺序只是候选顺序,最终顺序由
AutoConfigurationSorter重排。 - 在 4.x 里沿用 3.x 的包名
o.s.boot.autoconfigure.web.servlet.*:模块化后这些类不在原来的 jar 里。
小结
- 4.x 的自动配置注册只有一处:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports;spring.factories自 3.0 起不再承载EnableAutoConfiguration=。 - 4.0 模块化后,每个
spring-boot-<technology>模块各带一份清单与自己的包名,核心 jar 只剩 12 个框架级自动配置。 @AutoConfiguration=@Configuration(proxyBeanMethods = false)+@AutoConfigureBefore/After元注解;beforeName/afterName用于零编译依赖地声明顺序。- 顺序由
AutoConfigurationSorter.getInPriorityOrder决定:@AutoConfigureOrder→@AutoConfigureBefore/After拓扑排序 → 全限定名字典序兜底。兜底是为了让未声明关系的类也有确定、可复现的顺序。 AutoConfigurationImportSelector是最后处理的DeferredImportSelector(getOrder()=Integer.MAX_VALUE - 1),先排除、再经三个AutoConfigurationImportFilter批量过滤,最后交给AutoConfigurationGroup。
下一节拆开这套过滤里最关键的一环:@Conditional 家族各自在什么时机、用什么数据做出判断。
阅读导航:上一节:1.3 依赖解析与循环依赖 · 下一节:2.2 @Conditional 家族实现 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。