满屏的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类型的异常,因此在全局异常处理的时候需要统一处理。几个常见的异常如下:
NoHandlerFoundException:客户端的请求没有找到对应的controller,将会抛出404异常。HttpRequestMethodNotSupportedException:若匹配到了(匹配结果是一个列表,不同的是http方法不同,如:Get、Post等),则尝试将请求的http方法与列表的控制器做匹配,若没有对应http方法的控制器,则抛该异常HttpMediaTypeNotSupportedException:然后再对请求头与控制器支持的做比较,比如content-type请求头,若控制器的参数签名包含注解@RequestBody,但是请求的content-type请求头的值没有包含application/json,那么会抛该异常(当然,不止这种情况会抛这个异常)MissingPathVariableException:未检测到路径参数。比如url为:/user/{userId},参数签名包含@PathVariable("userId"),当请求的url为/user,在没有明确定义url为/user的情况下,会被判定为:缺少路径参数
如何统一异常处理?
在统一异常处理之前其实还有许多东西需要优化的,比如统一结果返回的形式。当然这里不再细说了,不属于本文范畴。
统一异常处理很简单,这里以前后端分离的项目为例,步骤如下:
新建一个统一异常处理的一个类 类上标注 @RestControllerAdvice这一个注解,或者同时标注@ControllerAdvice和@ResponseBody这两个注解。在方法上标注 @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失效、过期等等异常,如果整合了其他的框架,还要注意这些框架抛出的异常,比如Shiro,Spring Security等等框架。
异常匹配的顺序是什么?
有些朋友可能疑惑了,如果我同时捕获了父类和子类,那么到底能够被那个异常处理器捕获呢?比如Exception和ServiceException。

此时可能就疑惑了,这里先揭晓一下答案,当然是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。值越小,匹配度越高越精准。
总结
全局异常的文章万万千,能够讲清楚的能有几篇呢?只出最精的文章,做最野的程序员,如果觉得不错的,关注分享走一波,谢谢支持!!!

满屏的try-catch,不瘆得慌?的更多相关文章
- 利用snowfall.jquery.js实现爱心满屏飞
小颖在上一篇一步一步教你用CSS画爱心中已经分享一种画爱心的方法,这次再分享一种方法用css画爱心,并利用snowfall.jquery.js实现爱心满屏飞的效果. 第一步: 利用伪元素before和 ...
- Android 底部弹出Dialog(横向满屏)
项目中经常需要底部弹出框,这里我整理一下其中我用的比较顺手的一个方式(底部弹出一个横向满屏的dialog). 效果图如下所示(只显示关键部分): 步骤如下所示: 1.定义一个dialog的布局(lay ...
- VMware Tools的简易安装---解决Ubuntu 14.10不能满屏显示问题
由于使用的VMware WorkStation是中文破解版,安装时又是简易安装,因此VMware Tools并没有安装上,导致Ubuntu 14.10在VMware中装上之后,并不能满屏显示,如图1所 ...
- css背景图片拉伸 以及100% 满屏显示
如何用css背景图片拉伸 以及100% 满屏显示呢?这个问题听起来似乎很简单.但是很遗憾的告诉大家.不是我们想的那么简单. 比如一个容器(body,div,span)中设定一个背景.这个背景的长宽值在 ...
- 消除PyCharm中满屏的波浪线
PyCharm使用了较为严格的PEP8的检查规则,如果代码命名不规范,甚至多出的空格都会被波浪线标识出来,导致整个编辑器里铺满了波浪线,右边的滚动条也全是黄色或灰色的标记线,很是影响编辑. 在网上看了 ...
- 一款jQuery满屏自适应焦点图切换特效
一款jQuery满屏自适应焦点图切换特效 ,自适应当前浏览器的宽度,可以作为网站整个大背景的却换效果,很不错的一款不jquery特效. 兼容性没的说直接秒杀了IE6.适用浏览器:IE6.IE7.IE8 ...
- Android简易实战教程--第四十九话《满屏拖动的控件》
今天做个有意思的效果吧,控件的拖拽,简单实用,逻辑清晰点3分钟看完. 说的很高大上,其实就是拖动Button按钮跟着鼠标位置满手机屏幕跑罢了. 直接上简单的代码吧: public class Main ...
- Easyui layout设置满屏效果
html文件: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" ...
- 第145天:jQuery.touchSlider触屏满屏左右滚动幻灯片
1.HTML <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www. ...
随机推荐
- Oracle12C创建scott账户
Oracle12C创建scott账户 具体步骤: 打开SqlPlus 输入用户名和密码用户名:sys口令:sys as sysdba 创建c##scott用户CREATE USER c##scott ...
- java中变量在内存的位置
package day02; /* * 成员变量:在堆内存中,因为对象的存在,才在内存中存在:作用于整改类中 * 局部变量:在栈内存中:作用于函数中,或者语句中 * */ class car{ //描 ...
- MyBatis动态SQL(使用)整理
MyBatis 令人喜欢的一大特性就是动态 SQL.在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的.MyBatis 动态 SQL 的出现, 解决了这个麻烦. MyBa ...
- ubuntu安装docker-ce 、docker-ce-cli、containerd.io
问题 ubuntu安装docker的时候特别慢,百度搜了一大堆都没讲到点子上,最后请教了大佬才知道是源的问题 安装 修改源 sudo gedit /etc/apt/sources.list 添加源 阿 ...
- leetcode刷题-43字符串相乘
题目 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式. 思路 字符串转数字:从字符串第一位开始取,每次取出的值转换为数字 ...
- 阿里云短信服务验证码封装类 - PHP
本文记录在ThinkPHP6.0中使用阿里云短信验证码,该封装类不仅仅局限于TP,拿来即用 使用该类之前必须引入 flc/dysms 扩展,该封装类就是基于这个扩展写的 composer requir ...
- Blocks(POJ 3734)
原题如下: Blocks Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 8020 Accepted: 3905 Desc ...
- 不定方程(Exgcd)
#include<cstdio> using namespace std; int x,y; inline int abs(int a){return a>?a:-a;} int e ...
- PowerJob 应对庞大任务的锦囊妙计:MapReduce
本文适合有 Java 基础知识的人群 作者:HelloGitHub-Salieri HelloGitHub 推出的<讲解开源项目>系列.讲解 PowerJob 系列即将接近尾声,本系列的干 ...
- grpc服务如何添加sentry监控(添加中间件)
目录 需求 解决 需求 sentry是一款非常好用的工具,可以方便追踪线上的异常,在gin框架里边可以非常方便的使用Use添加中件间,grpc服务在网上搜索了一堆没一个能用的,只能硬着头皮看源码 终于 ...