在老旧Java项目中引入自定义异常的核心是渐进式错误治理:先建BusinessException基类及子类封装硬编码错误,再通过全局处理器兜底、按用例闭环分批改造,并配套code管理、CI拦截和日志规范。

在老旧 Java 项目中引入自定义异常,核心不是“加几个 exception 类”,而是让异常真正参与错误治理——既要避免破坏现有调用链,又要为后续可观测性、分层处理和业务语义沉淀打下基础。关键在于渐进、可逆、有收敛。
从最痛的点切入,先封装已有硬编码错误码/字符串
老项目常见 throw new RuntimeException("订单状态非法") 或 if (code == -1) throw new Exception(...)。这类散落各处的“字符串异常”是重构第一目标:
- 新建一个顶层业务异常基类(如 BusinessException),继承 RuntimeException,带 error code、message、timestamp 字段;
- 针对高频错误场景,定义具体子类(如 OrderStatusInvalidException、InventoryNotEnoughException),构造时自动填充标准 code(如 "ORDER_STATUS_001");
- 用 IDE 全局替换:把原字符串 throw 替换为 new OrderStatusInvalidException(),保留原有 message 作为 detail(不丢上下文)。
拦截器/全局异常处理器先行,兜住旧逻辑不崩
老项目往往没有统一异常处理机制,直接加自定义异常可能引发未捕获崩溃。必须先建立“安全网”:
- 添加 @ControllerAdvice(Spring)或 Filter(Servlet)捕获所有 BusinessException 及其子类;
- 对 BusinessException 统一返回结构(如 { "code": "ORDER_STATUS_001", "msg": "订单当前不可操作", "traceId": "xxx" }),其他异常走默认降级;
- 日志中显式打印 error code 和堆栈,但对外响应体不暴露内部类名或路径——保持兼容性。
按模块分批改造,禁止跨层 throw unchecked 异常
不要试图一次性改完 service → dao 全链路。推荐以“用例闭环”为单位推进:
立即学习“Java免费学习笔记(深入)”;
- 选一个典型接口(如 “提交订单”),梳理它涉及的 service 方法调用链;
- 将该链路上所有 RuntimeException 替换为 BusinessException 子类,并确保上层 service 不再 try-catch 吞掉——让异常自然向上抛;
- DAO 层仍可用 SQLException 等原生异常,但 service 层必须 catch 并转为语义化 BusinessException(如数据库唯一约束失败 → DuplicateKeyException);
- 对外 API 层只声明 throws BusinessException(或不声明,因是 unchecked),不暴露底层技术异常。
配套做三件事,否则容易退回到老路
单加异常类没用,必须同步建立约束和习惯:
- Code 管理表:建一个 Excel 或轻量配置中心,记录每个 error code 的含义、触发条件、建议修复方式,开发提 PR 时需关联 code;
- CI 检查:用 Checkstyle 或自定义 SpotBugs 规则,禁止代码中出现 new RuntimeException("...") 或字符串字面量 throw;
- Log 埋点规范:要求所有 BusinessException 构造时传入关键业务参数(如 orderId、userId),日志模板固定输出这些字段,方便 ELK 聚合分析。
不复杂但容易忽略:老项目往往有大量“吃异常”逻辑(空 catch 或只打一行 log)。重构时优先把这些地方放开,让异常浮上来,再用自定义异常替代——不是掩盖问题,而是让问题可定位、可分类、可响应。


















