开发者

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

目录
  • 一、为什么要学全局异常处理?看完你就懂了!
    • 1.1 痛点驱动
    • 1.2 核心价值
  • 二、Spring Boot全局异常处理的核心实现
    • 2.1 第一步:定义统一响应格式
    • 2.2 第二步:编写全局异常处理器
    • 2.3 第三步:在业务中抛异常,测试效果!
  • 三、进阶玩法:这些场景也能轻松搞定!
    • 3.1 自定义HTTP状态码(比如404)
    • 3.2 处理表单参数校验异常(@ModelAttribute)
    • 3.3 记录异常日志的细节
  • 四、避坑指南:这些坑别踩!
    • 4.1 全局异常不生效?
    • 4.2 生产环境暴露敏感信息?
    • 4.3 自定义异常没被捕获?
  • 五、总结:全局异常处理的学习路线

    在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 Int编程客栈eger code;  // 状态码(200=成功,400=参数错误,500=系统错误)
        private String msg;    // 提示信息
        private T data;        // 业务数据(可选)
        // 快速构建成功响应(带数据)
        public static <T> Result<T> successhttp://www.devze.com(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;
        js@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=1php(存在用户)时,返回{"code":200,"msg":"操作成功","data":{...}}(正常响应)。

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

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

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

    // 自定义异常(标记为404状态码)
    @ResponseStatus(HttpStatus.NOT_FOUND) 
    public class ResourceNotFoundException extends RuntimeExceptio编程n {
        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全局异常处理内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜