本节目标:追踪
@EnableAutoConfiguration的完整链路,看懂自动配置清单文件与--debug报告,并掌握 4.0 模块化后的包名迁移规律。
适用版本:Spring Boot 4.1.x(Java 21)
上一节留下的问题
上一节我们确认了:把 @SpringBootApplication 拆开后,真正让 Tomcat 起来的那个注解是 @EnableAutoConfiguration。但它是怎么把 Tomcat 拉进来的?为什么加了 spring-boot-starter-webmvc 就是 Tomcat,换成别的依赖又可能变成 Jetty?要回答这些,得一路跟到底。
起点:@EnableAutoConfiguration 只是一个 @Import
它的定义非常短,核心就一行:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// exclude / excludeName 属性在这里
}
关键在 @Import(AutoConfigurationImportSelector.class)。Spring 的 @Import 允许导入一个「选择器」,由它在运行期决定到底要注册哪些配置类。所以 @EnableAutoConfiguration 本身不干活,它把决定权交给了 AutoConfigurationImportSelector。
AutoConfigurationImportSelector 做了什么
这个类实现了 ImportSelector 接口,Spring 在解析配置时会调用它的 selectImports 方法,拿回一个「类名数组」,这些类随后被当作配置类注册进容器。它内部的逻辑可以概括成三步:
- 从 classpath 上所有 jar 里,收集全部候选自动配置类的全限定名;
- 去重、排序(按
@AutoConfiguration的before/after与字母序); - 剔除掉你通过
exclude/excludeName显式排除的那些,返回剩下的。
第 1 步「从哪收集」是理解自动配置的关键——它读的不是代码,而是 classpath 上的一个清单文件。
清单文件:AutoConfiguration.imports
从 Spring Boot 2.7 起,自动配置类的登记位置从 spring.factories 迁移到了一个专用文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
这个文件在 3.x 和 4.x 里都是它,不再使用 spring.factories。 老教程里让你往 spring.factories 写 org.springframework.boot.autoconfigure.EnableAutoConfiguration=... 的写法,在 4.x 已经过时。文件内容极简,一行一个全限定类名:
org.springframework.boot.tomcat.autoconfigure.TomcatWebServerFactoryAutoConfiguration
org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration
org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration
对比一下两个时代的写法差异:
| 项目 | 2.6 及更早 | 2.7 / 3.x / 4.x |
|---|---|---|
| 登记文件 | META-INF/spring.factories | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
| 文件格式 | key=值1,值2,...(properties) | 一行一个类名(纯列表) |
| 是否需要 key | 需要 EnableAutoConfiguration= | 不需要,文件名即含义 |
| 读取实现 | SpringFactoriesLoader | ImportCandidates |
当你在自己的 starter 里新增自动配置时,也是往这个 .imports 文件里加一行(第 7 章讲自定义 starter 时会亲手写一遍)。
想亲眼看看这个文件,可以直接拆开一个模块 jar:
# 打印 jar 里清单文件的全部内容
unzip -p ~/.m2/repository/org/springframework/boot/spring-boot-tomcat/4.1.1/spring-boot-tomcat-4.1.1.jar \
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
# 或者先确认文件存在,再按需查看
jar tf spring-boot-tomcat-4.1.1.jar | grep AutoConfiguration.imports
输出的每一行,都是这个模块贡献给全局的自动配置类。把所有相关模块的清单合并起来,就是 AutoConfigurationImportSelector 拿到的候选集合。
Starter 如何把依赖变成自动配置
回到最初的问题:为什么加了 spring-boot-starter-webmvc 就有 Tomcat?把链路串起来:
spring-boot-starter-webmvc是一个空壳 pom,它自己不写代码,只负责传递依赖;- 它传递引入
spring-boot-tomcat等模块的 jar; - 这些 jar 里各自带着
META-INF/spring/...AutoConfiguration.imports清单; AutoConfigurationImportSelector汇总所有清单,得到候选自动配置类;- 每个候选类再经条件装配筛选,最终 Tomcat 相关配置生效。
对应关系一张表看清:
| 你写的依赖 | 传递引入的模块 | 清单里的候选 | 结果 |
|---|---|---|---|
spring-boot-starter-webmvc | spring-boot-tomcat | Tomcat 相关自动配置 | 内嵌 Tomcat 启动 |
换成 spring-boot-starter-jetty | spring-boot-jetty | Jetty 相关自动配置 | 内嵌 Jetty 启动 |
spring-boot-starter-data-jpa | spring-boot-jdbc 等 | 数据源 / 事务自动配置 | DataSource、JPA 就绪 |
所以「换容器只改依赖」能成立,正是因为清单与条件是按 classpath 内容判断的,而不是写死的。
4.0 模块化:自动配置类搬到了各模块自己的包下
这是 4.x 相对 3.x 最容易被老资料带偏的地方。4.0 把原来集中在 spring-boot-autoconfigure 一个模块里的自动配置类,拆分到了各技术模块自己的包里。
迁移规律很整齐:
| 维度 | 3.x 及更早 | 4.x |
|---|---|---|
| 模块名 | spring-boot-autoconfigure(集中) | spring-boot-<technology>(分散) |
| 根包 | org.springframework.boot.autoconfigure.* | org.springframework.boot.<technology>.* |
| 自动配置子包 | 无固定后缀 | org.springframework.boot.<technology>.autoconfigure |
| starter 名 | 混杂 | spring-boot-starter-<technology> |
以 Tomcat 为例,3.x 里它的自动配置类在 org.springframework.boot.autoconfigure.web.embedded.* 一带;4.x 里搬到了 org.springframework.boot.tomcat.autoconfigure.*。这个变化在启动日志里也有直接证据——Tomcat 相关日志的类名从 3.x 的 o.s.b.w.embedded.tomcat.TomcatWebServer 变成了 4.x 的:
2026-10-09T15:42:07.027+08:00 INFO 43496 --- [ main] o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2026-10-09T15:42:09.015+08:00 INFO 43496 --- [tomcat-shutdown] o.s.boot.tomcat.GracefulShutdown : Graceful shutdown complete
注意 o.s.boot.tomcat.TomcatWebServer 与 o.s.boot.tomcat.GracefulShutdown——org.springframework.boot.tomcat 这个包在 3.x 里根本不存在。看到这种包名,就能判断项目跑在 4.x 上。迁移旧项目时,如果你在代码里 import 过具体的自动配置类(例如为了 exclude),这些 import 路径必须跟着改,否则编译不过。
用 --debug 打印自动配置报告
自动配置是「按条件生效」的,光看代码猜不出某个配置到底有没有生效。最实用的手段是打开调试开关,让 Spring Boot 把评估结果全部打出来:
java -jar target/probe-0.0.1-SNAPSHOT.jar --debug
也可以在 application.properties 里写:
debug=true
启动日志里会出现一份 CONDITIONS EVALUATION REPORT,分两大块:Positive matches(生效了)与 Negative matches(没生效)。截取一段真实形态:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
TomcatWebServerFactoryAutoConfiguration matched:
- @ConditionalOnClass found required classes 'jakarta.servlet.Servlet',
'org.apache.catalina.startup.Tomcat' (OnClassCondition)
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource',
'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
Negative matches:
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'jakarta.jms.ConnectionFactory' (OnClassCondition)
Neo4jAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'org.neo4j.driver.Driver',
'org.springframework.data.neo4j.core.Neo4jOperations' (OnClassCondition)
读这份报告的方法:
Positive matches里有 Tomcat,没有 Jetty —— 说明你的 classpath 上有 Tomcat 的类、没有 Jetty 的类,条件判断直接决定了内嵌容器是谁。Negative matches的原因几乎都是@ConditionalOnClass did not find ...—— 没引对应依赖,配置自然不生效。ActiveMQ、Neo4j 你没用,所以它们待在 Negative 里,这是正常现象,不是错误。- 想找某个配置为什么没生效,直接在报告里搜它的类名,看它被卡在哪个条件上。
报告里还会出现另外几块,一并认识一下:
| 报告区块 | 含义 |
|---|---|
Positive matches | 条件全部成立、配置生效 |
Negative matches | 条件不成立、配置跳过 |
Exclusions | 被你用 exclude / excludeName 排除的 |
Unconditional classes | 无条件、必定生效的配置 |
排查时按这个顺序看:先在 Exclusions 确认你没有误排,再看 Negative matches 找没生效的原因,最后在 Positive matches 确认该生效的都生效了。--debug 报告是排查「属性配了却没反应」「bean 没创建」的第一现场,比在代码里到处打日志高效得多。
@AutoConfiguration:自动配置类的新标注
在 4.x 里,自动配置类不再用 @Configuration 标注,而用 @AutoConfiguration。它是 @Configuration(proxyBeanMethods = false) 的语义化封装,并额外支持声明加载顺序:
package com.example.probe.autoconfig;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(GreetingService.class)
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public GreetingService greetingService() {
return new GreetingService("hello from auto-config");
}
}
写完把它登记进清单文件:
com.example.probe.autoconfig.GreetingAutoConfiguration
@AutoConfiguration 还支持声明顺序,等价于旧版的 @AutoConfigureBefore / @AutoConfigureAfter:
@AutoConfiguration(before = DataSourceAutoConfiguration.class)
public class GreetingAutoConfiguration {
// 保证自己排在数据源自动配置之前
}
顺序很重要:如果你的 bean 依赖 DataSource,就必须排在 DataSourceAutoConfiguration 之后,否则条件判断时数据源还没创建。默认顺序按类名的字母序,一旦有依赖关系就必须显式声明,不能靠运气。
小结
@EnableAutoConfiguration的核心是@Import(AutoConfigurationImportSelector.class)。- 候选自动配置类来自 classpath 上的
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,3.x 与 4.x 都是这个文件,不是spring.factories。 - 4.0 模块化后,自动配置类搬到各模块自己的包下,规律是
org.springframework.boot.<technology>.autoconfigure.*;Tomcat 相关就在org.springframework.boot.tomcat.autoconfigure.*。 --debug打印的CONDITIONS EVALUATION REPORT里,Positive matches/Negative matches是排查自动配置的第一现场。- 自动配置类用
@AutoConfiguration标注,可用before/after声明顺序。
但清单里的类有一大堆,为什么 DataSourceAutoConfiguration 平时不会乱建数据源?为什么你自己定义一个同类型的 bean,默认配置就自动让位?这些「该不该生效」的判断,靠的是条件装配——下一节的主角。
阅读导航:上一节:4.1 @SpringBootApplication 拆解 · 下一节:4.3 条件装配与开关 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。