SpringBoot实现统一异常处理

饭宝  金牌会员 | 2024-4-24 07:44:16 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 918|帖子 918|积分 2754

目录


前言

近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21和SpringBoot3开发,后端使用Spring Security、JWT、Spring Data JPA等技术栈,前端提供了vue、angular、react、uniapp、微信小程序等多种脚手架工程。
项目地址:https://gitee.com/breezefaith/fast-alden
在前后端分离的项目开发过程中,我们通常会对数据返回格式进行统一的处理,这样可以方便前端人员取数据,后端发生异常时同样会使用此格式将异常信息返回给前端。本文将介绍在SpringBoot项目中如何实现统一异常处理。
实现步骤

定义统一响应对象类
  1. /**
  2. * 响应结果类
  3. *
  4. * @param <T> 任意类型
  5. */
  6. @Data
  7. public class ResponseResult<T> {
  8.     /**
  9.      * 响应状态码,200是正常,非200表示异常
  10.      */
  11.     private int status;
  12.     /**
  13.      * 异常编号
  14.      */
  15.     private String errorCode;
  16.     /**
  17.      * 异常信息
  18.      */
  19.     private String message;
  20.     /**
  21.      * 响应数据
  22.      */
  23.     private T data;
  24.     public static <T> ResponseResult<T> success() {
  25.         return success(HttpServletResponse.SC_OK, null, null);
  26.     }
  27.     public static <T> ResponseResult<T> success(T data) {
  28.         return success(HttpServletResponse.SC_OK, null, data);
  29.     }
  30.     public static <T> ResponseResult<T> fail(String message) {
  31.         return fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, message, null);
  32.     }
  33.     public static <T> ResponseResult<T> fail(String errorCode, String message) {
  34.         return fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorCode, message, null);
  35.     }
  36.     public static <T> ResponseResult<T> success(int status, String message, T data) {
  37.         ResponseResult<T> r = new ResponseResult<>();
  38.         r.setStatus(status);
  39.         r.setMessage(message);
  40.         r.setData(data);
  41.         return r;
  42.     }
  43.     public static <T> ResponseResult<T> fail(int status, String errorCode, String message) {
  44.         return fail(status, errorCode, message, null);
  45.     }
  46.     public static <T> ResponseResult<T> fail(int status, String errorCode, String message, T data) {
  47.         ResponseResult<T> r = new ResponseResult<>();
  48.         r.setStatus(status);
  49.         r.setErrorCode(errorCode);
  50.         r.setMessage(message);
  51.         r.setData(data);
  52.         return r;
  53.     }
  54. }
复制代码
定义业务异常枚举接口和实现

通常一个系统中的自定义业务异常是可穷举的,可以考虑通过定义枚举的方式来列举所有的业务异常。
首先我们来定义一个异常信息枚举的基类接口。
  1. public interface IBizExceptionEnum {
  2.     String getCode();
  3.     String getMessage();
  4. }
复制代码
再给出一个常用的异常信息的枚举类,如果有其他业务模块的异常信息,同样可以通过实现IBizExceptionEnum接口来进行定义。
  1. @Getter
  2. public enum BizExceptionEnum implements IBizExceptionEnum {
  3.     ENTITY_IS_NULL("Base_Entity_Exception_0001", "实体为空"),
  4.     ENTITY_ID_IS_NULL("Base_Entity_Exception_0002", "实体id字段为空"),
  5.     ENTITY_ID_IS_DUPLCATED("Base_Entity_Exception_0003", "实体id字段%s重复");
  6.     private final String code;
  7.     private final String message;
  8.     BizExceptionEnum(String code, String message) {
  9.         this.code = code;
  10.         this.message = message;
  11.     }
  12. }
复制代码
定义业务异常基类

业务异常基类BizException继承自RuntimeException,代码中主动抛出的异常建议都包装为该类的实例。
  1. /**
  2. * 业务异常基类,支持参数化的异常信息
  3. */
  4. @Getter
  5. @Setter
  6. public class BizException extends RuntimeException {
  7.     private String code;
  8.     private Object[] args;
  9.     public BizException() {
  10.         super();
  11.     }
  12.     public BizException(String message) {
  13.         super(message);
  14.     }
  15.     public BizException(Throwable cause) {
  16.         super(cause);
  17.     }
  18.     public BizException(String message, Throwable cause) {
  19.         super(message, cause);
  20.     }
  21.     public BizException(Throwable cause, String code, String message, Object... args) {
  22.         super(message, cause);
  23.         this.code = code;
  24.         this.args = args;
  25.     }
  26.     public BizException(String code, String message, Object... args) {
  27.         super(message);
  28.         this.code = code;
  29.         this.args = args;
  30.     }
  31.     public BizException(IBizExceptionEnum exceptionEnum, Object... args) {
  32.         this(exceptionEnum.getCode(), exceptionEnum.getMessage(), args);
  33.     }
  34.     public BizException(Throwable cause, IBizExceptionEnum exceptionEnum, Object... args) {
  35.         this(cause, exceptionEnum.getCode(), exceptionEnum.getMessage(), args);
  36.     }
  37.     @Override
  38.     public String getMessage() {
  39.         if (code != null) {
  40.             if (args != null && args.length > 0) {
  41.                 return String.format(super.getMessage(), args);
  42.             }
  43.         }
  44.         return super.getMessage();
  45.     }
  46. }
复制代码
定义全局异常处理切面

本步骤需要使用@RestControllerAdvice注解,它是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。
还要用到@ExceptionHandler注解,可以认为它是一个异常拦截器,它采用“就近原则”,存在多个满足条件的异常处理器时会选择最接近的一个来使用。它本质上就是使用Spring AOP定义的一个切面,在系统抛出异常后执行。
具体实现代码如下:
  1. /**
  2. * 全局异常处理切面
  3. */
  4. @RestControllerAdvice
  5. public class GlobalExceptionHandlerAdvice {
  6.     private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerAdvice.class);
  7.     @ExceptionHandler({BizException.class})
  8.     public ResponseResult<Object> handleBizException(BizException e, HttpServletRequest request, HttpServletResponse response) {
  9.         log.error(e.getCode() + ": " + e.getMessage(), e);
  10.         response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  11.         return ResponseResult.fail(e.getCode(), e.getMessage());
  12.     }
  13.     @ExceptionHandler({RuntimeException.class, Exception.class})
  14.     public ResponseResult<Object> handleRuntimeException(Exception e, HttpServletRequest request, HttpServletResponse response) {
  15.         log.error(e.getMessage(), e);
  16.         response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  17.         return ResponseResult.fail(e.getMessage());
  18.     }
  19. }
复制代码
上述代码会对系统中抛出的BizException、RuntimeException和Exception对象进行处理,把异常包装为ResponseResult对象后将异常编号和异常信息返回给前端。
测试和验证

下面我们就可以定义一个Controller类来进行简单的测试。
  1. @RestController
  2. @RequestMapping("/demo")
  3. public class DemoController {
  4.     @GetMapping("/method1")
  5.     public ResponseResult<Integer> method1() {
  6.         throw new BizException(BizExceptionEnum.ENTITY_IS_NULL);
  7.     }
  8.     @GetMapping("/method2")
  9.     public void method2() {
  10.         throw new BizException(BizExceptionEnum.ENTITY_ID_IS_NULL);
  11.     }
  12.     @GetMapping(value = "/method3")
  13.     public String method3() {
  14.         throw new BizException(BizExceptionEnum.ENTITY_ID_IS_DUPLCATED, "1");
  15.     }
  16.     @GetMapping(value = "/method4")
  17.     public String method4() {
  18.         // 抛出ArithmeticException异常
  19.         return String.valueOf(1 / 0);
  20.     }
  21. }
复制代码
总结

本文介绍了如何在SpringBoot项目中实现统一异常处理,如有错误,还望批评指正。
在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。
出处:https://www.cnblogs.com/breezefaith/p/18006315本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

饭宝

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表