09 调试排错与后续路线
1. 排错的正确顺序
遇到错误时不要先随机修改代码。按下面顺序收集证据:
- 确认你刚刚改了什么。
- 找日志中的第一个关键异常,而不只看最后一行。
- 阅读异常类型、错误信息、文件名和行号。
- 找最底部的
Caused by,它经常是真正原因。 - 构造最小复现,只保留触发错误的请求或代码。
- 修复后重新运行相关测试,并确认没有引入新问题。
建议记录:请求方法、URL、请求体、响应状态、关键日志和数据库数据。
2. 编译错误与运行错误
编译错误
代码还不能生成可执行字节码,例如:
- 分号、括号缺失。
- 类型不匹配。
- 方法或变量不存在。
- 导入了错误的类。
使用:
mvn compile运行错误
代码可以编译,但运行过程中失败,例如:
NullPointerException- 数据库连接失败
- 端口被占用
- 请求参数转换失败
- SQL 约束冲突
这两类问题的排查方向不同,先确定错误发生在哪个阶段。
3. 常见 Java 异常
| 异常 | 常见原因 | 排查重点 |
|---|---|---|
NullPointerException | 对 null 调用方法或访问字段 | 哪个引用为空,它为什么为空 |
NumberFormatException | 文本不能转换为数字 | 原始输入内容 |
IndexOutOfBoundsException | 集合或数组下标越界 | 长度和下标 |
ClassCastException | 对象被转换为不兼容类型 | 实际运行类型 |
IllegalArgumentException | 参数不符合方法要求 | 调用方传入的参数 |
不要简单地在外层加一个 try-catch 吞掉所有异常,这会让问题更难定位。
4. Spring Boot 常见启动问题
端口被占用
典型信息:
Web server failed to start. Port 8080 was already in use.解决方式:停止占用端口的进程,或临时修改:
server:
port: 8081Bean 找不到
典型信息:
required a bean of type 'xxx' that could not be found检查:
- 实现类是否添加
@Service、@Component等注解。 - 类是否位于启动类根包或其子包。
- 注入的接口是否有实现。
- 条件配置是否导致 Bean 没有创建。
- 测试切片是否只加载了部分 Bean。
存在多个同类型 Bean
典型信息:
expected single matching bean but found 2表示同一个接口有多个实现。可以重新审视依赖设计,或使用 @Primary、@Qualifier 明确选择。
循环依赖
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 等合适响应。
懒加载异常
典型信息:
LazyInitializationException常见原因是在事务结束后访问懒加载关联。合理方案包括:在查询阶段获取需要的数据、使用 DTO 投影、join fetch 或调整事务边界。不要默认把所有关系改成 EAGER。
7. 日志基础
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. 常见新手误区
只背注解,不理解数据流
每写一个接口,都能说清楚:
HTTP 请求
→ Controller 接收 DTO
→ Service 执行业务规则
→ Repository 访问数据库
→ Service 转换 DTO
→ Controller 返回 JSONController 包办所有逻辑
如果 Controller 中出现大量数据库操作和业务判断,应把它们移动到 Service。
直接返回 Entity
这会把数据库模型与 API 强耦合,还可能暴露字段或触发关联序列化问题。使用响应 DTO。
到处使用 try-catch
局部捕获只在能够真正处理或补充上下文时使用。Web 错误可集中交给 @RestControllerAdvice。
用 Lombok 代替理解 Java
Lombok 能减少样板代码,但新手应先理解构造方法、Getter、Setter、equals 和 hashCode。熟悉后再按团队规范使用。
一开始就做微服务
先把单体项目的接口、事务、测试、日志和数据库做好。拆成多个服务会增加网络、部署、数据一致性和排错成本。
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. 后续学习路线
完成本套文档后,建议按以下顺序继续:
- SQL:连接、子查询、索引、事务隔离级别、执行计划。
- Java 进阶:并发、线程池、JVM 内存、垃圾回收和性能分析。
- Spring Security:登录、JWT、权限模型和常见 Web 安全问题。
- API 文档:OpenAPI / Swagger。
- 测试:Mockito、MockMvc、Testcontainers、集成测试。
- 数据库工程:Flyway、锁、并发更新、慢 SQL。
- Redis:缓存、分布式锁的边界、限流。
- 消息队列:异步处理、重试、幂等和死信队列。
- Docker、Linux、CI/CD、监控和日志聚合。
- 最后再根据实际项目学习 Spring Cloud 和微服务。
12. 面试前的基础自测
尝试不看文档回答:
==和equals有什么区别?- List、Set、Map 分别适合什么场景?
- checked exception 与 runtime exception 有什么区别?
- Maven 的
compile、test、package有什么关系? - 什么是 IoC 和依赖注入?
- 为什么推荐构造器注入?
- Controller、Service、Repository 各负责什么?
- 为什么 API 不应直接返回 Entity?
@Valid和@Transactional分别解决什么问题?- 如何处理资源不存在、参数错误和重复数据?
- JPA 与 Hibernate、Spring Data JPA 的关系是什么?
- 事务为什么可能回滚,什么情况下可能不回滚?
不能回答的问题就是下一轮复习重点。
13. 最终建议
一个可靠的学习闭环是:阅读一小节 → 手写代码 → 写测试 → 制造错误 → 阅读日志 → 修复 → 用自己的话总结。能独立解释和排错,比记住更多注解更接近真正掌握。