Skip to content

09 调试排错与后续路线

1. 排错的正确顺序

遇到错误时不要先随机修改代码。按下面顺序收集证据:

  1. 确认你刚刚改了什么。
  2. 找日志中的第一个关键异常,而不只看最后一行。
  3. 阅读异常类型、错误信息、文件名和行号。
  4. 找最底部的 Caused by,它经常是真正原因。
  5. 构造最小复现,只保留触发错误的请求或代码。
  6. 修复后重新运行相关测试,并确认没有引入新问题。

建议记录:请求方法、URL、请求体、响应状态、关键日志和数据库数据。

2. 编译错误与运行错误

编译错误

代码还不能生成可执行字节码,例如:

  • 分号、括号缺失。
  • 类型不匹配。
  • 方法或变量不存在。
  • 导入了错误的类。

使用:

bash
mvn compile

运行错误

代码可以编译,但运行过程中失败,例如:

  • NullPointerException
  • 数据库连接失败
  • 端口被占用
  • 请求参数转换失败
  • SQL 约束冲突

这两类问题的排查方向不同,先确定错误发生在哪个阶段。

3. 常见 Java 异常

异常常见原因排查重点
NullPointerExceptionnull 调用方法或访问字段哪个引用为空,它为什么为空
NumberFormatException文本不能转换为数字原始输入内容
IndexOutOfBoundsException集合或数组下标越界长度和下标
ClassCastException对象被转换为不兼容类型实际运行类型
IllegalArgumentException参数不符合方法要求调用方传入的参数

不要简单地在外层加一个 try-catch 吞掉所有异常,这会让问题更难定位。

4. Spring Boot 常见启动问题

端口被占用

典型信息:

text
Web server failed to start. Port 8080 was already in use.

解决方式:停止占用端口的进程,或临时修改:

yaml
server:
  port: 8081

Bean 找不到

典型信息:

text
required a bean of type 'xxx' that could not be found

检查:

  • 实现类是否添加 @Service@Component 等注解。
  • 类是否位于启动类根包或其子包。
  • 注入的接口是否有实现。
  • 条件配置是否导致 Bean 没有创建。
  • 测试切片是否只加载了部分 Bean。

存在多个同类型 Bean

典型信息:

text
expected single matching bean but found 2

表示同一个接口有多个实现。可以重新审视依赖设计,或使用 @Primary@Qualifier 明确选择。

循环依赖

text
ServiceA → ServiceB → ServiceA

循环依赖通常说明职责边界不清。优先提取共同服务、调整调用方向或使用事件,不要把允许循环依赖当成首选修复。

5. HTTP 接口常见问题

404 Not Found

  • URL 是否拼错。
  • Controller 是否被扫描。
  • 类和方法上的路径拼接是否正确。
  • 请求方法是否匹配。

405 Method Not Allowed

URL 可能正确,但 HTTP 方法错误,例如接口要求 POST,却发送了 GET。

400 Bad Request

  • JSON 格式错误。
  • 字段类型不匹配。
  • 日期格式不正确。
  • 参数校验失败。
  • 忘记设置 Content-Type: application/json

415 Unsupported Media Type

通常是请求体格式或 Content-Type 不符合接口要求。

500 Internal Server Error

查看服务端异常日志。500 表示错误没有被合理转换,不等于一定是 Spring 本身的问题。

6. 数据库常见问题

连接失败

检查:

  • 数据库服务是否启动。
  • 主机、端口和数据库名是否正确。
  • 用户名和密码是否正确。
  • 用户是否有目标库权限。
  • JDBC URL 参数是否合理。

表不存在或字段不匹配

  • 数据库迁移是否执行。
  • 当前连接的是不是预期数据库。
  • Entity 字段和迁移脚本是否同步。
  • 命名策略是否导致表名、列名变化。

唯一约束冲突

不能只删除唯一约束来绕过问题。应确认业务上是否允许重复,并把数据库异常转换成 409 等合适响应。

懒加载异常

典型信息:

text
LazyInitializationException

常见原因是在事务结束后访问懒加载关联。合理方案包括:在查询阶段获取需要的数据、使用 DTO 投影、join fetch 或调整事务边界。不要默认把所有关系改成 EAGER。

7. 日志基础

java
private static final Logger log = LoggerFactory.getLogger(TaskService.class);

public TaskResponse findById(Long id) {
    log.debug("Query task, id={}", id);
    // ...
}

使用占位符,不要为了日志大量拼接字符串。日志中避免记录:

  • 密码和验证码。
  • 完整 Token、Cookie、私钥。
  • 身份证、银行卡等敏感信息。
  • 没有必要的完整请求体。

常见级别:

  • ERROR:当前操作失败,需要关注。
  • WARN:异常情况,但系统仍可继续。
  • INFO:关键业务和生命周期信息。
  • DEBUG:开发排查细节。
  • TRACE:最细粒度信息,通常临时使用。

8. Debug 技巧

IDE 调试时重点掌握:

  • 在可疑行设置断点。
  • Step Over:执行当前行,不进入方法。
  • Step Into:进入方法内部。
  • Step Out:执行到当前方法返回。
  • 查看变量和调用栈。
  • 添加条件断点,例如只在 id == 100 时暂停。
  • Evaluate Expression:临时计算表达式。

断点只是观察工具。先形成假设,再用断点验证,比从第一行开始逐步执行更高效。

9. 常见新手误区

只背注解,不理解数据流

每写一个接口,都能说清楚:

text
HTTP 请求
→ Controller 接收 DTO
→ Service 执行业务规则
→ Repository 访问数据库
→ Service 转换 DTO
→ Controller 返回 JSON

Controller 包办所有逻辑

如果 Controller 中出现大量数据库操作和业务判断,应把它们移动到 Service。

直接返回 Entity

这会把数据库模型与 API 强耦合,还可能暴露字段或触发关联序列化问题。使用响应 DTO。

到处使用 try-catch

局部捕获只在能够真正处理或补充上下文时使用。Web 错误可集中交给 @RestControllerAdvice

用 Lombok 代替理解 Java

Lombok 能减少样板代码,但新手应先理解构造方法、Getter、Setter、equalshashCode。熟悉后再按团队规范使用。

一开始就做微服务

先把单体项目的接口、事务、测试、日志和数据库做好。拆成多个服务会增加网络、部署、数据一致性和排错成本。

10. 四周入门计划

第 1 周:Java 语法

  • 环境、变量、判断、循环、数组、方法和 String。
  • 每天写 3~5 个小练习。
  • 周末完成控制台版学生成绩管理。

第 2 周:面向对象与工程化

  • 类、接口、集合、异常、泛型、Stream。
  • Maven、Git、JUnit。
  • 完成内存版图书借阅服务及测试。

第 3 周:Spring Boot Web

  • IoC、DI、Bean、自动配置和配置文件。
  • REST、DTO、参数校验、状态码和异常处理。
  • 完成内存版用户 CRUD API。

第 4 周:数据库与完整项目

  • SQL、JPA、Repository、事务和分页。
  • 完成待办事项项目。
  • 增加测试、MySQL 和数据库迁移。

如果时间紧,可以把每一周延长为两周。稳定练习比赶进度更重要。

11. 后续学习路线

完成本套文档后,建议按以下顺序继续:

  1. SQL:连接、子查询、索引、事务隔离级别、执行计划。
  2. Java 进阶:并发、线程池、JVM 内存、垃圾回收和性能分析。
  3. Spring Security:登录、JWT、权限模型和常见 Web 安全问题。
  4. API 文档:OpenAPI / Swagger。
  5. 测试:Mockito、MockMvc、Testcontainers、集成测试。
  6. 数据库工程:Flyway、锁、并发更新、慢 SQL。
  7. Redis:缓存、分布式锁的边界、限流。
  8. 消息队列:异步处理、重试、幂等和死信队列。
  9. Docker、Linux、CI/CD、监控和日志聚合。
  10. 最后再根据实际项目学习 Spring Cloud 和微服务。

12. 面试前的基础自测

尝试不看文档回答:

  1. ==equals 有什么区别?
  2. List、Set、Map 分别适合什么场景?
  3. checked exception 与 runtime exception 有什么区别?
  4. Maven 的 compiletestpackage 有什么关系?
  5. 什么是 IoC 和依赖注入?
  6. 为什么推荐构造器注入?
  7. Controller、Service、Repository 各负责什么?
  8. 为什么 API 不应直接返回 Entity?
  9. @Valid@Transactional 分别解决什么问题?
  10. 如何处理资源不存在、参数错误和重复数据?
  11. JPA 与 Hibernate、Spring Data JPA 的关系是什么?
  12. 事务为什么可能回滚,什么情况下可能不回滚?

不能回答的问题就是下一轮复习重点。

13. 最终建议

一个可靠的学习闭环是:阅读一小节 → 手写代码 → 写测试 → 制造错误 → 阅读日志 → 修复 → 用自己的话总结。能独立解释和排错,比记住更多注解更接近真正掌握。