Spring Boot全局异常处理保姆级教程从入门到实战(看完秒懂)

 更新时间:2025年06月26日 11:34:02   作者:码不停蹄的玄黓  
本文讲解SpringBoot全局异常处理,通过@RestControllerAdvice集中处理异常,统一响应格式,提升代码简洁性与安全性,涵盖业务异常、参数校验及系统异常处理,避免敏感信息泄露,并提供日志记录与状态码自定义等进阶技巧,感兴趣的朋友跟随小编一起看看吧

在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项目异常处理将告别“屎山代码”,变得简洁、优雅、易维护!赶紧动手试试吧~ 有其他问题欢迎在评论区留言,我会一一解答! 😊

到此这篇关于Spring Boot全局异常处理保姆级教程从入门到实战(看完秒懂)的文章就介绍到这了,更多相关Spring Boot全局异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • ActiveMQ持久化机制代码实例

    ActiveMQ持久化机制代码实例

    这篇文章主要介绍了ActiveMQ持久化机制代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • 深入理解Java中的注解Annotation

    深入理解Java中的注解Annotation

    这篇文章主要介绍了深入理解Java中的注解Annotation,注解在Java中确实也很常见,但是人们常常不会自己定义一个注解拿来用,我们虽然很少去自定义注解,但是学会注解的写法,注解的定义,学会利用反射解析注解中的信息,在开发中能够使用到,这是很关键的,需要的朋友可以参考下
    2023-10-10
  • SpringBoot导入mail依赖报错问题解决

    SpringBoot导入mail依赖报错问题解决

    这篇文章主要介绍了SpringBoot导入mail依赖报错问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • springboot配置数据库密码特殊字符报错的解决

    springboot配置数据库密码特殊字符报错的解决

    这篇文章主要介绍了springboot配置数据库密码特殊字符报错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • SpringBoot整合Log4j2及配置步骤

    SpringBoot整合Log4j2及配置步骤

    这篇文章主要介绍了SpringBoot整合Log4j2以及配置详解,删除spring-boot-starter-parent默认使用spring-boot-starter-logging依赖,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-01-01
  • Java使用junit框架进行代码测试过程详解

    Java使用junit框架进行代码测试过程详解

    单元测试就是针对最小的功能单元编写测试代码,Junit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用Junit编写单元测试。本文就来讲讲Junit框架的使用教程,需要的可以参考一下
    2023-02-02
  • 详解Javascript判断Crontab表达式是否合法

    详解Javascript判断Crontab表达式是否合法

    这篇文章主要介绍了详解Javascript判断Crontab表达式是否合法的相关资料,需要的朋友可以参考下
    2017-03-03
  • Java利用Redis实现高并发计数器的示例代码

    Java利用Redis实现高并发计数器的示例代码

    这篇文章主要介绍了Java利用Redis实现高并发计数器的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • 简单了解spring cloud 网关服务

    简单了解spring cloud 网关服务

    这篇文章主要介绍了简单了解spring cloud 网关服务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • 使用mybatis的typeHandler对clob进行流读写方式

    使用mybatis的typeHandler对clob进行流读写方式

    这篇文章主要介绍了使用mybatis的typeHandler对clob进行流读写方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论