关于后端:Spring专题技术原理从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

42次阅读

共计 8523 个字符,预计需要花费 22 分钟才能阅读完成。

ExceptionHandler 的作用

ExceptionHandler 是 Spring 框架提供的一个注解,用于解决应用程序中的异样。当应用程序中产生异样时,ExceptionHandler 将优先地拦挡异样并解决它,而后将处理结果返回到前端。该注解可用于类级别和办法级别,以捕捉不同级别的异样。

在 Spring 中应用 ExceptionHandler 非常简单,只需在须要捕捉异样的办法上注解 @ExceptionHandler,而后定义一个办法,该办法将接管异样并返回异样信息,并将该异样信息展现给前端用户。

ExceptionHandler 的应用

阐明:针对可能出问题的 Controller,新增注解办法 @ExceptionHandler,上面是一个根本的 ExceptionHandler 示例

@RestController
public 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 等多种形式。

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

正文完
 0