SpringBoot项目实现统一异常处理的最佳方案
作者:程序员偏安
前言
近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21
和SpringBoot3
开发,后端使用Spring Security
、JWT
、Spring Data JPA
等技术栈,前端提供了vue
、angular
、react
、uniapp
、微信小程序
等多种脚手架工程。
项目地址:https://gitee.com/breezefaith/fast-alden
在前后端分离的项目开发过程中,我们通常会对数据返回格式进行统一的处理,这样可以方便前端人员取数据,后端发生异常时同样会使用此格式将异常信息返回给前端。本文将介绍在SpringBoot项目中如何实现统一异常处理。
实现步骤
定义统一响应对象类
/** * 响应结果类 * * @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; } }
定义业务异常枚举接口和实现
通常一个系统中的自定义业务异常是可穷举的,可以考虑通过定义枚举的方式来列举所有的业务异常。
首先我们来定义一个异常信息枚举的基类接口。
public interface IBizExceptionEnum { String getCode(); String getMessage(); }
再给出一个常用的异常信息的枚举类,如果有其他业务模块的异常信息,同样可以通过实现IBizExceptionEnum
接口来进行定义。
@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
,代码中主动抛出的异常建议都包装为该类的实例。
/** * 业务异常基类,支持参数化的异常信息 */ @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定义的一个切面,在系统抛出异常后执行。
具体实现代码如下:
/** * 全局异常处理切面 */ @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类来进行简单的测试。
@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项目中实现统一异常处理,如有错误,还望批评指正。
在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。
到此这篇关于SpringBoot项目实现统一异常处理的最佳方案的文章就介绍到这了,更多相关SpringBoot统一异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!