一、媒介

置信我们每一个人在SpringMVC开辟中,都碰到如许的题目:当我们的代码一般运转时,返回的数据是我们预期花样,好比json或xml情势,然则一旦涌现了非常(好比:NPE或许数组越界等等),返回的内容确切效劳端的非常客栈信息,从而致使返回的数据不能使客户端一般剖析; 很显然,这些并非我们愿望的结果。

我们晓得,一个较为罕见的体系,会触及掌握层,效劳(营业)层、缓存层、存储层以及接口挪用等,个中每一个环节都弗成避免的会碰到种种弗成预知的非常须要处置惩罚。若是每一个步调都零丁try..catch会使体系显的很芜杂,可读性差,保护本钱高;罕见的体式格局就是,完成一致的非常处置惩罚,从而将各种非常从各个模块中解耦出来;

二、罕见全局非常处置惩罚

在Spring中罕见的全局非常处置惩罚,重要有三种:

(1)注解ExceptionHandler

(2)继承HandlerExceptionResolver接口

(3)注解ControllerAdvice

在后面的解说中,重要以HTTP毛病码:400(要求无效)和500(内部效劳器毛病)为例,先看一下测试代码以及没有任何处置惩罚的返回结果,以下:

(图1:测试代码)

(图2:没有非常的毛病返回)

2.1 注解ExceptionHandler

注解ExceptionHandler作用对象为要领,最简朴的运用要领就是放在controller文件中,细致的注解界说不再引见。若是项目中有多个controller文件,一般能够在baseController中完成ExceptionHandler的非常处置惩罚,而各个contoller继承basecontroller从而到达一致非常处置惩罚的目标。因为对照罕见,简朴代码以下:

(图3:Controller中的ExceptionHandler运用)

在返回非常时,增加了所属的类名,便于人人影象明白。运转看一下结果:

(图4:增加ExceptionHandler以后的结果) 

  • 长处:ExceptionHandler简朴易懂,而且关于非常处置惩罚没有限定要领花样;

  • 瑕玷:因为ExceptionHandler仅作用于要领,关于多个controller的状况,仅为了一个要领,一切须要非常处置惩罚的controller都继承这个类,显着不相关的器械,强行给他们找个爹,不太好。

2.2 注解ControllerAdvice

这里虽说是ControllerAdvice注解,实际上是其与ExceptionHandler的组合运用。在上文中能够看到,零丁运用@ExceptionHandler时,其必须在一个Controller中,但是当其与ControllerAdvice组合运用时就完整没有了这个限定。换句话说,二者的组合到达的全局的非常捕捉处置惩罚。

(图5:注解ControllerAdvice非常处置惩罚代码)

在运转之前,需将之前Controller中的ExceptionHandler解释掉,测试结果以下:

(图6:注解ControllerAdvice非常处置惩罚结果) 

经由过程上面结果能够看到,非常处置惩罚确切已变更为ExceptionHandlerAdvice类。这类要领将一切的非常处置惩罚整合到一处,去除Controller中的继承干系,而且到达了全局捕捉的结果,引荐运用此类体式格局;

2.3 完成HandlerExceptionResolver接口

HandlerExceptionResolver自身SpringMVC内部的接口,其内部只要resolveException一个要领,经由过程完成该接口我们能够到达全局非常处置惩罚的目标。

(图7:完成HandlerExceptionResolver接口)

同样在实行之前,将上述两个要领的非常处置惩罚都解释掉,运转结果以下:

(图8:完成HandlerExceptionResolver接口运转结果) 

能够看到500的非常处置惩罚已见效了,然则400的非常处置惩罚却没有见效,而且根没有非常前的返回结果一样。这是怎么回事呢?不是说能够做到全局非常处置惩罚的么?没办法要想晓得题目标缘由,我们只能寻根究底,往Spring的祖坟上刨,下面我们连系Spring的源码调试,去须要缘由。

三、Spring中非常处置惩罚源码剖析

人人都晓得,在Spring中第一个收到要求的类就是DispatcherServlet,而该类中中心的要领就是doDispatch,我们能够在该类中打断点,进而一步步跟进非常处置惩罚。

3.1 HandlerExceptionResolver完成类处置惩罚流程

参照以下的跟进步调,在processHandlerException中断点,跟踪的结果以下图:

(图9:processHandlerException断点) 

能够看到在图中箭头【1】处,在遍历 handlerExceptionResolvers 进而来处置惩罚非常,而在箭头【2】处,看到handlerExceptionResolvers 中共有4个元素,个中末了一个就是2.3要领界说的非常处置惩罚类

以后的要求query要求,依据上述征象能够推测出,该非常处置惩罚应该是在前3个非常处置惩罚中被处置惩罚了,从而跳过我们自界说的非常;带着如许的预测,我们F8继承跟进,能够跟踪到该非常是被第三个,即DefaultHandlerExceptionResolver所处置惩罚。

  • DefaultHandlerExceptionResolver :SpringMVC默许装配了DefaultHandlerExceptionResolver,该类的doResolveException要领中重要对一些特别的非常举行处置惩罚,并将这类非常转换为相应的相应状况码。而query要求触发的非常为MissingServletRequestParameterException,其正好也是被DefaultHandlerExceptionResolver所针对的非常,故会在该类中被非常捕捉。

到此水落石出了,能够看到我们的自界说类MyHandlerExceptionResolver确切能够做到全局处置惩罚非常,只不过关于query要求的非常,中心被DefaultHandlerExceptionResolver插了一脚,以是就跳过了MyHandlerExceptionResolver类的处置惩罚,从而涌现400的返回结果。而关于calc要求,中心没有阻止,以是就到达了预期结果。

3.2 三类非常的处置惩罚递次

到此我们一共引见了3类全局非常处置惩罚,依照上面的剖析能够看出,完成HandlerExceptionResolver接口的体式格局是排在末了处置惩罚,那末@ExceptionHandler和@ControllerAdvice这两个的递次谁先谁后呢? 将三类非常处置惩罚悉数翻开(之前解释掉了),运转一下看看结果:

(图10:非常处置惩罚全摊开运转结果) 

经由过程征象能够看到,Controller中零丁@ExceptionHandle非常处置惩罚排在了首位,@ControllerAdvice排在了第二位。严谨的童鞋能够写个Controller02,将query和calc复制曩昔,非常处置惩罚就不要了,如许要求c02的要领时,非常捕捉的所属类名就都是@ControllerAdvice地点类了。

以上都是我们依据征象获得的结论,下面去Spring源码去找“证据”。在图9中,handlerExceptionResolvers中有4类处置惩罚器,而@ExceptionHandler和@ControllerAdvice的处置惩罚就在第一个ExceptionHandlerExceptionResolver中(之前断点跟进便可获知)。继承跟进直到进入ExceptionHandlerExceptionResolver类的doResolveHandlerMethodException要领,这里的HandlerMethod就是Spring将HTTP要求映射到指定Controller中的要领,而Exception就是须要被捕捉的非常;继承跟进,看看运用这两个参数究竟干了甚么事儿。

(图11:doResolveHandlerMethodException断点) 

继承跟进getExceptionHandlerMethod要领,发现有两个变量能够就是题目标症结:exceptionHandlerCache和exceptionHandlerAdviceCache。起首,二者的变量名很值得疑心;其次,前者在代码中看,显着是经由过程类作为key,从而获得一个处置惩罚器(resolver),这正好Controller中@ExceptionHandler处置惩罚划定规矩相吻合;末了,这两个Cache的处置惩罚递次,也相符之前的获得的结论。正如之前预测的那样,Spring中确切是优先依据Controller类名去查找对应的ExceptionHandler,没有找到的话,再举行@ControllerAdvice非常处置惩罚。

(图12:两个非常处置惩罚Cache )

若有兴致可继承深切发掘Spring的源码,这里针对 ExceptionHandlerExceptionResolver 简朴做个总结:

  • exceptionHandlerCache中包罗Controller中的ExceptionHandler非常处置惩罚,处置惩罚时经由过程HandlerMethod获得Controller,进而再找到非常处置惩罚要领,须要注重的是,其是在非常处置惩罚过程当中put值的;

  • exceptionHandlerAdviceCache则是在项目启动时初始化的,也许思绪是找到带有@ControllerAdvice注解的bean,从而缓存bean中的ExceptionHandler,在非常处置惩罚时须要对齐遍历查找处置惩罚,进而到达全局处置惩罚的目标。

3.3 咸鱼翻身

引见了这么多,简朴画张图总结一下。蓝色的局部是Spring默许增加的3类非常处置惩罚器,黄色局部是我们增加的非常处置惩罚以及其所被挪用的地位和递次。看看那里另有不太清晰的,往回翻翻看(ResponseStatusExceptionResolver是针对@ResponseStatus注解,这里不再胪陈)。

(图13:非常总结)

若是有须要将MyHandlerExceptionResolver提早处置惩罚,以至排在ExceptionHandlerExceptionResolver之前,能做到么?谜底是一定的,在Spring中若是想将MyHandlerExceptionResolver非常处置惩罚提早,须要再完成一个Ordered接口,完成内里的getOrder要领便可,这里返回-1,将其放在最上面,此次咸鱼终究能够翻身了。

(图14:完成Ordered接口)

运转看一下结果是否是相符预期,提示一下,我们三个非常处置惩罚都是见效的,以下图: 

(图15:完成Ordered接口运转结果)

四、总结

本文重要经由过程引见SpringMVC中三类罕见的全局非常处置惩罚,在调试中发现了题目,进而引发去Spring源码中去探讨缘由,终究解决题目,愿望人人能有所收成。固然Spring非常处置惩罚类不止引见的这些,有兴致的童鞋请自行探究!

参考链接:

[1] http://www.cnblogs.com/fangjian0423/p/springMVC-request-mapping.html

[2]https://blog.csdn.net/mll999888/article/details/77621352

作者:张远航

Last modification:March 25, 2020
如果觉得我的文章对你有用,请随意赞赏