ExceptionHandler的作用
ExceptionHandler是Spring框架提供的一个注解,用于解决应用程序中的异样。当应用程序中产生异样时,ExceptionHandler将优先地拦挡异样并解决它,而后将处理结果返回到前端。该注解可用于类级别和办法级别,以捕捉不同级别的异样。
在Spring中应用ExceptionHandler非常简单,只需在须要捕捉异样的办法上注解@ExceptionHandler,而后定义一个办法,该办法将接管异样并返回异样信息,并将该异样信息展现给前端用户。
ExceptionHandler的应用
阐明:针对可能出问题的Controller,新增注解办法@ExceptionHandler,上面是一个根本的ExceptionHandler示例:
@RestControllerpublic class ExceptionController { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleException(Exception ex) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("An error occurred: " + ex.getMessage()); } @RequestMapping("/test") public String test() throws Exception { throw new Exception("Test exception!"); }}
在下面的示例中,咱们定义了一个叫做ExceptionController的类,该类是一个@RestController注解的控制器,它包含一个能够产生异样的申请处理程序,一个用于捕捉和解决异样的@ExceptionHandler办法。
@RequestMapping注解配置了一个名为“/test”的API,该API将抛出一个异样,该异样将由咱们下面的ExceptionHandler进行解决。当申请“/test”时,Controller办法将引发异样并触发@ExceptionHandler办法。
在下面的@ExceptionHandler办法中,咱们通过ResponseEntity将异样信息提供给客户端,HTTP状态码设置为500。这使客户端理解已产生谬误,并可能在日志中记录异样信息以便日后调试。
总之,应用ExceptionHandler可能更好的掌控利用的异样信息,使得利用在产生异样的时候更加可控,并且更加容易进行调试。
ExceptionHandler的注意事项
- Controller类下多个@ExceptionHandler上的异样类型不能呈现一样的,否则运行时抛异样。
@ExceptionHandler下办法返回值类型反对多种,常见的ModelAndView,@ResponseBody注解标注,ResponseEntity等类型都OK.
源码剖析介绍
原理阐明-doDispatch
代码片段位于:org.springframework.web.servlet.DispatcherServlet#doDispatch
执行@RequestMapping办法抛出异样后,Spring框架 try-catch的办法捕捉异样, 失常逻辑发不产生异样都会走processDispatchResult流程 ,区别在于异样的参数是否为null .
HandlerExecutionChain mappedHandler = null; Exception dispatchException = null; ModelAndView mv = null; try{ //依据申请查找handlerMapping找到controller mappedHandler=getHandler(request); //找到处理器适配器HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); if(!mappedHandler.applyPreHandle(request,response)){ //拦截器preHandle return ; } //调用处理器适配器执行@RequestMapping办法 mv=ha.handle(request,response); //拦截器postHandle mappedHandler.applyPostHandle(request,response,mv); }catch(Exception ex){ dispatchException=ex; } //将异样信息传入了 processDispatchResult(request,response,mappedHandler,mv,dispatchException)
原理阐明-processDispatchResult
代码片段位于:org.springframework.web.servlet.DispatcherServlet#processDispatchResult
如果 @RequestMapping 办法抛出异样,拦截器的postHandle办法不执行,进入processDispatchResult,判断入参dispatchException,不为null , 代表产生异样,调用processHandlerException解决。
原理阐明-processHandlerException
代码片段位于:org.springframework.web.servlet.DispatcherServlet#processHandlerException
this以后对象指dispatchServlet,handlerExceptionResolvers能够看到三个HandlerExceptionResolver,这三个是Spring框架帮咱们注册的,遍历有序汇合handlerExceptionResolvers,调用接口的resolveException办法。
注册的第一个HandlerExceptionResolver.ExceptionHandlerExceptionResolver, 继承关系如上面所示。
原理阐明-AbstractHandlerExceptionResolver
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException
这里AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用来记录日志、prepareResponse办法,用来设置response的Cache-Control。
异样解决办法就位于doResolveException
留神:AbstractHandlerExceptionResolver和AbstractHandlerMethodExceptionResolver名字看起来十分类似,然而作用不同,一个是面向整个类的,一个是面向办法级别的。
原理阐明-AbstractHandlerMethodExceptionResolver
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo
接口办法实现AbstractHandlerExceptionResolver的resolveException,先判断shouldApplyTo,AbstractHandlerExceptionResolver 和子类AbstractHandlerMethodExceptionResolver都实现了shouldApplyTo办法,子类的shouldApplyTo都调用父类AbstractHandlerExceptionResolver的shouldApplyTo.
父类AbstractHandlerExceptionResolver的shouldApplyTo办法.
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#shouldApplyTo
Spring初始化的时候并没有额定配置 , 所以mappedHandlers和mappedHandlerClasses都为null, 能够在这块扩大进行筛选 ,AbstractHandlerExceptionResolver提供了setMappedHandlerClasses 、setMappedHandlers用于扩大。
doResolveException
代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
Spring申请办法执行一样的解决形式,设置argumentResolvers、returnValueHandlers,之后进行调用异样解决办法。
获取@ExceptionHandler
@ExceptionHandler的办法入参反对:Exception ;SessionAttribute 、 RequestAttribute注解、 HttpServletRequest 、HttpServletResponse、HttpSession。
@ExceptionHandler办法返回值常见的能够是: ModelAndView 、@ResponseBody注解、ResponseEntity。
getExceptionHandlerMethod办法
getExceptionHandlerMethod阐明: 获取对应的@ExceptionHandler办法,封装成ServletInvocableHandlerMethod返回。
exceptionHandlerCache是针对Controller层面的@ExceptionHandler的解决形式,而exceptionHandlerAdviceCache是针对@ControllerAdvice的解决形式. 这两个属性都位于ExceptionHandlerExceptionResolver中。
ExceptionHandlerMethodResolver,缓存A之前没存储过Controller的class ,所以新建一个ExceptionHandlerMethodResolver 退出缓存中,ExceptionHandlerMethodResolver 的初始化工作肯定做了某些工作。
resolveMethod办法
依据异样对象让 ExceptionHandlerMethodResolver 解析失去 method , 匹配到异样解决办法就间接封装成对象 ServletInvocableHandlerMethod ; 就不会再去走@ControllerAdvice里的异样处理器了,这里阐明了。
resolveMethodByExceptionType依据以后抛出异样寻找 匹配的办法,并且做了缓存,当前遇到同样的异样能够间接走缓存取出
resolveMethodByExceptionType办法,尝试从缓存A:exceptionLookupCache中依据异样class类型获取Method ,初始时候必定缓存为空 ,就去遍历ExceptionHandlerMethodResolver的mappedMethods(下面提及了key为异样类型,value为method,exceptionType为以后@RequestMapping办法抛出的异样,判断以后异样类型是不是@ExceptionHandler中value申明的子类或自身,满足条件就代表匹配上了;
可能存在多个匹配的办法,应用ExceptionDepthComparator排序,排序规定是依照继承程序来(继承关系越凑近数值越小,以后类最小为0,顶级父类Throwable为int最大值),排序之后选取继承关系最靠近的那个,并且ExceptionHandlerMethodResolver的exceptionLookupCache中,key为以后抛出的异样,value为解析进去的匹配method.
全局级别异样处理器实现HandlerExceptionResolver接口
public class MyHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ModelMap mmp=new ModelMap(); mmp.addAttribute("ex",ex.getMessage()); return new ModelAndView("error",mmp); }}
- 应用形式: 只须要将该Bean退出到Spring容器,能够通过Xml配置,也能够通过注解形式退出容器;
办法返回值不为null才有意义,如果办法返回值为null,可能异样就没有被捕捉. - 毛病剖析:比方这种形式全局异样解决返回JSP、velocity等视图比拟不便,返回json或者xml等格局的响应就须要本人实现了.如下是我实现的产生全局异样返回JSON的简略例子.
public class MyHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("产生全局异样!"); ModelMap mmp=new ModelMap(); mmp.addAttribute("ex",ex.getMessage()); response.addHeader("Content-Type","application/json;charset=UTF-8"); try { new ObjectMapper().writeValue(response.getWriter(),ex.getMessage()); response.getWriter().flush(); } catch (IOException e) { e.printStackTrace(); } return new ModelAndView(); }}
全局级别异样处理器@ControllerAdvice+@ExceptionHandler应用办法
用法说明:这种状况下 @ExceptionHandler与第一种形式用法雷同,返回值反对ModelAndView,@ResponseBody等多种形式。
@ControllerAdvicepublic class GlobalController { @ExceptionHandler(RuntimeException.class) public ModelAndView fix1(Exception e){ System.out.println("全局的异样处理器"); ModelMap mmp=new ModelMap(); mmp.addAttribute("ex",e); return new ModelAndView("error",mmp); }}
- 形式一:提到ExceptionHandlerExceptionResolver不仅保护@Controller级别的@ExceptionHandler,同时还保护的@ControllerAdvice级别的@ExceptionHandler代码片段位于:
isApplicableToBeanType办法是用来做条件判断的,@ControllerAdvice注解有很多属性用来设置条件,
basePackageClasses、assignableTypes、annotations等,比方我限定了annotations为注解X, 那标注了@X 的ControllerA就能够走这个异样处理器,ControllerB就不能走这个异样处理器。
当初问题的要害就只剩下了exceptionHandlerAdviceCache是什么时候扫描@ControllerAdvice的,上面的逻辑和@ExceptionHandler的逻辑一样了,exceptionHandlerAdviceCache初始化逻辑:
代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet,afterPropertiesSet是Spring bean创立过程中一个重要环节。
代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
ControllerAdviceBean.findAnnotatedBeans办法查找了SpringMvc父子容器中标注 @ControllerAdvice 的bean, new ExceptionHandlerMethodResolver初始化时候解析了以后的@ControllerAdvice的bean的@ExceptionHandler,退出到ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,key为ControllerAdviceBean,value为ExceptionHandlerMethodResolver . 到这里exceptionHandlerAdviceCache就初始化结束。
Spring父子容器中所有@ControllerAdivce的bean的办法
代码片段位于:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans
遍历了SpringMVC父子容器中所有的bean,标注ControllerAdvice注解的bean退出汇合返回。
比拟阐明
@Controller+@ExceptionHandler、HandlerExceptionResolver接口模式、@ControllerAdvice+@ExceptionHandler优缺点阐明:
调用优先级
- @Controller+@ExceptionHandler优先级最高
- @ControllerAdvice+@ExceptionHandler 略低
- HandlerExceptionResolver最低。
三种形式并存的状况 优先级越高的越先抉择,而且被一个捕捉解决了就不去执行其余的。
三种形式都反对多种返回类型
- @Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler能够应用Spring反对的@ResponseBody、ResponseEntity。
- HandlerExceptionResolver办法申明返回值类型只能是 ModelAndView,如果须要返回JSON、xml等须要本人实现.。
缓存利用
- @Controller+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,
- HandlerExceptionResolver接口是不做缓存的,在异样报错的状况下才会走本人的HandlerExceptionResolver实现类,多少有点性能损耗.