Spring Boot 3 优雅处理全局异常的 5 种方式(含源码)

前言

如果每个 Controller 都写 try-catch,代码会变得又臭又长。Spring Boot 提供了多种全局异常处理机制,本文逐一对比。

方式一:@ControllerAdvice(最常用)

Java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException e) {
        log.warn("业务异常:{}", e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValid(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
                .map(f -> f.getField() + ":" + f.getDefaultMessage())
                .collect(Collectors.joining(", "));
        return Result.fail(400, msg);
    }

    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.fail(500, "服务器繁忙,请稍后重试");
    }
}

方式二:统一响应体

Java
@Data
public class Result<T> implements Serializable {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp = System.currentTimeMillis();

    public static <T> Result<T> ok(T data) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setMessage("success");
        r.setData(data);
        return r;
    }

    public static <T> Result<T> fail(Integer code, String message) {
        Result<T> r = new Result<>();
        r.setCode(code);
        r.setMessage(message);
        return r;
    }
}

方式三:自定义异常 + 错误码枚举

Java
public enum ErrorCode {
    SUCCESS(200, "成功"),
    PARAM_ERROR(400, "参数错误"),
    NOT_LOGIN(401, "未登录"),
    FORBIDDEN(403, "无权限"),
    NOT_FOUND(404, "资源不存在"),
    SYSTEM_ERROR(500, "系统异常");

    private final Integer code;
    private final String msg;
    ErrorCode(Integer code, String msg) { this.code = code; this.msg = msg; }
}

方式四:实现 ErrorController 接管 /error

Java
@RestController
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public Result<Void> handleError(HttpServletRequest request) {
        Integer status = (Integer) request.getAttribute("javax.servlet.error.status_code");
        return Result.fail(status == null ? 500 : status, "请求失败");
    }
}

方式五:Spring 6 ProblemDetail(RFC 7807)

Spring Boot 3 原生支持 RFC 7807 标准错误响应:
Java
@RestControllerAdvice
public class ProblemDetailHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(OrderNotFoundException.class)
    ProblemDetail handleNotFound(OrderNotFoundException e) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
                HttpStatus.NOT_FOUND, e.getMessage());
        pd.setTitle("订单不存在");
        pd.setProperty("traceId", MDC.get("traceId"));
        return pd;
    }
}

响应示例:

JSON
{
  "type": "about:blank",
  "title": "订单不存在",
  "status": 404,
  "detail": "订单号 20260914001 未找到",
  "instance": "/api/order/20260914001",
  "traceId": "a1b2c3d4"
}

最佳实践建议

  1. 业务异常使用自定义 BusinessException,不要抛 RuntimeException
  2. 参数校验统一用 @Valid + BindingResult,在全局处理器里转换
  3. 系统异常必须打完整堆栈日志,但不要返回给前端
  4. 接入链路追踪后,把 traceId 一并返回,方便排查
发表于天涯网络_专业开发者社区 - 个人技术分享
打赏作者 已有 0 人打赏,共 ¥0.00
我的打赏
天眼管理员
天眼管理员
Lv10 专业会员 原创 3 粉丝 1463

天眼技术社区官方管理员账号

  • 3文章
  • 24总阅读
  • 0获赞
  • 1463粉丝

评论(0)

💬

还没有评论,来说两句吧