使用介绍一般自定义异常处理策略有两种方式使用@ExceptionHandler注解实现HandlerExceptionResolver接口,因为@ExceptionHandler注解足够强大,我们一般很少实现HandlerExceptionResolver定义异常处理策略。简单介绍一下@ExceptionHandler的使用,然后根据这些例子分析源码。@RestController@RequestMapping("location")publicclassLocationController{@RequestMapping("getLocationInfo")publicStringindex(){intsum=10/0;return"locationInfo";}@ExceptionHandler(RuntimeException.class)publicStringprocessRuntimeException(){return"LocationController->RuntimeExceptionoccurred";}@ExceptionHandler(Exception.class)publicStringprocessException(){return"LocationController->Exceptionoccurred";}}访问以下链接,返回结果为http://localhost:8080/location/getLocationInfoLocationController->RuntimeException发生。注释掉processRuntimeException方法后,再次访问上面的链接。结果是LocationController->Exceptionoccurs。在每个Controller中编写异常解析器还是很麻烦的,你能在一个地方处理异常吗?当然,你必须使用@RestControllerAdvice或者@ControllerAdvice来编写如下全局异常解析器@RestControllerAdviception";}@ExceptionHandler(Exception.class)publicStringprocessException(){return"MyExceptionHandler->RuntimeExceptionoccurred";}}访问上面的链接,返回结果为LocationController->Exceptionoccurred。我们也注释掉了其中的processException方法LocationController类,此时LocationController类中没有@ExceptionHandler注解标注的方法。访问上面链接,返回结果为MyExceptionHandler->RuntimeException发生。注释掉MyExceptionHandler中的processRuntimeException方法。访问上面链接,返回结果为MyExceptionHandler->Exceptionoccursthrough从上面的例子,我们可以得出如下结论:@RestControllerAdvice或@ControllerAdvice类解析器的优先级低于@RequestMapping类解析器如果一个异常可以被多个解析器处理,选择继承关系最近的解析器假定BizException继承自NullPointExceptionA方法解析BizExceptionB方法解析NullPointExceptionC方法解析ExceptionBizException会被A方法解析NullPointException会被B方法解析如果没有A方法,BizException会被B方法解析,如果B方法都没有,则解析通过C方法。不难理解@RestControllerAdvice和@ControllerAdvice的区别是什么?看名字就可以猜到@RestControllerAdvice只是在@ControllerAdvice的基础上增加了@ResponseBody注解。看一波源码还真是。@RestControllerAdvice类最终返回JSON,@ControllerAdvice最终返回一个视图。如果不明白为什么要加@ResponseBody注解,最后返回的内容是JSON,建议看一下返回值处理器相关内容的源码分析。,HttpServletResponseresponse,@NullableObjecthandler,Exceptionex);}SpringMVC默认的异常解析器存储在以下属性中:@NullableprivateListhandlerExceptionResolvers;order是ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerException是SpringMVCResolverUML中排序的如下接口解析器并没有通过Order接口来控制顺序,因为默认解析器继承自AbstractHandlerExceptionResolver,并没有重写getOrder方法。对SpringMVC比较熟悉的朋友应该知道,在源码包中定义了DispatcherServlet属性的默认实现。在DispatcherServlet.properties文件中,List的顺序也是按照这个来的。放一些内容org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvcra.annotation.ResponseStatusException.orgresolver,.servlet.mvc.support.DefaultHandlerExceptionReso接下来分析三个默认的HandlerExceptionResolverExceptionHandlerExceptionResolverExceptionHandlerExceptionResolver来支持@ExceptionHandler,而@ExceptionHandler应该是我们最常用的一个,方便我们自定义异常处理策略,比实现HandlerExceptionResolver接口简单。AbstractHandlerMethodExceptionResolver#shouldApplyTo可以看到@OverrideprotectedbooleanshouldApplyTo(HttpServletRequestrequest,@NullableObjecthandler){if(handler==null){//handler为空,交给父类判断//默认这个逻辑返回truereturnsuper.shouldApplyTo(request,null);}elseif(handlerinstanceofHandlerMethod){HandlerMethodhandlerMethod=(HandlerMethod)handler;handler=handlerMethod.getBean();//给父类判断returnsuper.shouldApplyTo(request,handler);}else{//不支持returnfalse;}}只有当handler为空或者handler的类型为HandlerMethod(@RequestMapping返回的类型为HandlerMethod)时才会执行后续的异常解析逻辑所以如果你实现了Controller接口或者HttpRequestHandler接口定义的Handler,这个注解就不起作用了。Theprocessingprocessof@ExceptionHandlerismainlyrelatedtothefollowingtwoclasses.过多的代码了ExceptionHandlerExceptionResolver//省略了继承和实现关系publicclassExceptionHandlerExceptionResolver{@NullableprivateHandlerMethodArgumentResolverCompositeargumentResolvers;@NullableprivateHandlerMethodReturnValueHandlerCompositereturnValueHandlers;privateList>messageConverters;//被@RequestMapping标记的类->ExceptionHandlerMethodResolverprivatefinalMap,ExceptionHandlerMethodResolver>exceptionHandlerCache=newConcurrentHashMap<>(64);//@ControllerAdvice注解标记的类->ExceptionHandlerMethodResolverprivatefinalMapexceptionHandlerMap<>LinkedHandlerAdvice();可以看到类返回了处理器的值,定义了自己的ExceptionHandlerExceptionResolverProcessor,消息转换器。所以你可以通过这些组件知道@ExceptionHandler方法支持的参数类型。>getDefaultArgumentResolvers(){Listresolvers=newArrayList<>();//基于注解的参数resolutionresolvers.add(newSessionAttributeMethodArgumentResolver());resolvers.add(newRequestAttributeMethodArgumentResolver());//基于类型的参数resolutionresolvers.add(newServletRequestMethodArgumentResolver());resolvers.add(newServletResponseMethodArgumentResolver());resolvers.add(newRedirectAttributesMethodArgumentResolver());resolvers.add(newModelMethodProcessor());//Customargumentsif(getCustomArgumentResolvers()!=null){resolvers.addAll(getCustomArgument();Resolver)}returnresolvers;}最重要的4张地图在这里。ExceptionHandlerExceptionResolver的工作过程主要是操作这4个map//省略继承和实现关系publicclassExceptionHandlerExceptionResolver{//@RequestMapping->ExceptionHa标记的类ndlerMethodResolverprivatefinalMap,ExceptionHandlerMethodResolver>exceptionHandlerCache=newConcurrentHashMap<>(64);//被@ControllerAdvice注解标记的类->ExceptionHandlerMethodResolverprivatefinalMapexceptionHandlerAdviceCache=newLinkedHashMap<>();}exceptionHandlerCache保存了@RequestMapping对应ExceptionHandlerMethodResolver在执行异常解析时被赋值。exceptionHandlerAdviceCache保存了@ControllerAdvice对应的ExceptionHandlerMethodResolver,在ExceptionHandlerExceptionResolver初始化时赋值。你可以认为ExceptionHandlerMethodResolver只是对Exception及其对应的Method进行了封装,第一个例子演示了ExceptionHandlerExceptionResolver初始化后,此时exceptionHandlerCache没有任何价值。访问如下链接后,http://localhost:8080/location/getLocationInfoexceptionHandlerCache中的值如下,将LocationController及其对应的ExceptionHandlerMethodResolver放入,跟随下面的方法。执行ExceptionHandlerExceptionResolver#doResolveHandlerMethodExceptionExceptionHandlerExceptionResolver#getExceptionHandlerMethod可以得出我们测试的结论,@RestControllerAdvice或@ControllerAdvice类中解析器的优先级低于@RequestMapping类。没有获取就从exceptionHandlerAdviceCache中查找,这不是优先级吗?然后看剩下的2个MappublicclassExceptionHandlerMethodResolver{//abnormal->对应的处理方法privatefinalMap,Method>mappedMethods=newHashMap<>(16);//Exception->对应的处理方法//这是另外一个基于缓存的onmappedMethods//为什么要另外做一个缓存?//因为根据异常类型获取处理方法时,一个异常可能有多个处理方法,即一个异常会从mappedMethods中找到多个处理方法//最后是继承最近的异常对应的处理方法relationship返回,所以我在查找的时候又做了一次缓存,避免每次都检查mappedMethods,然后取最优值//可以直接从exceptionLookupCache中找到最优的处理方式privatefinalMap,Method>exceptionLookupCache=newConcurrentReferenceHashMap<>(16);}@ControllerAdvice的mappedMethods是在ExceptionHandlerExceptionResolver初始化的时候赋值的。@RequestMapping的mappedMethods是在执行异常解析时赋值的,exceptionLookupCache是??异常解析时通过Exception查找Method的过程。为什么基于mappedMethods的缓存在搜索过程中需要再次缓存?因为根据异常类型获取处理方法时,一个异常可能有多个处理方法,即会从mappedMe中获取一个异常thods中检测到多个处理方法,最后返回继承关系最近的异常对应的处理方法,所以在查找的时候又做了一次缓存,避免每次都去检查mappedMethods,然后取最优值可以直接从exceptionLookupCache中找到最优的处理方式。以LocationController为例,发现异常后,exceptionLookupCache的值如下,这样再次出现ArithmeticException时,可以从exceptionLookupCache中找到对应的处理方法。ResponseStatusExceptionResolverResponseStatusExceptionResolver和DefaultHandlerExceptionResolver都没有实现很难,就不做过多分析了。ResponseStatusExceptionResolver主要用于处理以下异常。抛出的异常类型继承自ResponseStatusException。ResponseStatus抛出的异常类型用@ResponseStatus标示,举例说明这个处理器的功能)publicStringindex(){thrownewUnauthorizedException();}}访问http://localhost:8080/shoppingDefultRes处理一些常见的Http异常,比如400:请求无效405:请求方法不支持500:Internalservererror执行一些methodsoftheentry#DispatcherServlet#processDispatchResult//处理过程中发生异常if(exception!=null){if(exceptioninstanceofModelAndViewDefiningException){logger.debug("ModelAndViewDefiningExceptionencountered",exception);//直接使用异常中封装的ModelAndView作为最终的ModelAndView结果mv=((ModelAndViewDefiningException)exception).getModelAndView();}else{//其他异常类型,先获取解析器Objecthandler=(mappedHandler!=null?mappedHandler.getHandler():null);//通过异常解析器将异常解析成错误视图mv=processHandlerException(request,response,handler,exception);errorView=(mv!=null);}}如果整个处理过程出现异常,依次调用DispatcherServlet的成员变量handlerExceptionResolvers的resolveException方法,找到第一个不为null的ModelAndView,然后返回@NullableprivateListhandlerExceptionResolvers;可以关注转载这个文章通过以下二维码,请联系爪哇石塘公众号。