满屏的try-catch,你不瘆得慌?

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 满屏的try-catch,你不瘆得慌?

目录

  • 前言
  • Spring Boot 版本
  • 全局统一异常处理的前世今生
  • Spring Boot的异常如何分类?
  • 如何统一异常处理?
  • 异常匹配的顺序是什么?
  • 总结

前言

软件开发过程中难免遇到各种的BUG,各种的异常,一直就是在解决异常的路上永不停歇,如果你的代码中再出现try(){...}catch(){...}finally{...}代码块,你还有心情看下去吗?自己不觉得恶心吗?

冗余的代码往往回丧失写代码的动力,每天搬砖似的写代码,真的很难受。今天这篇文章教你如何去掉满屏的try(){...}catch(){...}finally{...},解放你的双手。

Spring Boot 版本

本文基于的Spring Boot的版本是2.3.4.RELEASE

全局统一异常处理的前世今生

早在Spring 3.x就已经提出了@ControllerAdvice,可以与@ExceptionHandler@InitBinder@ModelAttribute 等注解注解配套使用,这几个此处就不再详细解释了。

这几个注解小眼一瞟只有@ExceptionHandler与异常有关啊,翻译过来就是异常处理器其实异常的处理可以分为两类,分别是局部异常处理全局异常处理

局部异常处理@ExceptionHandler@Controller注解搭配使用,只有指定的controller层出现了异常才会被@ExceptionHandler捕获到,实际生产中怕是有成百上千个controller了吧,显然这种方式不合适。

全局异常处理:既然局部异常处理不合适了,自然有人站出来解决问题了,于是就有了@ControllerAdvice这个注解的横空出世了,@ControllerAdvice搭配@ExceptionHandler彻底解决了全局统一异常处理。当然后面还出现了@RestControllerAdvice这个注解,其实就是@ControllerAdvice@ResponseBody结晶。

Spring Boot的异常如何分类?

Java中的异常就很多,更别说Spring Boot中的异常了,这里不再根据传统意义上Java的异常进行分类了,而是按照controller进行分类,分为进入controller前的异常业务层的异常,如下图:

进入controller之前异常一般是javax.servlet.ServletException类型的异常,因此在全局异常处理的时候需要统一处理。几个常见的异常如下:

  1. NoHandlerFoundException:客户端的请求没有找到对应的controller,将会抛出404异常。
  2. HttpRequestMethodNotSupportedException:若匹配到了(匹配结果是一个列表,不同的是http方法不同,如:Get、Post等),则尝试将请求的http方法与列表的控制器做匹配,若没有对应http方法的控制器,则抛该异常
  3. HttpMediaTypeNotSupportedException:然后再对请求头与控制器支持的做比较,比如content-type请求头,若控制器的参数签名包含注解@RequestBody,但是请求的content-type请求头的值没有包含application/json,那么会抛该异常(当然,不止这种情况会抛这个异常)
  4. MissingPathVariableException:未检测到路径参数。比如url为:/user/{userId},参数签名包含@PathVariable("userId"),当请求的url为/user,在没有明确定义url为/user的情况下,会被判定为:缺少路径参数

如何统一异常处理?

在统一异常处理之前其实还有许多东西需要优化的,比如统一结果返回的形式。当然这里不再细说了,不属于本文范畴。

统一异常处理很简单,这里以前后端分离的项目为例,步骤如下

  1. 新建一个统一异常处理的一个类
  2. 类上标注@RestControllerAdvice这一个注解,或者同时标注@ControllerAdvice@ResponseBody这两个注解。
  3. 在方法上标注@ExceptionHandler注解,并且指定需要捕获的异常,可以同时捕获多个。

下面是作者随便配置一个demo,如下:

/**
 * 全局统一的异常处理,简单的配置下,根据自己的业务要求详细配置
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 重复请求的异常
     * @param ex
     * @return
     */
    @ExceptionHandler(RepeatSubmitException.class)
    public ResultResponse onException(RepeatSubmitException ex){
        //打印日志
        log.error(ex.getMessage());
        //todo 日志入库等等操作
        //统一结果返回
        return new ResultResponse(ResultCodeEnum.CODE_NOT_REPEAT_SUBMIT);
    }
    /**
     * 自定义的业务上的异常
     */
    @ExceptionHandler(ServiceException.class)
    public ResultResponse onException(ServiceException ex){
        //打印日志
        log.error(ex.getMessage());
        //todo 日志入库等等操作
        //统一结果返回
        return new ResultResponse(ResultCodeEnum.CODE_SERVICE_FAIL);
    }
    /**
     * 捕获一些进入controller之前的异常,有些4xx的状态码统一设置为200
     * @param ex
     * @return
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class, MissingServletRequestParameterException.class,
            ServletRequestBindingException.class, ConversionNotSupportedException.class,
            TypeMismatchException.class, HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            MissingServletRequestPartException.class, BindException.class,
            NoHandlerFoundException.class, AsyncRequestTimeoutException.class})
    public ResultResponse onException(Exception ex){
        //打印日志
        log.error(ex.getMessage());
        //todo 日志入库等等操作
        //统一结果返回
        return new ResultResponse(ResultCodeEnum.CODE_FAIL);
    }
}

注意上面的只是一个例子,实际开发中还有许多的异常需要捕获,比如TOKEN失效过期等等异常,如果整合了其他的框架,还要注意这些框架抛出的异常,比如ShiroSpring Security等等框架。

异常匹配的顺序是什么?

有些朋友可能疑惑了,如果我同时捕获了父类和子类,那么到底能够被那个异常处理器捕获呢?比如ExceptionServiceException

此时可能就疑惑了,这里先揭晓一下答案,当然是ServiceException的异常处理器捕获了,精确匹配,如果没有ServiceException的异常处理器才会轮到它的父亲父亲没有才会到祖父。总之一句话,精准匹配,找那个关系最近的。

为什么呢?这可不是凭空瞎说的,源码为证,出处org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#getMappedMethod,如下:

@Nullable
 private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
  List<Class<? extends Throwable>> matches = new ArrayList<>();
    //遍历异常处理器中定义的异常类型
  for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
      //是否是抛出异常的父类,如果是添加到集合中
   if (mappedException.isAssignableFrom(exceptionType)) {    
        //添加到集合中
    matches.add(mappedException);  
   }
  }
    //如果集合不为空,则按照规则进行排序
  if (!matches.isEmpty()) {
   matches.sort(new ExceptionDepthComparator(exceptionType));
      //取第一个
   return this.mappedMethods.get(matches.get(0));
  }
  else {
   return null;
  }
 }

在初次异常处理的时候会执行上述的代码找到最匹配的那个异常处理器方法,后续都是直接从缓存中(一个Map结构,key是异常类型,value是异常处理器方法)。

别着急,上面代码最精华的地方就是对matches进行排序的代码了,我们来看看ExceptionDepthComparator这个比较器的关键代码,如下:

//递归调用,获取深度,depth值越小越精准匹配
private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
    //如果匹配了,返回
  if (exceptionToMatch.equals(declaredException)) {
   // Found it!
   return depth;
  }
  // 递归结束的条件,最大限度了
  if (exceptionToMatch == Throwable.class) {
   return Integer.MAX_VALUE;
  }
    //继续匹配父类
  return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
 }

精髓全在这里了,一个递归搞定,计算深度,depth初始值为0。值越小,匹配度越高越精准。

总结

全局异常的文章万万千,能够讲清楚的能有几篇呢?只出最精的文章,做最野的程序员,如果觉得不错的,关注分享走一波,谢谢支持!!!

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
搜索推荐 大数据 数据处理
面试官:try-catch 到底写在循环里面好,还是外面好?大部分人都会答错!
面试官:try-catch 到底写在循环里面好,还是外面好?大部分人都会答错!
69 0
|
9月前
|
前端开发 JavaScript
遇事不决,抛一下(一个好玩的东西)
遇事不决,抛一下(一个好玩的东西)
44 0
|
安全 Android开发 开发者
【Android开发小技巧】扔掉这坑人的 Handler
大家都知道 Handler 特别坑,使用不当会造成各种问题,使用 Kotlin Coroutines + Lifecycle 可以很好地替代 Handler。
865 0
|
开发者
试着做点儿有趣的事情
一个游戏怎样才算是做完了?这是个因人而异的问题。有些游戏还没有做出来就做完了,因为开发者不想再做了。有的游戏看上去做完了,但是后续还在不停的更新,那我们就说这个游戏其实还没有做完。至于如何算是做完了,我觉得这应该交由该游戏的制作者来决定。
127 0
一个try-catch问出这么多花样【面试题】
一个try-catch问出这么多花样【面试题】
105 0
一个try-catch问出这么多花样【面试题】
|
前端开发 Java Spring
求求你不要写满屏的 try...catch 了,这才是优雅的处理方式,真香...
求求你不要写满屏的 try...catch 了,这才是优雅的处理方式,真香...
289 0
求求你不要写满屏的 try...catch 了,这才是优雅的处理方式,真香...
朋友,进来刷点 try-catch 看看你能全对吗(答应我,请务必看到最后 🫣)
随着这几年前端的高速发展,前端逻辑的复杂度越来越高,质量要求越来越高,再加上 async、await 的横行,try-catch 在前端的使用率越来越高。然而 try-catch 中可能隐藏着一些不为人知的小秘密㊙️,今天一起通过几道小题目看看这些秘密㊙️。
|
存储 Oracle Java
try-catch-finally中的4个巨坑,老程序员也搞不定!
在 Java 语言中 try-catch-finally 看似简单,一副人畜无害的样子,但想要真正的“掌控”它,却并不是一件容易的事。别的不说,咱就拿 fianlly 来说吧,别看它的功能单一,但使用起来却“暗藏杀机”,若您不信,咱来看下面的这几个例子...
209 0
try-catch-finally中的4个巨坑,老程序员也搞不定!
|
程序员
try-catch-finally中的4个巨坑,老程序员也搞不定!(5)
try-catch-finally中的4个巨坑,老程序员也搞不定!(5)
111 0
try-catch-finally中的4个巨坑,老程序员也搞不定!(5)
|
Java 程序员
try-catch-finally中的4个巨坑,老程序员也搞不定!(6)
try-catch-finally中的4个巨坑,老程序员也搞不定!(6)
145 0
try-catch-finally中的4个巨坑,老程序员也搞不定!(6)