Spring Boot全局异常处理保姆级教程:从入门到实战,告别重复try-catch!

在Spring Boot开发中,你是否遇到过这样的困扰?
每个Controller方法都要写一堆try-catch块,代码冗余到怀疑人生;前端抱怨接口返回格式不统一,有的返回JSON,有的返回HTML;系统报错时直接把数据库异常堆栈暴露给用户,安全隐患拉满……

别慌!今天这篇文章带你彻底搞定全局异常处理,用一行代码替代所有重复的try-catch,让异常处理变得优雅又高效!


一、为什么要学全局异常处理?看完你就懂了!

1.1 痛点驱动

想象一下:你有10个Controller,每个都要处理NullPointerExceptionIllegalArgumentException,甚至还要处理业务自定义的“用户不存在”异常……代码重复率高达80%,改个返回格式就得改10个地方,这不是在写代码,是在“复制粘贴”!

1.2 核心价值

  • 代码简洁:只需一个类集中处理所有异常,告别重复try-catch
  • 响应统一:前端收到的永远是{code: 200, msg: "成功", data: ...}格式,解析无压力。
  • 安全可控:系统异常(如数据库崩溃)不暴露堆栈,业务异常(如“余额不足”)明确提示用户。
  • 日志友好:异常信息集中记录,排查问题不用翻遍各个Controller。

二、Spring Boot全局异常处理的核心实现

Spring Boot提供了超好用的@RestControllerAdvice注解(@ControllerAdvice+@ResponseBody的组合),专门用于前后端分离场景的全局异常处理。咱们直接上干货!

2.1 第一步:定义统一响应格式

前端需要“标准化”的错误提示,所以先定义一个通用的Result类,所有接口返回值都用它包装。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;  // 状态码(200=成功,400=参数错误,500=系统错误)
    private String msg;    // 提示信息
    private T data;        // 业务数据(可选)

    // 快速构建成功响应(带数据)
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "操作成功", data);
    }

    // 快速构建失败响应(自定义code和msg)
    public static <T> Result<T> error(Integer code, String msg) {
        return new Result<>(code, msg, null);
    }
}

2.2 第二步:编写全局异常处理器

@RestControllerAdvice标记一个类,Spring Boot会自动扫描并拦截所有Controller的异常。重点是用@ExceptionHandler指定要处理的异常类型。

场景1:处理业务自定义异常(比如“用户不存在”)

业务中经常需要抛“用户不存在”、“订单已支付”这类异常,咱们可以自定义异常类,然后在全局处理器里捕获。

步骤1:定义业务异常类

// 自定义业务异常(继承RuntimeException,方便在Service层抛出)
public class BusinessException extends RuntimeException {
    private Integer code;  // 业务错误码(如4001=用户不存在)

    public BusinessException(Integer code, String msg) {
        super(msg);
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
}

步骤2:全局捕获业务异常

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理业务异常(比如用户不存在)
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }
}
场景2:处理参数校验异常(@Valid校验失败)

@Valid校验请求参数时,参数不合法会抛MethodArgumentNotValidException,咱们可以提取错误信息,友好提示用户。

// 全局处理参数校验异常(@RequestBody参数校验失败)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidationException(MethodArgumentNotValidException e) {
    // 提取所有校验失败的字段信息(比如"用户名不能为空")
    String errorMsg = e.getBindingResult().getFieldErrors().stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.joining(";"));
    return Result.error(400, "参数校验失败:" + errorMsg);
}
场景3:处理系统未知异常(兜底方案)

数据库崩溃、空指针等系统级异常,必须捕获并返回通用提示,同时记录完整日志(避免暴露敏感信息)。

// 全局处理其他未捕获的异常(系统级错误)
@ExceptionHandler(Exception.class)
public Result<?> handleSystemException(Exception e) {
    // 记录完整异常堆栈(生产环境必加!)
    log.error("系统发生未知异常,请求路径:{}", getRequestPath(), e);
    
    // 返回友好提示(前端看到的是"服务器繁忙,请稍后再试")
    return Result.error(500, "服务器繁忙,请稍后再试");
}

// 辅助方法:获取当前请求路径(需要spring-web依赖)
private String getRequestPath() {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    return attributes == null ? "" : attributes.getRequest().getRequestURI();
}

2.3 第三步:在业务中抛异常,测试效果!

现在,在Service层抛出自定义异常,看看是否被全局处理器捕获。

@Service
public class UserService {

    public User getUserById(Long id) {
        // 模拟数据库查询(假设id=100的用户不存在)
        User user = null; 
        if (user == null) {
            throw new BusinessException(4001, "用户ID=" + id + "不存在"); // 抛业务异常
        }
        return user;
    }
}

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return Result.success(user); // 正常返回
    }
}

测试结果:

  • id=100时,返回{"code":4001,"msg":"用户ID=100不存在","data":null}(业务异常被捕获)。
  • id=1(存在用户)时,返回{"code":200,"msg":"操作成功","data":{...}}(正常响应)。

三、进阶玩法:这些场景也能轻松搞定!

3.1 自定义HTTP状态码(比如404)

如果想让某些异常返回特定的HTTP状态码(如“资源不存在”返回404),可以用@ResponseStatus注解。

// 自定义异常(标记为404状态码)
@ResponseStatus(HttpStatus.NOT_FOUND) 
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String msg) {
        super(msg);
    }
}

// 全局处理(可选,也可以直接用@ResponseStatus)
@ExceptionHandler(ResourceNotFoundException.class)
public Result<?> handleResourceNotFound(ResourceNotFoundException e) {
    return Result.error(404, e.getMessage());
}

3.2 处理表单参数校验异常(@ModelAttribute)

如果是表单提交(非@RequestBody),参数校验失败会抛BindException,处理方式和MethodArgumentNotValidException类似:

@ExceptionHandler(BindException.class)
public Result<?> handleBindException(BindException e) {
    String errorMsg = e.getBindingResult().getFieldErrors().stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.joining(";"));
    return Result.error(400, "参数校验失败:" + errorMsg);
}

3.3 记录异常日志的细节

全局处理中记录日志时,建议带上请求路径用户ID(如果有登录)等信息,方便排查问题:

@ExceptionHandler(Exception.class)
public Result<?> handleSystemException(Exception e) {
    // 获取请求路径
    String path = getRequestPath();
    // 获取用户ID(假设从Token中解析)
    String userId = SecurityUtils.getCurrentUserId(); 
    
    // 记录日志(包含关键上下文信息)
    log.error("系统异常 | 用户ID={} | 路径={} | 异常信息={}", 
              userId, path, e.getMessage(), e); 
    
    return Result.error(500, "服务器繁忙,请稍后再试");
}

四、避坑指南:这些坑别踩!

4.1 全局异常不生效?

  • 检查是否添加了@RestControllerAdvice注解(别漏了@ResponseBody)。
  • 检查异常类的包路径是否被Spring扫描到(确保全局处理器和业务类在同一个或子包下)。
  • 检查是否有局部try-catch覆盖了全局处理(比如Controller里自己catch了异常,没抛出去)。

4.2 生产环境暴露敏感信息?

  • 系统异常(如SQLException)的msg不要直接返回给前端,用“服务器繁忙”代替。
  • 日志中可以记录完整堆栈,但响应体里只保留友好提示。

4.3 自定义异常没被捕获?

  • 确保自定义异常是RuntimeException的子类(Spring默认只捕获RuntimeExceptionError)。
  • 如果是检查型异常(如IOException),需要在方法上声明throws,或手动抛RuntimeException包装。

五、总结:全局异常处理的学习路线

  1. 基础用法:用@RestControllerAdvice+@ExceptionHandler捕获所有异常,返回统一Result
  2. 业务异常:自定义BusinessException,在Service层抛出,全局处理返回业务码。
  3. 参数校验:用@Valid+MethodArgumentNotValidException,提取校验错误信息。
  4. 系统异常:兜底处理Exception,记录日志并返回友好提示。
  5. 进阶优化:自定义状态码、记录请求上下文、处理表单参数异常。

掌握这些,你的Spring Boot项目异常处理将告别“屎山代码”,变得简洁、优雅、易维护!赶紧动手试试吧~ 有其他问题欢迎在评论区留言,我会一一解答! 😊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码不停蹄的玄黓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值