搜索 K
Appearance
Appearance
GlobalExceptionHandler 用一个 @RestControllerAdvice 类承担了 7 类异常的统一捕获与转换。
@ExceptionHandler 命中后返回的 Result 会再经过 GlobalResponseAdvice,但后者发现 body 已是 Result 就会跳过二次包装,保留原对象输出。
按照 GlobalExceptionHandler.java 中声明的顺序:
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public Result<Void> handleBusinessException(BusinessException ex) {
log.warn("业务异常: code={}, message={}", ex.getCode(), ex.getMessage());
return Result.fail(ex.getCode(), ex.getMessage());
}业务异常返回 HTTP 200,由 success=false 与 code 表达失败。这是一个有争议但显式的选择:前端只需要看 success 与 code 两个字段,完全跳过 HTTP 状态码这个独立维度,简化客户端拦截逻辑。
@RequestBody + @Valid 的字段级校验失败。处理器把所有 FieldError.getDefaultMessage() 用 ; 拼接成一条消息,HTTP 400,业务 code 400。
@Validated 与单参数(路径变量、Query 参数)校验失败时由 Hibernate Validator 抛出。处理逻辑与上一项类似,但读的是 ConstraintViolation.getMessage()。
表单 / Query 参数绑定到 DTO 失败时抛出。同样拼接 FieldError 列表,状态码 400。
Spring Boot 3.x 在静态资源 404 的情况下抛出的标准异常。本处理器特意不打日志:
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Result<Void> handleNoResourceFoundException(NoResourceFoundException ex) {
return Result.fail(CommonConstants.HTTP_NOT_FOUND, CommonConstants.RESOURCE_NOT_FOUND_MESSAGE);
}原因:Spring Boot 3 升级后,连 favicon.ico 与各种 Robots 探测请求都会抛 NoResourceFoundException,再打 ERROR 日志会让生产环境日志被噪音淹没。
未被前面拦下的所有 RuntimeException。以 ERROR 级别打日志(含完整堆栈),对外脱敏返回。
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<Void> handleGlobalException(Exception ex) {
log.error("系统异常: {}", ex.getMessage(), ex);
return Result.fail(CommonConstants.HTTP_INTERNAL_SERVER_ERROR, CommonConstants.SERVER_ERROR_MESSAGE);
}返回消息固定为 CommonConstants.SERVER_ERROR_MESSAGE(「服务器开小差了,请稍后重试」),不会把堆栈信息暴露给客户端。
| 异常 | HTTP 状态码选择 | 备选项 | 选定理由 |
|---|---|---|---|
| BusinessException | 200 | 4xx / 5xx | 简化前端单维度判断;牺牲一部分 RESTful 纯度 |
| 校验异常 | 400 | 422 | 400 在国内项目更常见,工具链 / 监控对其语义识别更稳定 |
| 静态资源 404 | 404 | 200 + 自定义提示 | 保留语义正确;改成 200 会与正常响应混淆 |
| 兜底异常 | 500 | 200 + code 区分 | 区分「业务可恢复」与「系统不可恢复」,便于上层链路监控告警 |
业务方完全可以再注册自己的 @RestControllerAdvice,只要满足:
MyAuthException,框架处理 RuntimeException)。Spring 会优先匹配更具体的处理器@Order(Ordered.HIGHEST_PRECEDENCE),让 Spring 优先选中业务方的 Advice别在业务 Advice 里再次抛异常
@ExceptionHandler 方法本身抛异常不会被同一个 Advice 链再次捕获,会直接走 Spring 的默认错误页或 ErrorController,破坏统一响应结构。
想新增一类异常的统一处理(例如 OAuth2 的 InvalidTokenException)有两个选择:
GlobalExceptionHandler 提交 PR 加 @ExceptionHandler 方法即可不需要改动 GlobalResponseAdvice —— 异常处理器返回的 Result 会被自动放行,不会被二次包装。