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实现类,多少有点性能损耗.