本篇目标:按症状而不是按知识体系组织排查方法,把启动、配置、数据库与事务、测试四类问题整理成可直接照做的清单,并在末尾给出错误关键字到章节的总索引。
适用版本:Spring Boot 4.1.x(Java 21)
附录 D 常见问题排查
排查 Spring Boot 问题的第一原则:先读日志的最后 30 行。启动失败时,框架抛出的异常链非常完整,Caused by 会一路指到根因,绝大多数问题在日志里已经写明了答案。第二原则:从最具体的异常往上看,别被最外层的包装异常带偏。
本附录按症状索引。每条给出「症状 → 可能原因 → 排查步骤 → 解法 → 详见章节」五段。建议先用 D.5 的关键字表定位到小节,再逐条核对。
D.0 通用排查流程
在跳进具体症状之前,先固定一套顺序,能省下大量试错时间。
- 读日志尾部。启动失败时,异常链的最后一段
Caused by就是根因;不要从最外层的包装异常开始猜。 - 定位是哪个阶段失败。是环境(JDK、Maven)、启动装配(Bean、端口、数据源)、运行期(SQL、事务),还是测试阶段?阶段不同,排查入口不同。
- 看「实际生效值」而不是「你以为写的值」。配置类问题几乎都能靠
/actuator/env、/actuator/configprops或启动时的--debug报告还原真相。 - 复现到最小。把问题缩到一个最小的可运行例子,往往在缩的过程中答案就出现了。
- 一次只改一处。同时改多个配置会让「哪个改动起了作用」无法归因。
下面四类按这个流程组织,每一条都能独立照做。
D.1 启动类问题
启动阶段的问题集中在端口、Java 环境、主类、Bean 装配与数据源五处。
D.1.1 端口被占用:Port 8080 was already in use
- 症状:启动到内嵌 Tomcat 阶段失败,日志出现
Web server failed to start. Port 8080 was already in use. - 可能原因:上一次启动的进程没退干净;或同机跑了另一个服务。
- 排查步骤:
lsof -i :8080找出占用进程;ps确认是不是残留的 Java 进程。 - 解法:结束旧进程,或改
server.port;集成测试里常设server.port: 0让系统随机分配。 - 详见:3.2 内嵌服务器与启动过程 。
D.1.2 JAVA_HOME 未生效,编译或启动用了错的 JDK
- 症状:
java -version显示的版本不是 21;或 Maven 报release version 21 not supported。 - 可能原因:
JAVA_HOME指向旧 JDK;shell 里 PATH 与JAVA_HOME不一致。 - 排查步骤:
echo $JAVA_HOME与java -version对照;mvn -version会打印它实际使用的 JDK。 - 解法:把
JAVA_HOME指向 JDK 21 的安装目录,重新开一个终端;Spring Boot 4.x 要求 Java 17+,本系列统一用 21。 - 详见:2.1 JDK 与 Maven 环境准备 。
D.1.3 Unable to find a suitable main class
- 症状:打包或运行时报
Unable to find a single main class from the following candidates。 - 可能原因:工程里有多个带
main方法的类,或有多个@SpringBootApplication。 - 排查步骤:搜索
public static void main,确认只有一个入口;检查是否误留了测试用的启动类。 - 解法:删掉多余入口,或在插件里显式指定主类。
- 详见:3.3 可执行 jar 。
<!-- 显式指定主类 -->
<configuration>
<mainClass>com.example.book.BookApplication</mainClass>
</configuration>
D.1.4 Bean 创建失败:BeanCreationException
- 症状:启动抛
org.springframework.beans.factory.BeanCreationException,外面套了好几层。 - 可能原因:某个 Bean 的构造方法或
@PostConstruct里抛了异常;配置项缺失导致初始化失败。 - 排查步骤:从日志最底部的
Caused by开始读,那里才是真正的根因;确认报错 Bean 的名字,回到它的定义处。 - 解法:修掉根因异常,而不是在异常外面加
try/catch掩盖。 - 详见:5.1 IoC 容器与 Bean 。
D.1.5 No qualifying bean of type
- 症状:
No qualifying bean of type 'com.example.XxxService' available。 - 可能原因:类没加
@Component/@Service;或它所在的包不在主类所在包的子包里,没被组件扫描覆盖。 - 排查步骤:确认类上的注解;对照主类包路径,检查目标类是否在其之下。
- 解法:补注解,或把类移到被扫描的包;必要时用
@ComponentScan扩展范围。 - 详见:5.2 依赖注入与作用域 。
D.1.6 循环依赖:BeanCurrentlyInCreationException
- 症状:启动报
The dependencies of some of the beans in the application context form a cycle。 - 可能原因:两个 Bean 互相构造注入。Spring Boot 2.6 起默认禁止循环引用。
- 排查步骤:日志会画出依赖环(
A ──> B ──> A),顺着它找出互相依赖的两个类。 - 解法:优先重构——把公共逻辑抽成第三个 Bean;退而求其次用
@Lazy打断环,或改 setter 注入。不要图省事打开spring.main.allow-circular-references=true。 - 详见:5.2 依赖注入与作用域 。
D.1.7 Failed to configure a DataSource
- 症状:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured. - 可能原因:classpath 上有 JPA/JDBC,但既没配
spring.datasource.url,也没有可用的内嵌数据库。 - 排查步骤:确认依赖里是否有
spring-boot-starter-data-jpa;确认有没有配 url;确认是否加了 H2 之类内嵌库。 - 解法:补上
spring.datasource.url,或加入 H2;如果这个模块根本不需要数据库,用@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)排除。 - 详见:12.1 数据源配置 。
D.2 配置类问题
配置相关的问题最隐蔽,因为「配置没生效」通常不报错,只是行为不对。
D.2.1 配置写了但没生效
- 症状:改了
application.yml,行为却和没改一样。 - 可能原因:键名拼错;被更高优先级的来源(命令行、环境变量)覆盖;文件不在被加载的位置。
- 排查步骤:启动时加
--debug查看自动配置报告;引入 Actuator 后用/actuator/configprops与/actuator/env看实际生效值与来源。 - 解法:以
/actuator/env的生效值为准,逐层核对来源优先级。 - 详见:6.3 外部化配置与优先级 。
D.2.2 YAML 语法报错:cannot start any token
- 症状:
while scanning for the next token found character '\t' that cannot start any token,或mapping values are not allowed here。 - 可能原因:YAML 里用了 Tab 缩进(这是最高频的原因);或冒号后没留空格、缩进层级不一致。
- 排查步骤:让编辑器显示不可见字符,搜索 Tab;对照报错里的行号看缩进。
- 解法:YAML 一律用空格缩进;
key: value冒号后必须有空格。 - 详见:6.1 YAML 与 profile 。
D.2.3 profile 没激活
- 症状:
application-dev.yml里的配置没起作用。 - 可能原因:
spring.profiles.active没设,或被启动参数覆盖;profile 名拼写不一致。 - 排查步骤:看启动日志第二行——
No active profile set, falling back to 1 default profile: "default"说明没激活;The following 1 profile is active: "dev"才是生效了。 - 解法:用
--spring.profiles.active=dev或环境变量激活,并核对文件名application-dev.yml的拼写。 - 详见:6.1 YAML 与 profile 。
D.2.4 环境变量没被识别
- 症状:设置了
SPRING_DATASOURCE_URL,应用读到的还是 yml 里的旧值。 - 可能原因:变量名不符合 relaxed binding 规则;变量没导出到当前进程。
- 排查步骤:
printenv | grep SPRING确认变量存在;核对转换规则——spring.datasource.hikari.maximum-pool-size对应SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE(点变下划线、kebab-case 转大写下划线)。 - 解法:按规则改写变量名;容器里注意变量要传给应用进程。
- 详见:6.3 外部化配置与优先级 。
D.2.5 InvalidConfigDataPropertyException
- 症状:启动直接失败,提示
Property 'spring.profiles.active' imported from location ... is invalid in a profile specific resource。 - 可能原因:把
spring.profiles.active写进了application-xxx.yml这类 profile 专属文档里,而该属性只允许出现在主配置中。 - 排查步骤:在 profile 专属文件里搜索
spring.profiles.active或spring.profiles.include。 - 解法:把它移到主
application.yml,或用spring.config.activate.on-profile配合正确的属性组织方式。 - 详见:6.1 YAML 与 profile 。
D.2.6 多份配置文件的加载顺序混乱
- 症状:同一属性在多个文件里都有,生效的却是「不应该生效」的那份。
- 可能原因:不清楚外部化配置的优先级;或 profile 专属文件与主文件同名键冲突。
- 排查步骤:记住大方向——命令行参数 > 环境变量 > 外部配置文件 > 打包进 jar 的配置;
/actuator/env会明确列出每个来源。 - 解法:把差异项收敛到 profile 文件,通用项留在主文件,避免同名键在多处定义。
- 详见:6.3 外部化配置与优先级 。
D.3 数据库与事务类问题
这一类问题往往到运行期才暴露,排查时要同时看应用日志与数据库状态。
D.3.1 连接超时或连接被拒
- 症状:
HikariPool-1 - Connection is not available, request timed out,或Connection refused。 - 可能原因:数据库没启动;url / 端口 / 主机写错;连接池被耗尽;防火墙拦截。
- 排查步骤:先用
psql/mysql等客户端直连验证网络与凭据;再看/actuator/metrics/hikaricp.connections.active判断是否池满。 - 解法:修 url 或启动数据库;池被耗尽时排查长事务与未关闭的连接,再评估调大
maximum-pool-size。 - 详见:12.1 数据源配置 。
D.3.2 Table not found
- 症状:运行期查询报
Table "BOOK" not found(H2)或relation "book" does not exist(PostgreSQL)。 - 可能原因:
ddl-auto为none且没有迁移脚本;迁移脚本没被执行;实体映射的表名与真实表名不一致。 - 排查步骤:查
flyway_schema_history看脚本是否执行;用@Table(name = "...")对照实体与真实表名。 - 解法:补迁移脚本并确认 Flyway starter 已引入;修正
@Table映射。 - 详见:15.1 Flyway 入门 。
D.3.3 ddl-auto 误用
- 症状:生产重启后数据丢失(
create/create-drop);或改字段名后旧列残留、查询结果为空(update)。 - 可能原因:把开发期的
create-drop或update带到了生产。 - 排查步骤:检查生效配置里的
spring.jpa.hibernate.ddl-auto。 - 解法:有迁移脚本时只取
validate或none;绝不用update让 Hibernate 与迁移脚本争抢 schema。 - 详见:15.1 Flyway 入门 。
D.3.4 事务没有回滚
- 症状:方法抛异常,数据却已经写进去了。
- 可能原因:抛的是受检异常(默认只回滚运行时异常);方法自调用绕过代理;方法不是
public;事务方法被同类内部调用。 - 排查步骤:确认异常类型;确认调用是否经过 Spring 代理(跨 Bean 调用才生效)。
- 解法:需要回滚受检异常时用
@Transactional(rollbackFor = Exception.class);把事务方法抽到独立 Bean 再调用。 - 详见:14.3 事务失效的常见场景 。
D.3.5 懒加载异常:LazyInitializationException
- 症状:
could not initialize proxy - no Session。 - 可能原因:
open-in-view关了,又在事务外访问未加载的关联对象。 - 排查步骤:确认
spring.jpa.open-in-view的值;定位访问关联属性的代码位置。 - 解法:在事务内用
join fetch或@EntityGraph提前加载;或改用 DTO 投影,别把实体直接抛到事务外。 - 详见:13.1 关联映射 。
D.3.6 N+1 查询
- 症状:一次列表查询在日志里打出成百上千条 SQL,接口响应变慢。
- 可能原因:循环里逐个访问懒加载关联,每条触发一次查询。
- 排查步骤:打开 SQL 日志(
logging.level.org.hibernate.SQL: debug)数一数条数。 - 解法:用
join fetch/@EntityGraph一次取回;或设hibernate.default_batch_fetch_size批量加载。 - 详见:13.2 JPQL 与原生 SQL 。
D.3.7 时间字段时区错乱
- 症状:入库时间比本地时间差 8 小时,或同一时刻在不同环境显示不一致。
- 可能原因:JVM 时区、数据库时区、JDBC 连接时区三者不一致;用了
java.util.Date而非java.time。 - 排查步骤:
date看服务器时区;查 JDBC URL 是否带时区参数;检查实体里时间字段的类型。 - 解法:统一用
java.time.Instant/LocalDateTime,并显式约定时区(如连接串里指定serverTimezone)。 - 详见:12.2 实体与 Repository 。
D.4 测试类问题
测试相关的问题多来自 4.x 的注解变更,这是迁移时的高发区。
D.4.1 测试注解报错:@MockBean 不存在
- 症状:编译或运行测试时报找不到
@MockBean。 - 可能原因:4.x 已移除
@MockBean/@SpyBean,沿用 3.x 写法会失败。 - 排查步骤:搜索测试类里的
@MockBean/@SpyBean。 - 解法:改用
@MockitoBean/@MockitoSpyBean。注意新注解不能用在@Configuration类上,要写在测试类字段上。 - 详见:17.2 切片测试 。
D.4.2 @SpringBootTest 里 MockMvc 注入失败
- 症状:
@Autowired MockMvc报找不到 Bean,测试启动失败。 - 可能原因:4.x 的
@SpringBootTest不再自带 MockMvc,缺少自动配置。 - 排查步骤:检查测试类上是否只有
@SpringBootTest。 - 解法:补上
@AutoConfigureMockMvc;同理,要用TestRestTemplate需加@AutoConfigureTestRestTemplate,新的RestTestClient对应@AutoConfigureRestTestClient。 - 详见:17.2 切片测试 。
D.4.3 测试上下文加载慢
- 症状:每个测试类都重新启动 Spring 上下文,整套测试跑很久。
- 可能原因:不同测试类的配置不一致,导致 Spring 的上下文缓存无法命中。
- 排查步骤:运行测试时看日志里出现了几次
Started ... Application;次数越多说明缓存命中越差。 - 解法:统一测试配置(相同的
@SpringBootTest属性与 mock 组合),减少@DirtiesContext的使用,优先用切片测试。 - 详见:17.3 集成测试 。
D.4.4 测试数据互相污染
- 症状:单独跑测试通过,一起跑就失败;用例之间有数据残留。
- 可能原因:多个测试共用同一个数据库,前一个用例写入的数据影响了后一个。
- 排查步骤:把失败用例单独跑一遍,能通过就说明是数据依赖问题。
- 解法:给测试方法加
@Transactional让它自动回滚;或用@Sql准备与清理数据;或用随机化的测试数据避免碰撞。 - 详见:17.1 单元测试 。
D.4.5 切片测试里自定义 Bean 注入失败
- 症状:
@DataJpaTest/@WebMvcTest里@Autowired自己的组件时报找不到 Bean。 - 可能原因:切片测试只加载相关层,不会加载完整的应用上下文,自定义组件默认不在其中。
- 排查步骤:确认测试用的是切片注解还是
@SpringBootTest;切片测试的扫描范围是有限的。 - 解法:用
@Import显式引入需要的配置类;确实需要全上下文时改用@SpringBootTest。 - 详见:17.2 切片测试 。
D.5 错误关键字 → 章节总索引
先在左列找到日志里的关键字,再跳到对应小节。
| 错误关键字 | 类别 | 章节 |
|---|---|---|
Port ... was already in use | 启动 | D.1.1 |
release version 21 not supported | 启动 | D.1.2 |
Unable to find a single main class | 启动 | D.1.3 |
BeanCreationException | 启动 | D.1.4 |
No qualifying bean of type | 启动 | D.1.5 |
BeanCurrentlyInCreationException | 启动 | D.1.6 |
Failed to configure a DataSource | 启动 | D.1.7 |
| 配置不生效(无报错) | 配置 | D.2.1 |
cannot start any token | 配置 | D.2.2 |
No active profile set | 配置 | D.2.3 |
| 环境变量未生效(无报错) | 配置 | D.2.4 |
InvalidConfigDataPropertyException | 配置 | D.2.5 |
| 多文件同名键冲突 | 配置 | D.2.6 |
Connection is not available, request timed out | 数据库 | D.3.1 |
Table ... not found / relation ... does not exist | 数据库 | D.3.2 |
ddl-auto 引发的数据异常 | 数据库 | D.3.3 |
| 事务未回滚(无报错) | 事务 | D.3.4 |
LazyInitializationException | 数据库 | D.3.5 |
| N+1(日志 SQL 过多) | 数据库 | D.3.6 |
| 时间字段差 8 小时 | 数据库 | D.3.7 |
@MockBean / @SpyBean 找不到 | 测试 | D.4.1 |
| MockMvc 无法注入 | 测试 | D.4.2 |
| 测试上下文重复加载 | 测试 | D.4.3 |
| 测试数据互相污染 | 测试 | D.4.4 |
| 切片测试里 Bean 找不到 | 测试 | D.4.5 |
小结
排查 Spring Boot 问题,先把四类症状分开:启动类看端口、JDK、主类、Bean 装配与数据源;配置类看键名、YAML 缩进、profile 与环境变量;数据库与事务类看连接、表结构、ddl-auto、事务代理与懒加载;测试类重点记住 4.x 的注解变更——@MockBean / @SpyBean 已移除,@SpringBootTest 不再自带 MockMvc。所有问题里,最容易浪费时间的不是报错,而是「不报错的静默错误」:配置没生效、事务没回滚、N+1,都要靠主动观测(Actuator、SQL 日志)而不是等它抛异常。查完症状回到 附录 B
核对配置键名,构建相关问题见 附录 C
。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。