软件开发过程当中不免遇到各类的BUG,各类的异常,一直就是在解决异常的路上永不停歇,若是你的代码中再出现try(){...}catch(){...}finally{...}
代码块,你还有心情看下去吗?本身不以为恶心吗?java
冗余的代码每每回丧失写代码的动力,天天搬砖似的写代码,真的很难受。今天这篇文章教你如何去掉满屏的try(){...}catch(){...}finally{...}
,解放你的双手。程序员
本文基于的Spring Boot的版本是2.3.4.RELEASE
。web
早在Spring 3.x
就已经提出了@ControllerAdvice
,能够与@ExceptionHandler
、@InitBinder
、@ModelAttribute
等注解注解配套使用,这几个此处就再也不详细解释了。spring
这几个注解小眼一瞟只有@ExceptionHandler
与异常有关啊,翻译过来就是异常处理器
。其实异常的处理能够分为两类,分别是局部异常处理
和全局异常处理
。json
局部异常处理
:@ExceptionHandler
和@Controller
注解搭配使用,只有指定的controller层出现了异常才会被@ExceptionHandler
捕获到,实际生产中怕是有成百上千个controller了吧,显然这种方式不合适。后端
全局异常处理
:既然局部异常处理不合适了,天然有人站出来解决问题了,因而就有了@ControllerAdvice
这个注解的横空出世了,@ControllerAdvice
搭配@ExceptionHandler
完全解决了全局统一异常处理。固然后面还出现了@RestControllerAdvice
这个注解,其实就是@ControllerAdvice
和@ResponseBody
结晶。缓存
Java中的异常就不少,更别说Spring Boot中的异常了,这里再也不根据传统意义上Java的异常进行分类了,而是按照controller
进行分类,分为进入controller前的异常
和业务层的异常
,以下图:markdown
进入controller以前异常通常是javax.servlet.ServletException
类型的异常,所以在全局异常处理的时候须要统一处理。几个常见的异常以下:app
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。值越小,匹配度越高越精准。
全局异常的文章万万千,可以讲清楚的能有几篇呢?只出最精的文章,作最野的程序员,若是以为不错的,关注分享走一波,谢谢支持!!!
另外做者的第一本PDF书籍已经整理好了,由浅入深的详细介绍了Mybatis基础以及底层源码,有须要的朋友公众号码猿技术专栏回复关键词Mybatis进阶
便可获取,目录以下: