Spring Boot 3 全局异常处理终极指南(附完整代码架构),拿走即用
一、为什么需要全局异常处理?
在传统的Spring Boot项目中,每个Controller都需要写大量的try-catch来处理异常,代码重复、可维护性差。全局异常处理通过@ControllerAdvice+@ExceptionHandler统一拦截所有异常,将业务逻辑与异常处理解耦。
本文将带你从零搭建一套完整的全局异常处理架构,包含统一响应体、自定义业务异常、参数校验异常处理、生产级日志链路等,直接复制可用。
二、统一响应体定义
先定义一个标准的API返回格式,让前端统一解析:
@Data @NoArgsConstructor @AllArgsConstructor public class ApiResult<T> { private int code; private String message; private T data; public static <T> ApiResult<T> success(T data) { return new ApiResult<>(200, "success", data); } public static <T> ApiResult<T> error(int code, String message) { return new ApiResult<>(code, message, null); } }三、自定义业务异常
定义业务异常类,区分系统异常和业务逻辑异常:
@Getter public class BusinessException extends RuntimeException { private final int code; private final String message; public BusinessException(int code, String message) { super(message); this.code = code; this.message = message; } public BusinessException(ErrorCode errorCode) { this(errorCode.getCode(), errorCode.getMessage()); } }四、全局异常处理器
核心类,统一处理所有异常类型:
@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { // 业务异常 @ExceptionHandler(BusinessException.class) public ApiResult<?> handleBusiness(BusinessException e) { log.warn("业务异常: code={}, msg={}", e.getCode(), e.getMessage()); return ApiResult.error(e.getCode(), e.getMessage()); } // 参数校验异常 @ExceptionHandler(MethodArgumentNotValidException.class) public ApiResult<?> handleValidation(MethodArgumentNotValidException e) { String msg = e.getBindingResult().getFieldErrors().stream() .map(err -> err.getField() + ": " + err.getDefaultMessage()) .collect(Collectors.joining(", ")); return ApiResult.error(400, msg); } // 参数类型不匹配 @ExceptionHandler(ConstraintViolationException.class) public ApiResult<?> handleConstraint(ConstraintViolationException e) { return ApiResult.error(400, e.getMessage()); } // 404 @ExceptionHandler(NoHandlerFoundException.class) public ApiResult<?> handleNotFound(NoHandlerFoundException e) { return ApiResult.error(404, "接口不存在"); } // 顶级异常兜底 @ExceptionHandler(Exception.class) public ApiResult<?> handleException(Exception e) { log.error("系统异常: ", e); return ApiResult.error(500, "服务器繁忙,请稍后重试"); } }五、错误码枚举
@Getter @AllArgsConstructor public enum ErrorCode { // 通用 PARAM_ERROR(400, "参数错误"), UNAUTHORIZED(401, "未登录"), FORBIDDEN(403, "无权限"), NOT_FOUND(404, "资源不存在"), SYSTEM_ERROR(500, "系统异常"), // 业务 USER_NOT_EXIST(1001, "用户不存在"), USER_PASSWORD_ERROR(1002, "密码错误"), ORDER_NOT_FOUND(2001, "订单不存在"), ORDER_STATUS_ERROR(2002, "订单状态异常"); private final int code; private final String message; }六、在Controller中使用
@RestController @RequestMapping("/user") public class UserController { @GetMapping("/{id}") public ApiResult<User> getUser(@PathVariable Long id) { if (id == null || id <= 0) { throw new BusinessException(ErrorCode.PARAM_ERROR); } User user = userService.findById(id); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_EXIST); } return ApiResult.success(user); } @PostMapping public ApiResult<Void> createUser(@Valid @RequestBody UserCreateRequest request) { userService.create(request); return ApiResult.success(null); } }七、生产环境建议
- 日志链路:在全局异常中加入traceId,配合MDC实现全链路追踪
- 告警通知:针对500系统异常,接入钉钉/企微机器人告警
- 敏感信息过滤:生产环境不返回堆栈信息,只返回code+message
- 国际化支持:通过Accept-Language返回对应语言的错误信息
- 单元测试:为每个异常处理逻辑编写测试用例,确保覆盖率
这套架构我已经在多个生产项目中验证过,从单体应用到微服务都能直接套用。如果你有更好的实践,欢迎在评论区分享交流。
