SpringBoot实现统一异常处理

目录

前言

近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21和SpringBoot3开发,后端使用Spring Security、JWT、Spring Data JPA等技术栈,前端提供了vue、angular、react、uniapp、微信小程序等多种脚手架工程。

项目地址:https://gitee.com/breezefaith/fast-alden

在前后端分离的项目开发过程中,我们通常会对数据返回格式进行统一的处理,这样可以方便前端人员取数据,后端发生异常时同样会使用此格式将异常信息返回给前端。本文将介绍在SpringBoot项目中如何实现统一异常处理。

实现步骤

定义统一响应对象类

java 复制代码
/**
 * 响应结果类
 *
 * @param <T> 任意类型
 */
@Data
public class ResponseResult<T> {
    /**
     * 响应状态码,200是正常,非200表示异常
     */
    private int status;
    /**
     * 异常编号
     */
    private String errorCode;
    /**
     * 异常信息
     */
    private String message;
    /**
     * 响应数据
     */
    private T data;

    public static <T> ResponseResult<T> success() {
        return success(HttpServletResponse.SC_OK, null, null);
    }

    public static <T> ResponseResult<T> success(T data) {
        return success(HttpServletResponse.SC_OK, null, data);
    }

    public static <T> ResponseResult<T> fail(String message) {
        return fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, message, null);
    }

    public static <T> ResponseResult<T> fail(String errorCode, String message) {
        return fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorCode, message, null);
    }

    public static <T> ResponseResult<T> success(int status, String message, T data) {
        ResponseResult<T> r = new ResponseResult<>();
        r.setStatus(status);
        r.setMessage(message);
        r.setData(data);

        return r;
    }

    public static <T> ResponseResult<T> fail(int status, String errorCode, String message) {
        return fail(status, errorCode, message, null);
    }

    public static <T> ResponseResult<T> fail(int status, String errorCode, String message, T data) {
        ResponseResult<T> r = new ResponseResult<>();
        r.setStatus(status);
        r.setErrorCode(errorCode);
        r.setMessage(message);
        r.setData(data);
        return r;
    }

}

定义业务异常枚举接口和实现

通常一个系统中的自定义业务异常是可穷举的,可以考虑通过定义枚举的方式来列举所有的业务异常。

首先我们来定义一个异常信息枚举的基类接口。

java 复制代码
public interface IBizExceptionEnum {
    String getCode();

    String getMessage();
}

再给出一个常用的异常信息的枚举类,如果有其他业务模块的异常信息,同样可以通过实现IBizExceptionEnum接口来进行定义。

java 复制代码
@Getter
public enum BizExceptionEnum implements IBizExceptionEnum {
    ENTITY_IS_NULL("Base_Entity_Exception_0001", "实体为空"),
    ENTITY_ID_IS_NULL("Base_Entity_Exception_0002", "实体id字段为空"),
    ENTITY_ID_IS_DUPLCATED("Base_Entity_Exception_0003", "实体id字段%s重复");

    private final String code;
    private final String message;

    BizExceptionEnum(String code, String message) {
        this.code = code;
        this.message = message;
    }
}

定义业务异常基类

业务异常基类BizException继承自RuntimeException,代码中主动抛出的异常建议都包装为该类的实例。

java 复制代码
/**
 * 业务异常基类,支持参数化的异常信息
 */
@Getter
@Setter
public class BizException extends RuntimeException {
    private String code;
    private Object[] args;

    public BizException() {
        super();
    }

    public BizException(String message) {
        super(message);
    }

    public BizException(Throwable cause) {
        super(cause);
    }

    public BizException(String message, Throwable cause) {
        super(message, cause);
    }

    public BizException(Throwable cause, String code, String message, Object... args) {
        super(message, cause);
        this.code = code;
        this.args = args;
    }

    public BizException(String code, String message, Object... args) {
        super(message);
        this.code = code;
        this.args = args;
    }

    public BizException(IBizExceptionEnum exceptionEnum, Object... args) {
        this(exceptionEnum.getCode(), exceptionEnum.getMessage(), args);
    }

    public BizException(Throwable cause, IBizExceptionEnum exceptionEnum, Object... args) {
        this(cause, exceptionEnum.getCode(), exceptionEnum.getMessage(), args);
    }

    @Override
    public String getMessage() {
        if (code != null) {
            if (args != null && args.length > 0) {
                return String.format(super.getMessage(), args);
            }
        }
        return super.getMessage();
    }
}

定义全局异常处理切面

本步骤需要使用@RestControllerAdvice注解,它是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。

还要用到@ExceptionHandler注解,可以认为它是一个异常拦截器,它采用"就近原则",存在多个满足条件的异常处理器时会选择最接近的一个来使用。它本质上就是使用Spring AOP定义的一个切面,在系统抛出异常后执行。

具体实现代码如下:

java 复制代码
/**
 * 全局异常处理切面
 */
@RestControllerAdvice
public class GlobalExceptionHandlerAdvice {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerAdvice.class);

    @ExceptionHandler({BizException.class})
    public ResponseResult<Object> handleBizException(BizException e, HttpServletRequest request, HttpServletResponse response) {
        log.error(e.getCode() + ": " + e.getMessage(), e);
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return ResponseResult.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler({RuntimeException.class, Exception.class})
    public ResponseResult<Object> handleRuntimeException(Exception e, HttpServletRequest request, HttpServletResponse response) {
        log.error(e.getMessage(), e);
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return ResponseResult.fail(e.getMessage());
    }
}

上述代码会对系统中抛出的BizException、RuntimeException和Exception对象进行处理,把异常包装为ResponseResult对象后将异常编号和异常信息返回给前端。

测试和验证

下面我们就可以定义一个Controller类来进行简单的测试。

java 复制代码
@RestController
@RequestMapping("/demo")
public class DemoController {
    @GetMapping("/method1")
    public ResponseResult<Integer> method1() {
        throw new BizException(BizExceptionEnum.ENTITY_IS_NULL);
    }

    @GetMapping("/method2")
    public void method2() {
        throw new BizException(BizExceptionEnum.ENTITY_ID_IS_NULL);
    }

    @GetMapping(value = "/method3")
    public String method3() {
        throw new BizException(BizExceptionEnum.ENTITY_ID_IS_DUPLCATED, "1");
    }

    @GetMapping(value = "/method4")
    public String method4() {
        // 抛出ArithmeticException异常
        return String.valueOf(1 / 0);
    }
}

总结

本文介绍了如何在SpringBoot项目中实现统一异常处理,如有错误,还望批评指正。

在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。

相关推荐
头发还在的女程序员几秒前
Java在线租房系统源码解析:Spring Boot+Vue 3+UniApp房源管理与预约看房方案
java·vue.js·spring boot·租房小程序·租房系统·租房软件
FYKJ_20108 分钟前
springboot同人周边订单管理系统48955-计算机毕业设计、课程设计
java·javascript·vue.js·spring boot·django·php·课程设计
Flynn Liang31 分钟前
LLM 应用安全:Prompt 注入、越权与输入输出护栏
java·人工智能·大模型·langchain4j·llm 安全·prompt 注入·guardrail
特立独行的猫A6 小时前
用仓颉语言 cj-tauri 框架,五分钟给娃做了个鸿蒙形近字游戏
java·后端·python
码事漫谈9 小时前
我花了一周时间,看AI公司怎么"管"AI,结果越看越后背发凉
后端
爱学习的程序媛12 小时前
Python常用框架梳理
开发语言·后端·python·ai·web·后端框架·python框架
GISer_Jing12 小时前
Agent核心内容
后端·ai·langchain
budingxiaomoli13 小时前
Spring统一功能处理
java·后端·spring
某鹏14 小时前
网关选型:Go、Java、Graal 谁都不能完胜
后端
用户02478365309714 小时前
从屎山到防线:14 行状态迁移表,给任务系统装上防瞎跳闸门
后端