经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot实现统一异常处理
来源:cnblogs  作者:程序员偏安  时间:2024/2/5 9:56:55  对本文有异议

image

前言

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

项目地址: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. }

上述代码会对系统中抛出的BizExceptionRuntimeExceptionException对象进行处理,把异常包装为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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号