前言
如果每个 Controller 都写try-catch,代码会变得又臭又长。Spring Boot 提供了多种全局异常处理机制,本文逐一对比。
方式一:@ControllerAdvice(最常用)
@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, "服务器繁忙,请稍后重试");
}
}
方式二:统一响应体
@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;
}
}
方式三:自定义异常 + 错误码枚举
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
@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 标准错误响应:@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;
}
}
响应示例:
{
"type": "about:blank",
"title": "订单不存在",
"status": 404,
"detail": "订单号 20260914001 未找到",
"instance": "/api/order/20260914001",
"traceId": "a1b2c3d4"
}
最佳实践建议
- 业务异常使用自定义
BusinessException,不要抛RuntimeException - 参数校验统一用
@Valid+BindingResult,在全局处理器里转换 - 系统异常必须打完整堆栈日志,但不要返回给前端
- 接入链路追踪后,把
traceId一并返回,方便排查
评论(3)