自动配置原理:从 spring.factories 到 AutoConfiguration.imports
前两篇你已经会「跑起来」和「拆开那个三合一注解」了。这一篇回答最容易卡住的那个问题:为什么我只加了一行依赖,数据源、事务管理器、JSON 转换器就全都有了?答案不是魔法,而是一条很朴素的流水线:jar 包里预先放着一份候选清单,启动时 Boot 把清单读出来,逐个问几个「你配了吗 / 你有这个类吗」的问题,只把通过的那些注册进容器。整篇文章就是把这条流水线拆成四段看清楚,再教你一件事——它没生效时,怎么让 Boot 自己开口告诉你为什么。
精装房的水电家具都是提前配好的,但你一旦把自己的沙发摆进客厅,装修方就不会再往那儿放它的沙发。自动配置就是这套「先看你有没有安排,没有才补」的规矩:@ConditionalOnMissingBean——客人没带伞,店里才借一把;你自己带了伞,店员看一眼就把伞架收回去。所以下面第二节才会反复强调「延迟导入」:Boot 一定要等你把话说完,才决定要不要替你配。

学完这一篇,你应该能回答三个问题:
- 候选清单从哪个文件读来?
spring.factories和AutoConfiguration.imports差在哪个版本? - 一个自动配置类要满足哪些条件才会真的生效?被跳过时我能从哪里看到原因?
- 我想换掉 Boot 配好的
ObjectMapper,正确做法是什么?为什么不该打开覆盖开关?
新建一个 Spring Boot 项目,在 pom.xml 里只加了一行依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>application.yml 里只写了三四行数据库地址,什么 DataSource、EntityManagerFactory、PlatformTransactionManager 一律没配。可是启动之后,这些 Bean 全都好好地在容器里待着——@Autowired 一个 DataSource 就能直接用。
这里有个很值得追问的点:Spring 的 IoC 容器只会造「你告诉它的东西」,它不会凭空变出一个连接池。那这些 Bean 是谁注册进去的?
答案就是自动配置。自动配置不是魔法,它是一批被提前写进 jar 包里、只有满足条件才会生效的配置类。本文要做的,就是把「谁读了哪个文件、谁判断了条件、谁最后把 Bean 塞进容器」这条链路完整拆一遍。
「一行依赖换来一屋子 Bean」这件事,最好自己勾一遍看。生成器与课文同源:先只勾 Data JPA,看它一个人就拖来了 spring-orm、hibernate-core、spring-boot-starter-jdbc;再把 MySQL 驱动 与 H2 叠上去,注意两个驱动同时在 classpath 时会发生什么——那正是第十三节沙盘与第十四节那段报错的现场;最后加 RabbitMQ,勾完就把它取消,你会在第 ② 步的实验里看到它被哪一道闸砍掉。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version> <!-- 版本由 BOM 统管,子依赖不写 version -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
一切的起点,是上一篇里 @EnableAutoConfiguration 上的那一行 @Import:
@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { }真正干活的是 AutoConfigurationImportSelector。它的骨架大致长这样:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; // spring.boot.enableautoconfiguration=false 时直接返回空 } AutoConfigurationEntry entry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(entry.getConfigurations()); } @Override public Class<? extends Group> getImportGroup() { return AutoConfigurationGroup.class; // 分组=延迟导入,保证排在用户配置之后 }}DeferredImportSelector:注意「Deferred(延迟)」。普通ImportSelector立刻被处理,而它被推迟到所有用户自己的@Configuration都处理完之后才执行。这一步至关重要,第七节会看到它换来了什么selectImports:整个流程的入口。先判断开关是否打开,再向getAutoConfigurationEntry要候选清单getAutoConfigurationEntry:内部依次做「取候选 → 去重 → 过滤排除 → 条件裁剪」,最终返回一份「该导入的配置类」数组
把 getAutoConfigurationEntry 展开,一次完整的加载是这样一个序列:
getCandidateConfigurations()拿到全部候选自动配置类(就是 jar 里清单文件的那一份)- 去掉重复项(多个 jar 可能重复声明),并剔除用户通过
exclude/excludeName/spring.autoconfigure.exclude指定的项 AutoConfigurationImportFilter先做一轮快速筛选(例如OnClassCondition直接看 classpath,能一次性踢掉大批不相关的配置类,省下解析开销)- 剩下的类交给
ConfigurationClassParser逐个解析 - 解析时,类上和方法上的
@Conditional条件被ConditionEvaluator逐条求值 - 通过者注册为
BeanDefinition;未通过者被记进ConditionEvaluationReport - 容器
refresh(),从这些BeanDefinition实例化出真正的 Bean
注意链路里有两道筛选。第一道是 AutoConfigurationImportFilter,它不解析类、只看 classpath,作用是「快速砍掉」,属于性能优化;第二道是条件求值,它才会真正读注解、判断 Bean 是否存在。理解这两层的分工,你就能明白为什么一个「类路径没有 RabbitMQ」的项目,启动时不会为 Rabbit 相关配置类付出解析代价。
这两道闸长得不一样,能力也不一样,这张图把它们并排放:

看右边那一列的最后一行——只有第二道闸会留下「为什么」。所以「为什么不生效」这个问题,永远只能在 --debug 报告里找,而不是在候选清单阶段找;第一道闸砍掉的东西连名字都不会出现在报告里,这是新手最常见的「报告里搜不到它」的真正原因(第十三节第四格会再遇到一次)。
「候选清单」到底从哪来?这里有一个版本分水岭。Spring Boot 2.7 之前用 spring.factories,2.7 起引入新写法,3.0 彻底移除旧机制。
| 维度 | spring.factories(≤ 2.7) | AutoConfiguration.imports(2.7+) |
|---|---|---|
| 文件路径 | META-INF/spring.factories | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
| 文件格式 | properties:一个 key 映射一串类名 | 纯文本:一行一个类名 |
| 定位方式 | 靠 key org.springframework.boot.autoconfigure.EnableAutoConfiguration | 文件名本身就是 key,不需要额外声明 |
| 加载 API | SpringFactoriesLoader.loadFactoryNames | ImportCandidates.load(AutoConfiguration.class, ...) |
| 现状 | 2.7 起弃用,3.0 起失效 | 现行标准 |
旧的写法长这样,注意那串靠反斜杠续行、逗号分隔的类名:
# META-INF/spring.factories(旧)org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.demo.autoconfigure.MyServiceAutoConfiguration,\com.example.demo.autoconfigure.OtherAutoConfiguration新的写法则干净得多,一行一个类,没有再声明 key 的必要:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(新)com.example.demo.autoconfigure.MyServiceAutoConfigurationcom.example.demo.autoconfigure.OtherAutoConfiguration坑:从 2.7 升级到 3.0 时,如果你的自定义 starter 还在用 spring.factories 注册自动配置,升级后会整批静默失效——不报错,只是那些自动配置类再也不会被加载。升级清单里务必搜一遍 spring.factories。顺带一提,spring.factories 在 3.0 里并没有被完全删除,它仍然承载「监听器、初始化器」等其它扩展点,被移走的只是自动配置这一项。
链路讲完,来看一个真实选手。Spring Boot 自带的 DataSourceAutoConfiguration(做了必要精简)几乎用全了自动配置的每个关键注解:
@AutoConfiguration(before = HibernateJpaAutoConfiguration.class)@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")@EnableConfigurationProperties(DataSourceProperties.class)@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class })public class DataSourceAutoConfiguration { @Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class }) protected static class PooledDataSourceConfiguration { }}逐个注解解释,这才是读自动配置源码的正确姿势:
@AutoConfiguration:新式自动配置标记。它等价于@Configuration(proxyBeanMethods = false),但额外获得排序能力(before/after)。老的自动配置类用的是@Configuration,新版应该统一换成它before = HibernateJpaAutoConfiguration.class:声明「我必须排在 JPA 自动配置之前」。因为 JPA 需要 DataSource,顺序错了 JPA 就找不到数据源——这正是第五节的主题@ConditionalOnClass({ DataSource.class, ... }):类路径上必须存在这些类。没有spring-jdbc时,整个配置类直接跳过,容器里也就不会有 DataSource@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory"):如果项目用的是响应式 R2DBC,就让 R2DBC 那边负责,这里主动让路@EnableConfigurationProperties(DataSourceProperties.class):把spring.datasource.*前缀的配置项绑定成一个 Java 对象,供下面的@Bean方法使用@Import(...):继续导入更细粒度的配置。所以自动配置是分层嵌套的:外层判断「要不要配数据源」,内层判断「用哪种连接池」
自动配置类之间不是平级的——它们有依赖关系。最典型的一对:DataSourceAutoConfiguration 必须在 HibernateJpaAutoConfiguration 之前。如果 JPA 先跑,它去容器里找 DataSource 时还没被注册,就会报「找不到数据源」启动失败。
Spring Boot 提供了三种表达顺序的方式:
// 方式一:@AutoConfiguration 上的 before / after(最常用,成对声明依赖)@AutoConfiguration(before = HibernateJpaAutoConfiguration.class)public class DataSourceAutoConfiguration { }// 方式二:类的 after —— 等对方先跑完@AutoConfiguration(after = DataSourceAutoConfiguration.class)@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class })public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { // 到这里可以安全假设 DataSource 已经就绪}// 方式三:@AutoConfigureOrder —— 给一个绝对数字,越小越靠前@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)public class MyEarlyAutoConfiguration { }before/after是相对声明,表达「我和谁有依赖」。它们不允许成环,成环会在启动时报错@AutoConfigureOrder是绝对声明,用一个数字定顺序。它更「强硬」但也更容易写出难以维护的魔法数字,官方推荐优先用 before/after- 兜底规则:
AutoConfigurationSorter在处理完所有 before/after 之后,会把剩下没有任何依赖关系的类按类名字母序(A→Z)排列。这一步保证排序是确定性的——同样的清单每次启动顺序一致,不会因为 JVM 差异而抖动
要点:面试问「自动配置类的顺序是怎么定的」,标准答案是三层——先按 before/after 解出依赖顺序,没有依赖关系的用 @AutoConfigureOrder 的数字,最后剩下的按字母序兜底。三句话答全,基本就满分了。
这三层不是三条并列规则,而是一条按顺序落水的流水线:前一层能定的,后一层就不再有发言权。点着走一遍,重点看第 ④ 格——它决定「同样的清单每次启动顺序一致」这件事到底靠什么:
自动配置类之所以能「智能地」判断该不该生效,全靠条件注解。下面这张表把最常用的一批集中列出,下一篇会逐个讲透:
| 条件注解 | 判定依据 | 典型用途 |
|---|---|---|
@ConditionalOnClass | 类路径存在指定类 | 有依赖才装配(如检测到 HikariCP) |
@ConditionalOnMissingClass | 类路径不存在指定类 | 排除某个实现 |
@ConditionalOnBean | 容器内已有指定类型的 Bean | 依赖另一个 Bean 才能装配 |
@ConditionalOnMissingBean | 容器内没有指定类型的 Bean | 用户没配时的兜底 |
@ConditionalOnSingleCandidate | 该类型只有一个候选 Bean | 唯一数据源、唯一事务管理器 |
@ConditionalOnProperty | 配置项等于 / 不等于某值 | 功能开关(如 spring.aop.auto) |
@ConditionalOnWebApplication | 当前是 Web 应用 | Web 相关的自动配置 |
@ConditionalOnExpression | SpEL 表达式为真 | 复杂的组合条件 |
这张表先当「字典」用。真正难的是它们之间的求值顺序——比如 @ConditionalOnBean 依赖「别人已经注册好了」,那它凭什么保证别人先注册?这正是下一篇要回答的核心问题。
自动配置「没生效」时,最不该做的就是瞎猜。Spring Boot 内置了一份条件评估报告,会把你项目里每个自动配置类的判定结果和原因全部打印出来。开启方式三选一:
# 方式一:启动参数java -jar app.jar --debug# 方式二:配置文件debug: true# 方式三:只打开自动配置包的日志logging: level: org.springframework.boot.autoconfigure: DEBUG打开后,控制台会有一段报告,核心是两个小节——「谁生效了」和「谁被跳过了、为什么」:
============================CONDITIONS EVALUATION REPORT============================Positive matches:----------------- DataSourceAutoConfiguration matched: - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition) - @ConditionalOnMissingBean (types: io.r2dbc.spi.ConnectionFactory) did not find any beans (OnBeanCondition) - @AutoConfigureBefore matched: HibernateJpaAutoConfiguration (AutoConfigureOrder)Negative matches:----------------- RabbitAutoConfiguration matched: - @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel' (OnClassCondition)Exclusions:----------- DataSourceAutoConfiguration (excluded by @SpringBootApplication exclude)- Positive matches:这些配置类通过了条件,真的生效了。逐条列出它通过了哪些判断,等于告诉你「这些 Bean 是它给的」
- Negative matches:这些被跳过,原因写在括号里。排查自动配置问题,80% 的时间是在看这一段——比如你发现
RabbitAutoConfiguration因为「找不到com.rabbitmq.client.Channel」被跳过,那根因就是依赖没加全 - Exclusions:被你主动排除的条目,会在这里单独列出,确认你的 exclude 生效了
说明:报告的源头是 ConditionEvaluationReport 对象。它记录了每一次条件求值的结果,--debug 只是把这份对象渲染成可读文本。理解这一点,你就知道这份报告不是「日志」,而是容器内部决策过程的快照。

三种开启方式里,第一种(--debug 启动参数)只适合本地临时看一次;真正排查线上问题时你需要的是能按环境切换的第二、第三种。所以下面这份 yml 请这样勾:先开「日志」,把自动配置包的 DEBUG 单独调出来——这比整体 debug: true 温和得多,也不会把别的信息淹掉;再叠「数据源」,你会看到那三行 url/username/driver 正是第八节和第十四节报错的解药;最后加「Profile」,把上面两组搬进分文档,得到「开发环境吵、生产环境安静」的那份配置。
server:
port: 8080
spring:
application:
name: demo-service
logging:
level:
root: INFO
com.example.demoservice: DEBUG
org.springframework.jdbc.core.JdbcTemplate: DEBUG # 打 SQL 与参数
file:
name: logs/app.log
logback:
rollingpolicy: { max-file-size: 50MB, max-history: 14 }
自动配置是「你没配时兜底」,那你想自己接管的正确做法是什么?先看一个错误的示范:有人为了让自己的 Bean 生效,去打开这个开关——
# ❌ 不推荐:打开 BeanDefinition 覆盖spring.main.allow-bean-definition-overriding=trueSpring Boot 2.1 起默认 不允许 覆盖(false)。打开它看似能「覆盖一切」,实则把「最终谁生效」变成了一个取决于注册顺序的问题:自动配置类和你的类谁先注册,是不确定的,于是你可能得到自己的实现,也可能得到 Boot 的默认实现——一个极难复现的隐性 bug。
正确姿势是利用自动配置主动让路的机制。因为绝大多数自动配置类都带着 @ConditionalOnMissingBean:
@Configurationpublic class MyDataSourceConfig { // 只要你定义了 DataSource,DataSourceAutoConfiguration 就会因为 // @ConditionalOnMissingBean 而主动退让,不会和你的 Bean 冲突 @Bean @ConfigurationProperties("spring.datasource.hikari") public DataSource dataSource(DataSourceProperties props) { return props.initializeDataSourceBuilder() .type(HikariDataSource.class) .build(); }}为什么这样就能生效?关键还在第二节埋的那个伏笔——AutoConfigurationImportSelector 是 DeferredImportSelector,它被推迟到所有用户配置都处理完之后才执行。于是当自动配置类求值 @ConditionalOnMissingBean 时,你定义的那个 DataSource 早已注册完毕,条件自然判定为「已存在」,自动配置随即让路。延迟导入,就是「用户优先」的技术保障。
allow-bean-definition-overriding=true 是一种「掩盖冲突」而非「解决冲突」。它让你以为覆盖成功,实际上埋下了顺序依赖的雷。自定义 Bean 要覆盖自动配置,请优先依赖 @ConditionalOnMissingBean 的让路机制;只有在不方便定义同类型 Bean 时(例如想改的是 Builder 而不是最终对象),才改用官方提供的 Customizer 扩展点。
「延迟导入 → 你先注册 → 它才求值 → 它让路」这条因果链,就是下面这六帧:

再把它摊成一次单步执行。左边是那六行真正发生的事情,右边同步刷新「此刻容器里有没有 dataSource」和「谁在求值」——重点在第 ③ 步与第 ④ 步:那半秒钟的先后关系,就是「用户优先」的全部技术含量:
// 容器解析你的 MyDataSourceConfig(普通 @Configuration,不延迟)// @Bean dataSource 的 BeanDefinition 注册完毕,此刻对象还没造// DeferredImportSelector 终于被执行:自动配置候选清单进场// DataSourceAutoConfiguration 求值 @ConditionalOnMissingBean(DataSource.class)// 条件命中「已经有一个了」→ false,整个内层配置不注册// refresh() 实例化:容器里唯一的 DataSource 是你那个 HikariDataSource| 正在处理 | MyDataSourceConfig |
| 属于哪一批 | 用户自己的配置 |
| 延迟队列 | 还没启动 |
ConfigurationClassParser.parse普通 @Configuration 分支下面这个演示把自动配置的条件求值过程做成可切换的。试着切换三个条件开关,观察每一个自动配置类对应的 Bean,是在什么条件下被创建、又在什么条件下被整批跳过:
按钮按完,换成命令行——本篇的核心问题「它到底生没生效、为什么没生效」在这台容器里正好有三个命令能直接回答:conditions 打印条件求值记录,cond 手动扳动某个条件开关,restart 让整条装配链重新走一遍。
按这个顺序敲,第 4 步与第 5 步的差别就是「第一道闸」和「第二道闸」的差别:
boot—— 建容器,看装配日志conditions—— 谁在正选、谁在落选,落选原因写在括号里cond jdbcOnClasspath false—— 把 JDBC 从 classpath 上「拿掉」restart之后再conditions—— 找DataSourceAutoConfiguration:它这次还在报告里吗?cond userDataSource true—— 模拟你自己定义了一个 DataSource,再restartlab autoconf filter与lab autoconf report—— 粗筛那一步砍掉多少,报告里又留下多少
第 3 步与第 5 步的现象很像,答案却完全不同。jdbcOnClasspath false 让配置类从粗筛阶段就消失,所以报告里搜不到它的名字;userDataSource true 时它照样出现在落选段,括号里写着 OnBeanCondition——这正是第二节那张双闸图给出的两个可验证预言。
第二节的七步序列是文字版。下面这个实验把它变成可以按段推进的画面——autoconf 场景模拟的就是自动配置装配链,五个按钮正好对应「清单 → 过滤 → 排序 → 生效 → 报告」:
- 候选清单从哪来(imports):扫描 classpath 上所有 jar 的
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,把里面的全限定类名合并成一份大清单。这一步只做 IO,不看任何条件,所以数量会很吓人(内置就有 130+ 条) - 条件过滤(filter):
AutoConfigurationImportFilter先粗筛一轮。你会看到大批配置类因为 classpath 上缺类而被一次性踢掉——这就是第七节说的「快刀」,它不解析注解 - 排序与分组(sort):剩下的交给
AutoConfigurationSorter:先按before/after解依赖,再按@AutoConfigureOrder,最后字母序兜底。这一段的输出顺序,就是你以后在报告里看到的顺序 - 真正生效(apply):逐个做完整的条件求值,通过者注册成
BeanDefinition;未通过者写进报告 - 装配报告(report):把上面的结果渲染成正选 / 落选 / Exclusions / Unconditional 四段——和你在控制台加
--debug看到的一模一样
候选数在 filter 那一步掉得最狠,而真正决定「有没有这个 Bean」的是 apply。这两个数字的落差,正是「为什么加了依赖还是没生效」这个问题的答案所在——依赖影响的是 classpath,classpath 影响的是条件求值。
流水线的每一段都能被你手动干预。下面三个实验分别练三件事,最后一个还给出第十节决策的实操版。
第一个专练条件注解。cond 的 missing 参数讲的就是那条借伞规矩:客人没带伞(容器里没有同类型 Bean),店里才借一把(自动配置才补上);切到 report 能看到每个条件的判定原文,格式与 --debug 一致。先把四个条件各试一遍,你就再也不需要靠猜来判断「为什么不生效」:
第二个练「反向操作」:自己写一个 starter,把这条链路端到端跑通一次。starter 的四个参数依次是 meta(写清单登记自动配置类)、props(用 @ConfigurationProperties 绑前缀属性)、bean(条件满足后生成 Bean)、off(用 spring.xxx.enabled=false 关掉它)。做完这一段,第八节那段「让路机制」就不再是抽象说法,而是你自己写过四行代码的东西:
第三个练「同一个属性被多处声明时谁赢」。自动配置大量依赖配置项来决定开合,所以你必须知道优先级:命令行 > JNDI > 系统属性 > application-{profile}.yml > application.yml > 默认值。prop 场景会当场演示覆盖过程:
第四个实验把本篇和上一篇缝回一张图:一个对象进容器一共四条路,beanin 的 auto 档专门演示自动配置这条路——不经过扫描、不看包名,只认 jar 里那份清单。切到 scan 再对比一次,你就把「走扫描的门」和「走清单的门」这两种命运同时看完了,也正是本节末尾那张对比图想说的话:
顺手记住这张对比图——上一篇「Bean 找不到」和本篇「自动配置不生效」其实是同一枚硬币的两面:你的类走扫描的门,框架的类走清单的门。走错门,就永远不会出现:

本篇讲到现在,你手上其实已经有了六把扳手,但它们的作用位置各不相同:有的删候选、有的改条件、有的只管注册之后的事。把「扳手 ↔ 它作用在哪一环」配一遍,下次排查就不会再靠试:
--debug 打印的报告很长,但真正要看的只有四格。先看这条动画——它讲清这份报告是怎么被写出来的(理解了这一点,你就明白为什么它是「决策快照」而不是日志),再用左边换场景、右边直接告诉你该读哪一格:

Negative matches:DataSourceAutoConfiguration:- @ConditionalOnClass did not find required class 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)#去 Negative matches 这一格:括号里的 OnClassCondition 已经点名了缺失的类
四格的读法顺序是有讲究的——先确认它在不在正选(在,就去查你的属性和配置;不在),再去落选看原因(括号里的 Condition 类名就是根因),最后看 Exclusions 确认你没手滑 exclude 掉。这套顺序能替你省掉大部分「凭感觉改改看」。
报错原文都可以整段粘进搜索框:
| 报错原文(片段) | 真实原因 | 30 秒自救 | 深挖看第几篇 |
|---|---|---|---|
java.lang.NoClassDefFoundError: org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType | 自动配置类的 @ConditionalOnClass 需要的类不在 classpath——依赖少引了一个 | 开 --debug,在 Negative matches 里搜这个类名,它会告诉你哪个自动配置因此被跳过;再按该类所属 artifact 补依赖 | 本篇第七节 · 第十三节沙盘 |
ClassNotFoundException: com.rabbitmq.client.Channel 且 RabbitAutoConfiguration 全程静默 | 同上:类路径缺类,条件不满足,整个配置类被跳过。Boot 不会为「没启用」的功能报错 | 这正是 --debug 报告里 - @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel' (OnClassCondition) 那一行;加 spring-boot-starter-amqp | 本篇第六节条件矩阵 |
| 自定义 starter 升到 Spring Boot 3 后「整批静默失效」,没有任何报错 | 还在用旧机制:自动配置项写在 META-INF/spring.factories 里,而 3.0 已移除该入口 | 把类名逐行搬到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports;注意 spring.factories 仍服务于监听器、初始化器等其它扩展点,别整删文件 | 本篇第三节版本对照表 |
Field dataSource in ... required a bean of type 'javax.sql.DataSource' that could not be found | 数据源相关自动配置被跳过或被 exclude;也可能是 spring.datasource.url 缺失导致条件不成立 | 先看 Exclusions 有没有它,再看 Negative matches 的原因;两处都没有就说明根本没引入 starter | 第 17 篇第四节 · 第 28 篇连接池 |
Parameter 0 of method jpaVendorAdapter in ...HibernateJpaAutoConfiguration required a bean of type '...' that could not be found | 自动配置类之间的顺序出了问题:本该先注册的 Bean 还没到位 | 用 @AutoConfiguration(before/after) 显式声明依赖关系,不要靠 @AutoConfigureOrder 的魔法数字调 | 本篇第五节排序三层 |
The bean 'objectMapper', defined in class path resource [...] could not be registered. A bean with that name has already been defined [...] and overriding is disabled. | 同名 Bean 冲突,而 Boot 2.1 起默认禁止覆盖 | 别急着打开 allow-bean-definition-overriding;改名或干脆删掉你自己的定义,让自动配置的 @ConditionalOnMissingBean 让路机制工作 | 本篇第八节 · 第十五节自测第二题 |
spring.autoconfigure.exclude 写了类名却报 No auto-configuration classes found | 属性里写的类名拼错、或那个类根本不在当前 classpath(例如 starter 没引) | 从 IDE 里复制自动配置类的全限定名粘贴过去,别手打;顺带确认对应 starter 已在 pom 里 | 第 17 篇第六节三种排除手段 |
自动配置「不生效」几乎从不抛异常——它只是安静地不出现在容器里。所以排查第一步永远是 --debug,而不是改代码试试。看不见 ≠ 出错,看不见 = 条件没满足。
上面那条规律有唯一一个常见反例:自动配置生效了、但配不出可用的 Bean。这时候它不但会报错,还会把「我是谁配的」直接写在栈里。下面这段就是新手第一次引了 spring-boot-starter-data-jpa 却忘写连接信息时的现场,先别看解析,点出你认为的凶手行:
新建项目只勾了 Data JPA 与 H2,application.yml 一个字没写。启动即失败,屏幕中段打出 Reason: failed to configure a DataSource。
目标:开 --debug 看清一条真实的自动配置链路,并在报告里找出五个 Bean 的出处。
pom.xml:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>autolab</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- H2 会自动触发 DataSourceAutoConfiguration,无需任何 datasource 配置 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>主类(src/main/java/com/example/autolab/AutolabApplication.java,注意根包):
package com.example.autolab;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class AutolabApplication { public static void main(String[] args) { SpringApplication.run(AutolabApplication.class, args); }}src/main/resources/application.properties 只需要一行:
debug=true运行 mvn spring-boot:run,预期启动日志的关键片段(顺序与内容会随依赖略有出入):
:: Spring Boot :: (v3.2.5)============================CONDITIONS EVALUATION REPORT============================Positive matches:----------------- DataSourceAutoConfiguration matched: - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition) - @ConditionalOnMissingBean (types: io.r2dbc.spi.ConnectionFactory) did not find any beans (OnBeanCondition) DataSourceAutoConfiguration$EmbeddedDataSourceConfiguration matched: - @ConditionalOnMissingBean (types: javax.sql.DataSource,javax.sql.XADataSource) did not find any beans (OnBeanCondition) - EmbeddedDatabaseCondition and PooledDataSourceCondition both matched (OnClassCondition) DispatcherServletAutoConfiguration matched: - @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition) - found 'session' scope (OnWebApplicationCondition)Negative matches:----------------- RabbitAutoConfiguration: - @ConditionalOnClass did not find required classes 'org.springframework.amqp.rabbit.core.RabbitTemplate', 'com.rabbitmq.client.Channel' (OnClassCondition) QuartzAutoConfiguration: - @ConditionalOnClass did not find required class 'org.quartz.Scheduler' (OnClassCondition)Exclusions:----------- None.Unconditional:-------------- None.... Tomcat started on port 8080 (http) with context path ''... Started AutolabApplication in 2.1 seconds现在做三次「找东西」,每次都在报告里写下你那一行的原文:
- 在正选里搜
DispatcherServletAutoConfiguration,说出DispatcherServlet这个 Bean 是谁给的 - 在落选里随便挑三条,把括号里的 Condition 类名抄下来,并说出各自缺的是哪个类
- 把
debug=true删掉、改成命令行--debug启动,确认报告一模一样(顺便体会第十三节说的「开启方式三选一」)
验收清单:① 你能不看资料解释正选与落选的差别;② 至少一条落选记录里,你能指出「补哪个依赖它就会翻到正选」;③ 说清 Exclusions: None. 这行为什么值得单独看一眼。
每个变体只动一处:
- 让一条落选翻成正选:在上一步的基础上加
spring-boot-starter-amqp,重启。你会观察到RabbitAutoConfiguration从 Negative matches 移到 Positive matches——这是最直观的「条件由 classpath 决定」的证明。 - 亲眼看看让路机制:新建
MyJsonConfig,里面写一个@Bean ObjectMapper(随便设个日期格式),然后请求一个返回对象的接口。你会观察到JacksonAutoConfiguration的那条@ConditionalOnMissingBean判定发生变化,而你自己的格式化规则生效了。这正是第十节决策的 B 选项。 - 故意制造冲突:在第 2 步基础上再打开
spring.main.allow-bean-definition-overriding=true,并把你的 Bean 命名成objectMapper。你会观察到 启动不再提示冲突、但生效的是谁取决于注册顺序——把这条和上一轮实验的日志对比着记进笔记,你会永久记住为什么不该这么干。 - 用属性关掉一项自动配置:在
application.properties写spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,删掉 H2 依赖前的顺序请留意。你会观察到 报告的 Exclusions 段出现它,同时正选里的EmbeddedDataSourceConfiguration一起消失。
给自己做一个最小 starter greeting-spring-boot-starter,把本篇整条链路走通一遍。
- 两个 Maven 模块:
greeting-spring-boot-starter(只放依赖聚合)与greeting-spring-boot-autoconfigure(放代码) - 自动配置类要求:
@AutoConfiguration+@ConditionalOnClass(GreetingService.class)+@ConditionalOnMissingBean,并用@EnableConfigurationProperties(GreetingProperties.class)绑定前缀acme.greeting GreetingProperties至少两个字段:prefix(默认[hello])和enabled(默认 true);@Bean方法上再加一个@ConditionalOnProperty(prefix = "acme.greeting", name = "enabled", havingValue = "true", matchIfMissing = true)- 在
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports里登记这个类 - 另一个 demo 工程只依赖这个 starter,不写任何
@ComponentScan相关配置,直接注入GreetingService并暴露GET /greet
验收清单:① demo 里不加任何配置也能启动成功,/greet 返回带默认前缀的文本;② 改 acme.greeting.prefix 立刻生效;③ 设 acme.greeting.enabled=false 后启动失败并报「required a bean of type GreetingService」,且 --debug 落选段能指出是哪条属性条件拦住的;④ 把 optional 依赖去掉,观察 @ConditionalOnClass 让整个配置类消失;⑤ README 里画出「清单 → 过滤 → 排序 → 生效」四段,标注你这份文件分别落在哪一段。
不看上文,按顺序说出自动配置的四段流水线,以及每段大致做了什么。
AutoConfigurationImportFilter 和 ConditionEvaluator 的区别是什么?为什么说前者是「快刀」、后者是「细筛」?
spring.factories 与 AutoConfiguration.imports 的分水岭在哪个版本?升到 3.0 时哪种 starter 会静默失效?
自动配置类的顺序由哪三层决定?为什么官方推荐 before/after 而不是 @AutoConfigureOrder?
想覆盖一个自动配置好的 Bean,正确姿势是什么?用「精装房」和「借伞」两个类比各说一遍。
清单定候选、条件定生死、顺序定先后、延迟定谁让——四段流水线一句收。
自动配置的完整链路是 @EnableAutoConfiguration → AutoConfigurationImportSelector(延迟导入)→ 读取 AutoConfiguration.imports 清单 → 快速筛选 + 条件求值 → 注册 BeanDefinition → refresh() 实例化。清单格式在 3.0 从 spring.factories 迁移到了 AutoConfiguration.imports;顺序靠 before/after、@AutoConfigureOrder、字母序三层确定;能否生效全看条件注解。最后记住两条实战铁律:排查自动配置先开 --debug 看条件报告,覆盖自动配置靠「自己定义 Bean + @ConditionalOnMissingBean 让路」而不是打开覆盖开关。