《Spring Boot 入门》7.3 自动配置的调试与排查

学会读 --debug 打印的自动配置报告:从 Positive/Negative matches、Exclusions、Unconditional classes 四段定位 bean 为何没生效,用 DataSource 实例逐层看 reason,再用 ConditionEvaluationReport、Actuator 端点与 IDE 断点把排查串起来,附症状速查表。

本节目标:掌握一套可复用的排查手法——读懂 --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。

  1. 复现并抓证据。 加 --debug 重启,日志里出现 CONDITIONS EVALUATION REPORT。

  2. 搜类名。 在 Positive matches 里搜 AcmeAutoConfiguration——没有。

  3. 转 Negative matches。 找到它,原因是:

    AcmeAutoConfiguration:
       Did not match:
          - @ConditionalOnProperty (acme.enabled=true) did not find property 'acme.enabled' (OnPropertyCondition)
    

    等等——7.2 里我们写的是 matchIfMissing = true,缺属性也应当匹配。这说明实际情况是宿主显式配了 acme.enabled=false。

  4. 核对事实。 打开 application.yml,果然在某个 profile 片段里写了 acme.enabled: false,本意是「临时关掉」,却忘了改回来。

  5. 修复并验证。 删掉那行(或改成 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 控制器与路由 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「java」更多文章

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