Java统一异常处理的关键是分层拦截、语义归一、响应可控,而非捕获Exception;应定义BaseException基类及子类分层抛出,@ExceptionHandler精准拦截业务异常,分离日志与用户提示,严禁catch Exception兜底。

Java中统一处理异常的关键不在于“捕获所有异常”,而在于分层拦截、语义归一、响应可控。直接在每个方法里写 try-catch 或全局 @ControllerAdvice 捕获 Exception 是常见但危险的做法。
为什么不能 catch Exception 作为统一兜底
捕获 Exception 会吞掉本该由 JVM 处理的错误(如 OutOfMemoryError)、框架内部异常(如 Spring 的 BeanCreationException),甚至掩盖空指针或类型转换失败等开发期应暴露的问题。
- 运行时异常(
RuntimeException及其子类)本就不强制捕获,强行包裹反而模糊责任边界 -
Exception包含受检异常(如IOException)和非受检异常,混在一起处理会导致业务逻辑与基础设施错误无法区分 - HTTP 响应码无法精准映射:400(客户端错误)和 500(服务端崩溃)必须分开,而
catch(Exception)一律返回 500
推荐的三层异常封装结构
按职责分离:底层抛具体异常 → 中间层转为领域异常 → Web 层转为标准响应体。核心是定义清晰的异常基类和转换规则。
- 定义
BaseException继承RuntimeException,带code(业务码)、message(用户提示)、httpStatus(默认 400)字段 - 业务模块抛
AuthException、OrderNotExistException等继承自BaseException的子类 - Web 层用
@ExceptionHandler(BaseException.class)统一拦截,构造Result响应体并设置对应 HTTP 状态码
public class BaseException extends RuntimeException {
private final int code;
private final HttpStatus httpStatus;
public BaseException(int code, String message, HttpStatus httpStatus) {
super(message);
this.code = code;
this.httpStatus = httpStatus;
}
// getter...
}
@ControllerAdvice 中如何避免误捕系统级异常
只声明处理你定义的异常类型,明确排除 Error 和框架底层异常。Spring Boot 2.3+ 默认已过滤部分错误,但仍需主动防御。
立即学习“Java免费学习笔记(深入)”;
- 在
@ExceptionHandler上指定具体异常类,例如@ExceptionHandler(BusinessException.class),而非泛型Exception.class - 添加单独处理器捕获
HttpRequestMethodNotSupportedException、HttpMessageNotReadableException等 Spring 自带异常,并映射为 400 或 405 - 不处理
Throwable或Error子类;这类问题应由 JVM 日志、APM 工具或容器健康检查发现
日志记录与用户提示必须严格分离
用户看到的提示信息(message)必须脱敏、友好、无堆栈;而日志中要保留完整上下文(traceId、参数快照、原始异常堆栈)。
- 禁止把
e.getMessage()直接返回给前端——它可能含路径、SQL 片段、内部类名 - 使用 MDC 注入
traceId,确保日志可追溯;在异常处理器中调用log.error("biz error", e)而非log.error(e.getMessage(), e) - 对敏感操作(如支付、删库)的异常,额外记录操作人、IP、时间戳到审计表,不依赖通用日志
真正难的不是封装异常类,而是让每个开发明白:异常类型即契约,code 字段即 API 协议的一部分,改一个 throw new XxxException 就等于修改了接口语义。










