ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花
在Spring Boot/Spring MVC项目中,分散在Controller层的try-catch会导致代码冗余、响应格式不统一,全局统一异常处理能实现异常集中管控、标准化响应、日志统一打印,是后端工程化的必备实践。核心依托Spring的全局异常切面能力,无需侵入业务代码,即可完成异常拦截与处理。
一、核心原理:为什么能全局捕获异常?
Spring提供@RestControllerAdvice(或@ControllerAdvice)注解,用于定义全局控制器增强类,配合@ExceptionHandler注解指定要捕获的异常类型,实现:
- 拦截Controller层抛出的所有指定异常,跳过原生异常跳转流程
- 统一封装异常响应结果,保证前端接收格式一致
- 集中处理异常日志、异常状态码、异常信息脱敏
二者搭配是Spring官方推荐的标准方案,兼容Spring MVC、Spring Boot全版本,无额外依赖。
二、标准落地:五步实现统一异常处理
步骤1:定义统一响应体(前端标准化格式)
先封装全局响应对象,确保成功/异常响应格式统一,避免前端解析混乱,包含状态码、提示信息、数据三个核心字段。
import lombok.Data;
/**
* 全局统一返回结果
* @param <T> 响应数据泛型
*/
@Data
public class Result<T> {
// 响应状态码:200成功,500服务器异常,400参数错误等
private Integer code;
// 响应提示信息
private String msg;
// 响应数据
private T data;
// 成功响应
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("操作成功");
result.setData(data);
return result;
}
// 失败响应(异常专用)
public static <T> Result<T> fail(Integer code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
result.setData(null);
return result;
}
}
步骤2:自定义业务异常(区分系统异常/业务异常)
系统内置异常(空指针、数组越界)属于未知异常,业务异常(参数非法、权限不足)属于已知可预见异常,自定义业务异常可精准传递业务错误信息。
/**
* 自定义业务异常
*/
public class BusinessException extends RuntimeException {
// 业务异常状态码
private Integer code;
public BusinessException(String msg) {
super(msg);
this.code = 500;
}
public BusinessException(Integer code, String msg) {
super(msg);
this.code = code;
}
// getter
public Integer getCode() {
return code;
}
}
步骤3:编写全局异常处理器(核心类)
使用@RestControllerAdvice标记全局增强类,@ExceptionHandler绑定异常类型,实现不同异常的差异化处理。
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;
/**
* 全局统一异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 捕获自定义业务异常
*/
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException e) {
// 打印业务异常日志(仅打印信息,不打印堆栈,避免日志冗余)
log.error("业务异常:code={}, msg={}", e.getCode(), e.getMessage());
return Result.fail(e.getCode(), e.getMessage());
}
/**
* 捕获参数校验异常(Spring Validation常用)
*/
@ExceptionHandler(IllegalArgumentException.class)
public Result<?> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("参数异常:msg={}", e.getMessage());
return Result.fail(400, e.getMessage());
}
/**
* 捕获404资源不存在异常
*/
@ExceptionHandler(NoResourceFoundException.class)
public Result<?> handleNoResourceFoundException(NoResourceFoundException e) {
log.error("404异常:请求路径不存在", e);
return Result.fail(404, "请求资源不存在");
}
/**
* 捕获所有未知系统异常(兜底处理,优先级最低)
*/
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception e) {
// 打印完整堆栈,便于排查问题
log.error("系统未知异常", e);
// 脱敏返回,不暴露敏感异常信息
return Result.fail(500, "服务器繁忙,请稍后重试");
}
}
步骤4:业务代码抛出异常(无需try-catch)
Controller/Service层直接抛出异常,全局处理器会自动拦截,无需编写冗余try-catch。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/biz")
public Result<String> testBizException(@RequestParam Integer id) {
if (id == null || id < 0) {
// 抛出自定义业务异常
throw new BusinessException(400, "用户ID不能为空且不能小于0");
}
return Result.success("测试成功");
}
@GetMapping("/sys")
public Result<String> testSysException() {
// 模拟系统异常
int i = 1 / 0;
return Result.success("测试成功");
}
}
步骤5:测试验证
- 请求/test/biz?id=-1:返回400+自定义提示,日志打印业务异常
- 请求/test/sys:返回500+脱敏提示,日志打印完整堆栈
- 请求不存在接口:返回404提示
所有异常响应格式统一,前端可轻松解析。
三、进阶优化:工程化细节处理
1. 异常优先级管控
@ExceptionHandler按精确匹配优先原则,子类异常会优先于父类异常捕获。例如:BusinessException优先于Exception捕获,无需担心兜底异常覆盖业务异常。
2. 整合Spring Validation参数校验
针对@Valid/@Validated校验失败的MethodArgumentNotValidException,新增专属捕获逻辑,提取字段错误提示:
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public Result<?> handleValidException(Exception e) {
String msg;
if (e instanceof MethodArgumentNotValidException ex) {
msg = ex.getBindingResult().getFieldError().getDefaultMessage();
} else {
msg = ((BindException) e).getBindingResult().getFieldError().getDefaultMessage();
}
log.error("参数校验异常:{}", msg);
return Result.fail(400, msg);
}
3. 全局状态码枚举化
避免硬编码状态码,定义枚举类统一管理:
import lombok.Getter;
@Getter
public enum ResultCodeEnum {
SUCCESS(200, "操作成功"),
BIZ_ERROR(500, "业务异常"),
PARAM_ERROR(400, "参数错误"),
NOT_FOUND(404, "资源不存在"),
NO_AUTH(401, "未授权");
private final Integer code;
private final String msg;
ResultCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
4. 敏感信息脱敏
生产环境禁止返回堆栈信息、数据库异常等敏感内容,仅返回友好提示;开发环境可开启详细异常信息,便于调试。
四、常见避坑指南
- 异常处理器未生效:检查GlobalExceptionHandler是否被Spring扫描(包路径是否在启动类扫描范围内),是否添加@RestControllerAdvice注解。
- 异步方法异常无法捕获:@RestControllerAdvice仅拦截Controller层同步异常,异步方法需单独处理(@Async方法可搭配@ExceptionHandler或Future捕获)。
- 过滤器/拦截器异常无法捕获:全局处理器仅拦截Controller层异常,过滤器异常需在Filter中手动处理,拦截器异常可通过HandlerInterceptor适配。
- 重复捕获异常:避免在业务层try-catch后再次抛出,否则全局处理器无法正常拦截。
核心总结:Spring统一异常处理的核心是@RestControllerAdvice + @ExceptionHandler,搭配统一响应体+自定义异常,实现异常集中管控、格式标准化、代码无侵入,是后端项目的基础规范。
ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

全部评论
(1) 回帖