最直接有效的统一封装方式是@ControllerAdvice+@ExceptionHandler,仅拦截控制器层异常,需确保类被Spring扫描;推荐按BusinessException、RuntimeException、Exception分层处理,自定义异常应继承RuntimeException并携带code、HttpStatus等字段,避免new Exception随意抛出;ResponseStatusException适合简单状态码映射但不支持自定义字段;日志应在@ExceptionHandler中统一记录并脱敏,关键在于团队对异常分类与错误码规范达成共识。

统一用 ControllerAdvice 拦截全局异常
Spring Boot 项目中,最直接有效的统一封装方式是配合 @ControllerAdvice + @ExceptionHandler。它能拦截所有 @RestController 抛出的异常,避免每个接口都写 try-catch。
注意:它只对控制器层抛出的异常生效,Service 层 throw 出来但被 Controller 吞掉(比如 try-catch 后 return null)的情况不会被捕获。
- 必须确保类被 Spring 扫描到(加
@Component或放在主启动类同包下) - 推荐按异常类型分层处理:先捕获业务异常(如
BusinessException),再捕获RuntimeException,最后兜底Exception - 不要在
@ExceptionHandler方法里抛新异常,否则会再次触发拦截,可能造成循环
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity handleBusinessException(BusinessException e) {
return ResponseEntity.status(e.getHttpStatus())
.body(ApiResponse.error(e.getCode(), e.getMessage()));
}
}
自定义异常必须继承 RuntimeException 或显式声明 throws
Java 的 checked/unchecked 异常机制直接影响封装效果。如果业务异常继承 Exception(checked),调用方必须 try-catch 或 throws,这会污染 Service 接口签名,违背“异常用于异常场景”的设计原则。
实际项目中几乎全部采用 unchecked 异常(即继承 RuntimeException),让异常自然向上冒泡到 @ControllerAdvice。
立即学习“Java免费学习笔记(深入)”;
- 自定义异常类建议带状态码(
int code)、HTTP 状态(HttpStatus)、错误信息(支持 i18n key) - 不要在构造函数里拼接完整提示语,留待统一格式化层处理
- 避免用
new Exception("xxx")随意抛出,这类异常无法被分类识别,最终只能走兜底逻辑
ResponseStatusException 适合简单 HTTP 状态映射
如果只是需要返回 400、404、500 这类标准状态码,且不关心业务码或结构体封装,Spring 自带的 ResponseStatusException 是轻量选择。
它会被 @ControllerAdvice 默认捕获(因为继承自 RuntimeException),但它的 message 会直接暴露给前端,不适合含敏感信息或需统一字段结构的场景。
- 适用于快速原型或内部工具接口
- 不能携带额外字段(如
code、timestamp),和团队定义的ApiResponse格式冲突 - 若已存在全局异常处理器,
ResponseStatusException仍走该流程,但你得在 handler 里显式判断类型,否则会被当成普通RuntimeException处理
if (user == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "user.not.found");
}
日志记录和敏感信息脱敏不能依赖异常类本身
很多人把日志打印逻辑塞进自定义异常的构造方法里,这是危险的——异常可能被吞掉(比如被中间件吃掉、被测试框架忽略),导致关键错误无声丢失。
真正可靠的日志点,是在 @ControllerAdvice 的 @ExceptionHandler 方法内统一记录,且必须区分 level:业务异常(WARN)、系统异常(ERROR)。
- 记录堆栈时,过滤掉
org.springframework和javax.servlet等框架包路径,聚焦业务堆栈 - 对
message和toString()做关键词脱敏(如 “password”、“token”、“idCard”) - 不要在异常对象里存可变对象(如
Map),序列化或日志输出时容易引发意外 NPE 或循环引用
@ControllerAdvice 类,而在于整个团队对“什么算业务异常”“什么必须立即告警”“错误码如何分配”达成一致。否则,再多的封装也只会变成一层更厚的遮羞布。










