满屏的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. ...
随机推荐
- Unity Plugins的使用方法
一.为插件设置平台的方法 unity5之前,是通过把插件搞到对应目录进行区分平台的(比如在build target是ios平台时只把IOS目录的插件build进去),unity5之后提供了设置平台/c ...
- 公共项目开发:我为什么用 JSDoc,而不用 ts
公共模块,通常会被多个项目.不同的开发人员使用,所以开发公共模块时,你自己会用还不够,要让所有人都能很快的知道怎么去使用,这一点很关键.通常会从3个方面做到这点: 精心分割代码逻辑,遵循开闭原则: 变 ...
- 深入了解Netty【五】线程模型
引言 不同的线程模型对程序的性能有很大的影响,Netty是建立在Reactor模型的基础上,要搞清Netty的线程模型,需要了解一目前常见线程模型的一些概念. 具体是进程还是线程,是和平台或者编程语言 ...
- 跟着兄弟连系统学习Linux-【day03】
day03-20200529 p10.学习注意事项 linux严格区分大小写(与python有点像) Linux中所有内容都是通过文件形式保存,通过命令执行设置参数,写 ...
- 使用Json-lib将对象和Json互转
工程下载地址: https://files.cnblogs.com/files/xiandedanteng/jsonSample20200308.rar Depenency: <!-- 使用js ...
- Springboot中WebMvcConfigurer接口详解
Springboot 使用越来越多,企业的基本框架,到Springcloud分布式,可以说无论面试还是平常技术学习,一说到spring几乎就就代替了Java,可以说spring,springboot的 ...
- 利用预编译解决C/C++重复定义的错误 -2020.09.13
利用预编译解决C/C++重复定义的错误 -2020.09.13 我们现在有main.c和function.h两个文件 main.c #include <stdio.h> #include ...
- charles 入门配置(win10+vivoX20)(Charles一)
charles的几个功能可以参考:https://www.cnblogs.com/mihoutao/p/10601171.html 首先是charles的下载 下载地址:https://www.cha ...
- 小BUG大原理:FastJSON实体转换首字母小写的尴尬事件
问题描述 因为项目连接的Oracle数据库,字段名映射方便使用大写,但是通过接口调用返回到前端的字段名首字母为小写,这样带来的问题前端显示的字段就需要写这种很尴尬的格式. 原因分析 开发环境使用的是S ...
- 《Redis内存数据库》Redis数据类型和基本操作
前言 redis 有多种数据类型,兼容应用的开发. 说明 第一种数据类型:string(字符串) set key value -- 设置key和key对应的value值 get key ...