《Spring Boot 高级》2.1 自动配置类的加载顺序

从 spring.factories 到 AutoConfiguration.imports,逐层拆开 Spring Boot 4 自动配置类的发现、过滤与排序:AutoConfigurationSorter 如何在 @AutoConfigureBefore/@AutoConfigureAfter 与 @AutoConfigureOrder 之上做拓扑排序,以及为何还要用全限定名做兜底。

本节目标:讲清 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 MVCo.s.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationo.s.boot.webmvc.autoconfigure.WebMvcAutoConfiguration
内嵌 Tomcato.s.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizero.s.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration
Jacksono.s.boot.autoconfigure.jackson.JacksonAutoConfigurationo.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

两个点值得展开:

  1. 它是 DeferredImportSelector(延迟导入选择器)。普通 @Import 在解析当前配置类时立即处理,而延迟导入会被攒到所有配置类解析完之后、由 ConfigurationClassParser 的 DeferredImportSelectorGrouping 统一处理。这正是自动配置「最后装配、用户配置优先」的机制根源:用户自己的 @Bean 先被登记,自动配置的 @ConditionalOnMissingBean 才可能在它之后评估并退让。
  2. 它的 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 里 AutoConfigurationImportFilter key 下的三个条件类——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 家族实现 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计