首页 > Spring统一异常处理该如何做
头像
球1个offer
发布于 今天 11:51
+ 关注

Spring统一异常处理该如何做

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. 敏感信息脱敏

生产环境禁止返回堆栈信息、数据库异常等敏感内容,仅返回友好提示;开发环境可开启详细异常信息,便于调试。

四、常见避坑指南

  1. 异常处理器未生效:检查GlobalExceptionHandler是否被Spring扫描(包路径是否在启动类扫描范围内),是否添加@RestControllerAdvice注解。
  2. 异步方法异常无法捕获:@RestControllerAdvice仅拦截Controller层同步异常,异步方法需单独处理(@Async方法可搭配@ExceptionHandler或Future捕获)。
  3. 过滤器/拦截器异常无法捕获:全局处理器仅拦截Controller层异常,过滤器异常需在Filter中手动处理,拦截器异常可通过HandlerInterceptor适配。
  4. 重复捕获异常:避免在业务层try-catch后再次抛出,否则全局处理器无法正常拦截。

核心总结:Spring统一异常处理的核心是@RestControllerAdvice + @ExceptionHandler,搭配统一响应体+自定义异常,实现异常集中管控、格式标准化、代码无侵入,是后端项目的基础规范。

ps:如果这篇帖子对于还在找工作和找实习的你有所帮助,可以关注我,给本贴点赞、评论、收藏并订阅专栏;同时不要吝啬您的花花

全部评论

(1) 回帖
加载中...
话题 回帖

近期热帖

热门推荐