《Spring Boot 入门》4.2 自动配置如何生效

顺着 @EnableAutoConfiguration 一路追到 AutoConfigurationImportSelector 与 AutoConfiguration.imports 清单文件,讲清 3.x/4.x 的加载路径、4.0 模块化后包名的迁移规律,并用 --debug 打印的自动配置报告解读 Positive/Negative matches。

本节目标:追踪 @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 方法,拿回一个「类名数组」,这些类随后被当作配置类注册进容器。它内部的逻辑可以概括成三步:

  1. 从 classpath 上所有 jar 里,收集全部候选自动配置类的全限定名;
  2. 去重、排序(按 @AutoConfiguration 的 before / after 与字母序);
  3. 剔除掉你通过 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.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件格式key=值1,值2,...(properties)一行一个类名(纯列表)
是否需要 key需要 EnableAutoConfiguration=不需要,文件名即含义
读取实现SpringFactoriesLoaderImportCandidates

当你在自己的 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?把链路串起来:

  1. spring-boot-starter-webmvc 是一个空壳 pom,它自己不写代码,只负责传递依赖;
  2. 它传递引入 spring-boot-tomcat 等模块的 jar;
  3. 这些 jar 里各自带着 META-INF/spring/...AutoConfiguration.imports 清单;
  4. AutoConfigurationImportSelector 汇总所有清单,得到候选自动配置类;
  5. 每个候选类再经条件装配筛选,最终 Tomcat 相关配置生效。

对应关系一张表看清:

你写的依赖传递引入的模块清单里的候选结果
spring-boot-starter-webmvcspring-boot-tomcatTomcat 相关自动配置内嵌 Tomcat 启动
换成 spring-boot-starter-jettyspring-boot-jettyJetty 相关自动配置内嵌 Jetty 启动
spring-boot-starter-data-jpaspring-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 条件装配与开关 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「java」更多文章

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