Spring 中的 @ExceptionHandler 注解的作用是什么?
- 标签:后端、Spring
🏛 业务架构师
@ExceptionHandler 的作用是定义"特定异常如何被处理"的方法:标注在 @ControllerAdvice 全局类(或单个 Controller)里,指明处理的异常类型,方法返回统一格式的响应。
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public Result<Void> handle(BizException e) {
return Result.fail(e.getCode(), e.getMessage()); // 业务异常 → 4xx/错误码
}
@ExceptionHandler(Exception.class)
public Result<Void> handleUnknown(Exception e) {
log.error("unexpected", e); alert(e);
return Result.fail("SYSTEM_ERROR"); // 系统异常 → 告警 + 兜底
}
}业务架构师视角,这是错误码体系与故障响应的治理入口,我的标准分层:
- 业务异常(用户可见:余额不足、库存不足)→ 定错误码、HTTP 200/4xx、info 日志,不告警——它是正常业务分支;
- 系统异常(DB 超时、NPE)→ 5xx、error 日志、触发告警——它是故障信号;
- 区分这两类的运营价值:如果业务异常也告警,值班同学会被噪音淹没,真正的故障反而被淹没——告警信噪比直接决定故障发现速度(MTTR)。
配套纪律:异常处理集中在一处,业务代码只管抛带业务语义的异常;错误码枚举统一管理、对外文档同步。异常处理是 API 治理的门面,架构师必须把它从"个人写法"升级为"公司规范"。
🏗 系统架构师
作用:Controller 内声明「某类异常 → 某个处理方法」的映射,异常不再向上抛给容器:
java
@ExceptionHandler(BizException.class)
public Result<Void> handle(BizException e) {
return Result.fail(e.getCode(), e.getMessage());
}配合 @ControllerAdvice 升级为全局异常收口——这才是它的架构位置。
我的落地标准:
- 统一错误契约:全公司一套 code/message/traceId 结构,Controller 层禁止自行 try-catch 拼响应——错误格式漂移会毁掉所有调用方的重试与告警逻辑;
- 告警分流:业务异常转 4xx(计数不告警),系统异常转 5xx(触发告警并按 traceId 关联链路现场)——异常处理体系直接决定 MTTR;
- 不泄露内部细节:堆栈、SQL、内部域名绝不进响应体,只随日志留档;
- 优先级:本 Controller 的 Handler 先于全局 Advice,用这个层级做「通用兜底加个别接口覆写」。
💻 全栈开发专家
标在方法上,把指定异常就地转成响应;配合 @ControllerAdvice 升级为全局处理,这是主力方案:
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public Result<Void> biz(BizException e) {
return Result.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> invalid(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
.collect(Collectors.joining("; "));
return Result.fail(400, msg);
}
@ExceptionHandler(Exception.class)
public Result<Void> unknown(Exception e) {
log.error("unexpected", e); // 兜底必须带堆栈
return Result.fail(500, "系统繁忙");
}
}机制:DispatcherServlet 捕获 handler 异常后,先找本 Controller 内的 handler,再找 @ControllerAdvice;异常匹配取最精确的继承距离。
工程细节:兜底日志必须 error 级别带堆栈(只打 message 丢根因的惨案见过);静态资源 404 和 Filter 里的异常不走这里,要 ErrorController 兜;Boot 3 可返回 ProblemDetail(RFC 7807 标准错误体),前端和告警都好对接。