本节目标:掌握一套可复用的排查手法——读懂
--debug报告、从 Negative matches 反推根因、用 Actuator 与 IDE 断点定位「bean 为什么没出现」。
适用版本:Spring Boot 4.1.x(Java 21)
7.3 自动配置的调试与排查
上一节我们把 AcmeClient 装进了宿主项目,启动日志也打出了 Acme client ready:。但真实项目里更常见的是相反的场面:属性配了、依赖引了,可那个 bean 就是没出现;或者反过来,项目里冒出两个同类型的 bean,启动直接报冲突。
自动配置是「按条件生效」的,光看代码猜不出到底哪条条件没过。本节的任务,就是把它「为什么生效 / 为什么不生效」变成一件可以查证的事。这也是本章叙事线的收尾:先读懂官方 starter(7.1),再自己造一个(7.2),最后学会在它不生效时怎么查(本节)。
7.3.1 打开报告:三种开关
要排查,先拿到证据。让 Spring Boot 打印自动配置的评估报告,有三种方式:
| 方式 | 写法 | 适用场景 |
|---|---|---|
| 命令行参数 | java -jar app.jar --debug | 本地快速验证,不改代码不改配置 |
| 配置项 | application.properties 里写 debug=true | 想让某个环境长期输出报告 |
| 日志级别 | logging.level.org.springframework.boot.autoconfigure=DEBUG | 只放大自动配置相关的调试日志 |
三者里最常用的是 --debug。它不会把日志级别整体调低,而是额外输出一份结构化的 CONDITIONS EVALUATION REPORT,不干扰正常日志。启动命令:
java -jar target/demo-0.0.1-SNAPSHOT.jar --debug
注意
--debug与「把日志级别调成 DEBUG」不是一回事。前者只多打印评估报告;后者会刷出海量框架日志。排查自动配置,优先用--debug。
7.3.2 报告的四段结构
报告头部是一行 CONDITIONS EVALUATION REPORT,正文固定分成四段:
| 段落 | 含义 |
|---|---|
Positive matches | 条件全部满足、已生效的自动配置类 |
Negative matches | 至少一条条件不满足、未生效的自动配置类(附原因) |
Exclusions | 被你用 exclude / excludeName 显式排除的类 |
Unconditional classes | 没有任何条件、总会生效的基础设施类 |
排查的顺序永远是:先在 Positive matches 里找它「在不在」,找不到再去 Negative matches 里看它「卡在哪一条」。 四段里,Negative matches 信息量最大,因为它把「为什么没生效」直接写在了原因里。
7.3.3 读 Positive matches
一个正常运行的 Web 项目,Positive matches 里通常能看到数据源相关的自动配置。形态大致如下:
Positive matches:
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource',
'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
DataSourceAutoConfiguration.PooledDataSourceConfiguration matched:
- @ConditionalOnMissingBean (types: javax.sql.DataSource; SearchStrategy: all)
did not find any beans (OnBeanCondition)
读这两段的要点:
- 每条
matched:下面列的都是「通过了哪些条件」。 冒号后是条件名(OnClassCondition、OnBeanCondition等),括号里是判定依据。 - 外层类匹配 ≠ 内部配置生效。
DataSourceAutoConfiguration匹配后,真正建DataSource的是它内部的PooledDataSourceConfiguration——所以排查时要连嵌套类一起看。 OnBeanCondition说「did not find any beans」是好事:说明当前没有别处定义DataSource,自动配置可以放手创建。
7.3.4 读 Negative matches:为什么我的 bean 没生效
这是本节的核心。Negative matches 会把每个未生效的类连同原因列出来。下面用一个最常见的场景走一遍完整推理:配了 spring.datasource.*,可注入 DataSource 却报没有这个 bean。
第一步:搜类名。 在报告里搜 DataSourceAutoConfiguration。假设它出现在 Negative matches:
Negative matches:
-----------------
DataSourceAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'javax.sql.DataSource',
'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
第二步:读条件名。 括号里的 OnClassCondition 告诉你:卡在「类是否存在」这一条。
第三步:读依据。 did not find required classes 后面列出它需要的类——javax.sql.DataSource(JDK 自带)与 EmbeddedDatabaseType(来自 spring-jdbc)。前者永远在,后者不在,说明你的 classpath 上没有 spring-jdbc。
第四步:定位根因。 你大概只引了 spring-boot-starter-webmvc,却没引任何数据库相关 starter。修复:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
换一个场景:你自己定义了 DataSource。 这次报告里是这样:
DataSourceAutoConfiguration:
Did not match:
- @ConditionalOnMissingBean (types: javax.sql.DataSource; SearchStrategy: all)
found beans of type 'javax.sql.DataSource' dataSource (OnBeanCondition)
条件是 OnBeanCondition,依据是「found beans of type ‘javax.sql.DataSource’ dataSource」。翻译过来:你已经在别处定义了一个名为 dataSource 的 bean,所以自动配置主动让位。 这不是错误,恰恰是 @ConditionalOnMissingBean 在正常工作。看到 found beans 而你的 bean 也确实在,就可以放心跳过。
第三种场景:匹配了却启动失败。 有时 DataSourceAutoConfiguration 明明在 Positive matches 里,启动却抛出异常:
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
这说明自动配置已经生效,但它拿不到可用的连接信息:classpath 上有 JDBC 却没有驱动,或没配 spring.datasource.url。两种处理:
- 真要连库:补上
spring.datasource.url/username/password,并确保驱动在 classpath 上。 - 暂时不连库:显式排除这个自动配置,让应用先跑起来:
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication {
// ...
}
注意 4.x 里 DataSourceAutoConfiguration 的包是 org.springframework.boot.jdbc.autoconfigure(4.0 模块化后从旧的 autoconfigure.jdbc 迁到这里)。
把三种场景归纳成一句话:Negative matches 的每一条,都要分三步读——条件名 → 判定依据 → 你的项目事实。 条件名告诉你「在查什么」,依据告诉你「查到了什么」,两者一对,根因就出来了。
7.3.5 Exclusions 与 Unconditional classes
Exclusions 列出被你主动排除的自动配置。如果你在 @SpringBootApplication(exclude = ...) 里排除了数据源,报告里会出现:
Exclusions:
-----------
org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration
看到它,就能确认「这个类不是没满足条件,而是被我主动关掉了」。排查「某个功能怎么没生效」时,先看这里,能排除掉一大类「自己关的还去别处找原因」的乌龙。
Unconditional classes 列出没有任何 @Conditional、总会生效的自动配置类。这一段通常很短,且几乎都是基础设施(属性绑定、占位符解析之类)。它的价值在于:如果一个类出现在这里,就说明它「无条件生效」——它没生效只可能是被排除了,或根本没被加载(.imports 没登记)。反过来说,绝大多数业务相关的自动配置都不应该出现在这一段,如果出现了,往往意味着某个类忘了加条件。
7.3.6 编程式获取 ConditionEvaluationReport
报告不只能看日志。--debug 打印的其实就是 ConditionEvaluationReport 的内容,而这个对象在运行期可以拿到。想在自己的代码或测试里做断言、做定制输出时很有用:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class ProbeApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(ProbeApplication.class, args);
ConditionEvaluationReport report =
ConditionEvaluationReport.get(context.getBeanFactory());
report.getConditionAndOutcomesBySource().forEach((source, outcomes) -> {
if (source.contains("DataSourceAutoConfiguration")) {
System.out.println(source);
outcomes.forEach(outcome ->
System.out.println(" " + outcome.getCondition()
+ " -> match=" + outcome.isMatch()));
}
});
}
}
关键 API:
| 方法 | 返回 |
|---|---|
ConditionEvaluationReport.get(beanFactory) | 从容器拿到报告对象 |
getConditionAndOutcomesBySource() | Map<类名, 条件评估结果> |
outcome.getCondition() / outcome.isMatch() | 单个条件的描述与是否通过 |
用它,就能把「某个自动配置为什么没生效」写成一段可复现的诊断输出,而不必每次都去翻启动日志。
7.3.7 Actuator:/actuator/conditions 与 /actuator/beans
如果应用已经跑起来,最方便的是让 Actuator 把同样的信息通过 HTTP 暴露出来。先加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
再开放这两个端点:
management.endpoints.web.exposure.include=health,info,conditions,beans
GET /actuator/conditions 返回的就是那份报告的 JSON 版:
curl -s http://localhost:8080/actuator/conditions | head -c 400
{
"contexts": {
"application": {
"positiveMatches": { "DataSourceAutoConfiguration": [ { "condition": "OnClassCondition" } ] },
"negativeMatches": { "Neo4jAutoConfiguration": [ { "condition": "OnClassCondition" } ] },
"exclusions": [],
"unconditionalClasses": []
}
}
}
GET /actuator/beans 则列出容器里所有 bean 及其来源,是回答「这个 bean 到底有没有、从哪个类来的」的终极武器:
{
"contexts": {
"application": {
"beans": {
"acmeClient": {
"aliases": [],
"scope": "singleton",
"type": "com.acme.spring.boot.autoconfigure.AcmeClient",
"resource": "class path resource [com/acme/spring/boot/autoconfigure/AcmeAutoConfiguration.class]",
"dependencies": ["acmeProperties"]
}
}
}
}
}
resource 字段特别有用:它直接告诉你这个 bean 是哪个配置类创建的。当项目里出现两个同类 bean 时,对比它们的 resource,立刻能分辨「哪个是自己写的、哪个是自动配置建的」。
Actuator 端点默认只暴露
health,conditions与beans需要显式加进management.endpoints.web.exposure.include。生产环境暴露这些端点有信息泄露风险,请配合安全策略按需开启。
7.3.8 在 IDE 里打断点
日志和端点能解决大部分问题,但遇到「条件太多、组合太绕」时,断点更直接。三个常用断点位置:
| 断点位置 | 看什么 |
|---|---|
你自己自动配置类的 @Bean 方法 | 方法有没有被调用——没被调用,说明条件没过 |
ConditionEvaluationReport.get(...) 之后 | 断下后用变量视图展开 conditionAndOutcomesBySource,逐条看结果 |
OnClassCondition / OnPropertyCondition 的判定方法 | 看某个具体条件到底查了什么、结果如何 |
技巧:给 @Bean 方法上的断点加一个条件(IntelliJ 里右键断点 → Condition),只在特定 bean 名出现时停下,避免被大量无关调用刷屏。
7.3.9 速查表:症状 → 排查命令 → 常见原因
把本节的手法压成一张随时可查的表:
| 症状 | 排查手段 | 常见原因 |
|---|---|---|
| bean 没注入 | --debug 搜类名,看它在哪一段 | 条件不满足:缺类 / 属性没配 / profile 不对 |
| 属性配了却不生效 | /actuator/conditions 或 --debug | 前缀拼错、属性类没被 @EnableConfigurationProperties 注册 |
启动报 Failed to configure a DataSource | 看异常 Description | 没配 spring.datasource.url、没驱动,或想临时禁库 |
| 出现两个同类型 bean 冲突 | /actuator/beans 比对 resource | 自己定义了一个,自动配置又建了一个(漏 @ConditionalOnMissingBean) |
| 自定义 starter 完全没加载 | 检查 .imports 文件 | 路径 / 文件名 / 类名写错,或放到了 src/main/java |
| 想知道某 bean 从哪来 | /actuator/beans 的 resource 字段 | —— |
| 某个功能「莫名其妙」没生效 | 先看 Exclusions 段 | 之前用 exclude 关掉了却忘了 |
7.3.10 一个完整的排查实例
把方法串起来走一遍。场景:上一节的 AcmeClient 在新项目里没注入,注入点报 NoSuchBeanDefinitionException。
复现并抓证据。 加
--debug重启,日志里出现CONDITIONS EVALUATION REPORT。搜类名。 在
Positive matches里搜AcmeAutoConfiguration——没有。转
Negative matches。 找到它,原因是:AcmeAutoConfiguration: Did not match: - @ConditionalOnProperty (acme.enabled=true) did not find property 'acme.enabled' (OnPropertyCondition)等等——7.2 里我们写的是
matchIfMissing = true,缺属性也应当匹配。这说明实际情况是宿主显式配了acme.enabled=false。核对事实。 打开
application.yml,果然在某个 profile 片段里写了acme.enabled: false,本意是「临时关掉」,却忘了改回来。修复并验证。 删掉那行(或改成
true),重启,AcmeAutoConfiguration回到Positive matches,注入恢复正常。
整个过程的要点是:不要猜,让报告告诉你卡在哪一条。 报告里那句 did not find property 'acme.enabled',比任何「我觉得应该是……」都快。
小结
--debug(或debug=true)会额外打印CONDITIONS EVALUATION REPORT,不影响正常日志级别。- 报告分四段:
Positive matches、Negative matches、Exclusions、Unconditional classes;Negative matches信息量最大。 - 读
Negative matches三步:条件名 → 判定依据 → 你的项目事实。OnClassCondition did not find多为缺依赖,OnBeanCondition found beans多为条件让位(正常)。 - 报告可用
ConditionEvaluationReport.get(beanFactory)在代码里拿到;运行中的应用可用 Actuator 的/actuator/conditions与/actuator/beans查看。 - 遇到「bean 没出现 / 两个 bean 冲突 / 属性不生效」,先查报告与
/actuator/beans的resource字段,再考虑打断点。
至此本章收尾:你已经能读懂官方 starter 的组成(7.1)、亲手造一个(7.2)、并在它不生效时把它查出来(7.3)。下一章我们回到最常写的代码——用 Spring MVC 写控制器与路由,把「一个 HTTP 请求进来,怎么落到你的方法上」讲透。
阅读导航:上一节:7.2 写一个自定义 Starter · 下一节:8.1 控制器与路由 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。