在SpringBoot项目中,异常处理是绕不开的话题。如果每个Controller都写一堆try-catch,代码会变得臃肿不堪;如果直接把异常堆栈抛给前端,用户体验和系统安全都会大打折扣。真正优雅的做法,是让异常处理集中化、标准化、可扩展。今天我们就来聊聊如何把SpringBoot全局异常处理做到极致。
一、为什么需要全局异常处理?
先看一段典型的“反面教材”:
@GetMapping("/user/{id}") public User getUser(@PathVariable Long id) { try { return userService.findById(id); } catch (UserNotFoundException e) { return null; // 或者返回一个乱七八糟的错误对象 } catch (Exception e) { e.printStackTrace(); return null; } }每个接口都这样写,代码重复率极高,返回格式五花八门,前端无法统一处理。更严重的是,异常信息可能暴露数据库结构或服务器路径,带来安全隐患。
二、SpringBoot的利器:@ControllerAdvice + @ExceptionHandler
SpringBoot提供了@ControllerAdvice和@ExceptionHandler,让我们可以定义全局异常处理器。
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result<?> handleBusinessException(BusinessException e) { return Result.fail(e.getCode(), e.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public Result<?> handleValidException(MethodArgumentNotValidException e) { String msg = e.getBindingResult().getFieldError().getDefaultMessage(); return Result.fail(400, msg); } @ExceptionHandler(Exception.class) public Result<?> handleException(Exception e) { log.error("系统异常", e); return Result.fail(500, "系统繁忙,请稍后重试"); } }@RestControllerAdvice相当于@ControllerAdvice+@ResponseBody,返回JSON格式。这样所有Controller抛出的异常都会被这里捕获,统一返回。
三、统一返回格式
为了让前端处理更简单,我们需要定义一个统一的返回结构:
@Data public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "成功"; r.data = data; return r; } public static <T> Result<T> fail(int code, String message) { Result<T> r = new Result<>(); r.code = code; r.message = message; return r; } }所有接口都返回Result,前端只需判断code即可,异常和正常响应格式完全一致。
四、自定义业务异常
对于业务逻辑错误,建议定义自己的异常体系:
public class BusinessException extends RuntimeException { private final int code; public BusinessException(int code, String message) { super(message); this.code = code; } public int getCode() { return code; } }在Service层直接抛出BusinessException,由全局处理器统一转换为标准响应。这样业务代码干净利落,不用关心HTTP状态码。
五、处理参数校验异常
SpringBoot的@Valid注解配合@NotNull、@Size等,可以自动校验参数。校验失败会抛出MethodArgumentNotValidException,我们可以在全局处理器中捕获并提取友好提示,避免返回冗长的堆栈信息。
六、日志记录与监控
全局异常处理不仅是返回错误,还要记录日志。对于业务异常,可以记录warn级别;对于系统异常,必须记录error级别,并带上请求路径、参数、用户信息等上下文。这样排查问题时才能快速定位。
@ExceptionHandler(Exception.class) public Result<?> handleException(HttpServletRequest request, Exception e) { log.error("请求路径:{},异常:{}", request.getRequestURI(), e.getMessage(), e); return Result.fail(500, "系统异常"); }七、优雅到极致的细节
区分HTTP状态码:业务异常返回200,系统异常返回500,或者统一返回200由code区分,根据团队规范选择。
避免暴露敏感信息:生产环境不要返回堆栈、SQL语句、类名等。
支持国际化:异常消息可以从MessageSource读取,实现多语言。
优先级:@ExceptionHandler会优先匹配最具体的异常类型,最后才走Exception兜底。
异步异常:对于@Async方法抛出的异常,需要单独配置AsyncUncaughtExceptionHandler。
八、总结
全局异常处理看似简单,却直接体现了一个项目的工程化水平。通过@ControllerAdvice、自定义异常、统一返回格式、日志记录,我们可以让代码更干净、接口更规范、排查更高效。记住:优雅的异常处理,不是消灭异常,而是让异常变得可控、可读、可追踪。从今天起,告别try-catch堆砌,让你的SpringBoot项目真正优雅起来。