目录

前言
近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于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项目中实现统一异常处理,如有错误,还望批评指正。
在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。
出处:https://www.cnblogs.com/breezefaith/p/18006315本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |