【SpirngBoot组件(3)】全局异常捕获

个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充

前言

在未进行全局捕获的情况下,异常有两种处理结果

  1. 主动try...catch捕获,对每种情况进行针对性处理。缺点如下:
    • 代码冗长,需要考虑各种各样的情况,无端增加工作量;
    • 人非完人,不可能考虑完整所有情况,有部分情况甚至无从捕获
  2. 被动处理,不捕获,异常信息最终将会被直接打印到前端。缺点如下:
    • 正常用户根本看不懂异常信息,大量的异常信息对于用户而言跟乱码无异
    • 异常信息会打印出后端的信息,泄露后端数据,造成巨大安全隐患,尤其是sql错误会打印出sql语句,直接暴露表名和字段名
    • 前端仅能判断执行成功与否,而无法根据具体情况作出处理,降低了项目的健壮性

而进行全局异常捕获后,则可在控制器层面对所有异常进行处理,不同异常返回不同信息,与前端约定好数据格式后返回统一格式的数据。

因而,全局异常已经成为项目必要的组件之一。

1.介绍

通常借助注解@ControllerAdvice@ExceptionHandler完成,当有异常被抛至控制层时,便可对其进行统一处理,返回约定好的json格式,或者某个页面

2.集成

  1. 编写好controller层相关代码,此处不做赘述

  2. 创建Controller增强器

    @Slf4j
    @ControllerAdvice
    public class BaseExceptionHandler {
    }
    
    • @Slf4j:标记使用Slf4j日志框架,此处不做赘述
    • @ControllerAdvice:标记当前类为controller蹭强器
  3. 创建异常拦截方法,可创建多个

    • 拦截返回mav对象方法

          @ExceptionHandler({NestedServletException.class})
          @ResponseStatus(HttpStatus.OK)
          @ResponseBody
          public ModelAndView servletException(HttpServletRequest request, HttpServletResponse response, Exception exception) throws ServletException, IOException {
              ModelAndView mav = new ModelAndView();
              ReturnMsg message = ReturnMsg.FAIL;
              out(response, message);
              return mav;
          }
      
      • @ExceptionHandler:参数为拦截的异常类型,可枚举多种类型,但整个controller蹭强器下,一个异常仅被拦截处理一次(按照先后顺序)
      • @ResponseStatus:响应状态,通常用HttpStatus.OK即可
      • @ResponseBody:返回json对象
    • 拦截返回自定义对象方法

      @ExceptionHandler({Exception.class})
      @ResponseStatus(HttpStatus.OK)
      @ResponseBody
      public ReturnMsg processException(HttpServletRequest request, HttpServletResponse response, Exception exception) throws ServletException, IOException {
          // 打印异常信息至控制台,开始处理异常
          log.error("异常统一处理-Exception:" + exception.getLocalizedMessage(), exception);
          //异常默认为是操作失败
          ReturnMsg message = ReturnMsg.FAIL;
          // 检查异常的类型
          if (exception instanceof NestedServletException) {
              // 异步请求错误,已处理
          } else if (exception instanceof BaseException) {
              // 自定义类型的异常,转换为自定义异常
              message = ((BaseException) exception).asReturnMsg();
          } else {
              // 非自定义类型异常,打印错误信息至日志,封装ReturnMsg对象
              log.error(request.getRequestURI(), exception);
              message = ReturnMsg.FAIL;
          }
          //返回消息体
          return message;
      }
      
    • 数据写入response方法

          public static void out(ServletResponse response, ReturnMsg returnMsg) {
              PrintWriter out = null;
              try {
                  response.setContentType("application/json;charset=utf-8");
                  out = response.getWriter();
                  out.println(ObjectMapperFactory.getInstance().writeValueAsString(returnMsg));
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  if (null != out) {
                      out.flush();
                      out.close();
                  }
              }
          }
      

3.使用

  • 需要处理的异常,可以try...catch捕获后处理,也可以直接抛出自定义异常,如throw new BaseException("密码错误"),由controller蹭强器转换为约定的格式后返回前端
  • 无需处理的异常,就不管咯,controller蹭强器处理后返回前端信息,但请自行完善增强器,以处理各种不同情况
  • 异常会被打印到控制台,并移交给日志框架处理,用于线上版本查看日志

4.相关资料

  • 自定义异常及统一数据返回格式:请自行查阅相关资料,实现方案很简单易懂,本人也有做相关笔记

5.补充

  • 请慎用Preconditions及其类似工具检查数据,数据违规时抛出的异常将会和其余运行异常混淆,无法区分,要么放弃此类工具,要么自行寻找解决方案
  • 尽管全局处理了,但异常就是异常,是不正常的情况。前端数据异常请返回数据告知前端,后端问题请尽可能完善代码以规避。异常就是异常,处理了仍然是异常

BB两句

这类框架性组件尽早定下来,当前项目已经大范围使用了Preconditions。。。花了半天没有完美解决方案,只能做出让步进行兼容处理,处女座表示相当难受


作者:Echo_Ye

WX:Echo_YeZ

email :echo_yezi@qq.com

个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

原文地址:https://www.cnblogs.com/silent-bug/p/13563223.html