SpringMVC : 异常拦截和处理流程

1,067 阅读4分钟

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

这一篇只关注一个小点 , 学习一下 SpringMVC 是如何进行异常拦截和处理的

二 . 使用

以最基础的 Exception 拦截的使用为例 , 常见的使用方式为 :

@ExceptionHandler(Exception.class)
public void deaCommonException(Exception exception, HttpServletResponse response) {
    logger.info("------> 处理通用异常 <-------");
}

当在请求中触发了异常之后 , 就会被该通用拦截器拦截到 , 最终给前端抛出 500 异常 .

那么整个流程里面 , 代码层面是怎么处理的呢 ?

三 . 源码梳理

3.1 拦截的入口

方法是在 DispatcherServlet # doDispatch 中进行核心的处理 , 当方法中出现异常了 , 自然也能在其中被 catch 处理掉 , 其主要流程为 :

  • Step 1 : 调用方法执行
  • Step 2 : 抛出异常后被 catch 捕获 , 记录该异常 , 并不往更外层抛出
  • Step 3 : processDispatchResult 中如果发现存在异常 , 则进行异常的处理

doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
   try {
      ModelAndView mv = null;
      // 准备异常接收对象
      Exception dispatchException = null;

      try {
         // 省略主流程处理 , 主要是 handler 的调用
         mappedHandler = getHandler(processedRequest);
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

      }
      
      // 此处接收 exception , 映射给接收对象
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      // 核心逻辑 , 对 Exception 进行处理
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {

   boolean errorView = false;

   if (exception != null) {
      // 视图处理类 ,特定条件的异常会转发给特定的处理视图 , 可以在处理程序处理期间的任何时间抛出
      if (exception instanceof ModelAndViewDefiningException) {
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {
         // Step 1 : 获取当前请求的 handler 类 , 业务处理类 
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         // Step 2 : 核心 , 处理主流程处理
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }

   
   if (mv != null && !mv.wasCleared()) {
      // 渲染给定的ModelAndView -> resolveViewName
      render(mv, request, response);
      if (errorView) {

        // request.removeAttribute("javax.servlet.error.status_code");
        // request.removeAttribute("javax.servlet.error.exception_type");
        // request.removeAttribute("javax.servlet.error.message");
        // request.removeAttribute("javax.servlet.error.exception");
        // request.removeAttribute("javax.servlet.error.request_uri");
        // request.removeAttribute("javax.servlet.error.servlet_name");
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
   }

   if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      return;
   }

   if (mappedHandler != null) {
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}

3.2 异常的处理主流程

当异常被捕获到 , 并且通过 processHandlerException 发起异常处理流程后 , 会通过如下流程开始依次处理异常 :

  • DispatcherServlet # processHandlerException : 处理起点
  • HandlerExceptionResolverComposite # resolveException
  • AbstractHandlerExceptionResolver # resolveException
  • AbstractHandlerMethodExceptionResolver # doResolveException
  • ExceptionHandlerExceptionResolver # doResolveHandlerMethodException
  • ServletInvocableHandlerMethod # invokeAndHandle : 调用具体方法
  • 调用最后的 @ExceptionHandler 处理方法处理异常
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      @Nullable Object handler, Exception ex) throws Exception {

   // 成功和错误响应可能使用不同的内容类型
   // HandlerMapping.class.getName() + ".producibleMediaTypes";
   request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

   // Check registered HandlerExceptionResolvers...
   ModelAndView exMv = null;
   if (this.handlerExceptionResolvers != null) {
      // -      
      // - DefaultErrorAttributes
      // - HandlerExceptionResolverComposite 
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
         exMv = resolver.resolveException(request, response, handler, ex);
         if (exMv != null) {
            break;
         }
      }
   }
   if (exMv != null) {
      if (exMv.isEmpty()) {
         request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
         return null;
      }
      // We might still need view name translation for a plain error model...
      if (!exMv.hasView()) {
         String defaultViewName = getDefaultViewName(request);
         if (defaultViewName != null) {
            exMv.setViewName(defaultViewName);
         }
      }
      WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
      return exMv;
   }

   throw ex;
}

AnnotationConfigServletWebServerApplicationContext_parent.png

循环 Exception resolve 列表

public ModelAndView resolveException(
      HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

   if (this.resolvers != null) {
      // 遍历 resolvers 处理类 , 常见的有以下几种 , 添加默认异常解析器
      // - ExceptionHandlerExceptionResolver 
      // - ResponseStatusExceptionResolver
      // - DefaultHandlerExceptionResolver
      for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
         ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
         if (mav != null) {
            return mav;
         }
      }
   }
   return null;
}

注意 , 这里调用的 resolveException 均为父类 AbstractHandlerExceptionResolver , 由父类再调用子类 doResolveException .

调用 Method 处理方法

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
      HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

   // 通过业务方法和异常类型从集合中获取去 
   ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
   if (exceptionHandlerMethod == null) {
      return null;
   }
   
   // setHandlerMethodArgumentResolvers +  setHandlerMethodReturnValueHandlers

   ServletWebRequest webRequest = new ServletWebRequest(request, response);
   ModelAndViewContainer mavContainer = new ModelAndViewContainer();

   // 此处会调用具体的代理方法 , 会根据是否存在 Throwable cause 分别调用2个不同的方法
   exceptionHandlerMethod.invokeAndHandle

   //如果完全处理了请求 ,这直接返回 ModelAndView
   if (mavContainer.isRequestHandled()) {
      return new ModelAndView();
   }
   else {
      // 省略返回处理的 ModelAndView 或者 RedirectAttributes 对象
   }
}

image.png

获取实际

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
      @Nullable HandlerMethod handlerMethod, Exception exception) {

   Class<?> handlerType = null;

   // 如果 HandlerMethod 存在 , 则先尝试处理
   if (handlerMethod != null) {
      // 控制器类本身的局部异常处理程序方法 , 此处是具体的业务方法
      handlerType = handlerMethod.getBeanType();
      // 从缓存中获取当前异常类对应的 ExceptionResolver , 不存在则 new ExceptionHandlerMethodResolver , 并且缓存
      ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
      
      Method method = resolver.resolveMethod(exception);
      
      // 如果控制器本身存在存在局部异常处理方法 , 则直接返回
      if (method != null) {
         return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
      }
      
      // 代理类需要获取实际类
      if (Proxy.isProxyClass(handlerType)) {
         handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
      }
   }

   // 此处遍历所有的异常类
   for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
      ControllerAdviceBean advice = entry.getKey();
      
      if (advice.isApplicableToBeanType(handlerType)) {
         // Exception 处理类 , 包含核心的类信息
         ExceptionHandlerMethodResolver resolver = entry.getValue();
         // 
         Method method = resolver.resolveMethod(exception);
         
         if (method != null) {
            return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
         }
      }
   }

   return null;
}

// 从缓存中尝试获取Method
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
   Method method = this.exceptionLookupCache.get(exceptionType);
   if (method == null) {
      method = getMappedMethod(exceptionType);
      // 不存在则获取后添加到缓存中
      this.exceptionLookupCache.put(exceptionType, method);
   }
   return method;
}

private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
   List<Class<? extends Throwable>> matches = new ArrayList<>();
  
   // 遍历所有的 Method Mapper
   // PS : 这里是第二个循环 ,第一个循环遍历 Class , 第二个循环遍历 Method
   for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
      if (mappedException.isAssignableFrom(exceptionType)) {
         matches.add(mappedException);
      }
   }
   if (!matches.isEmpty()) {
      matches.sort(new ExceptionDepthComparator(exceptionType));
      // 返回匹配的第一个方法 ?
      return this.mappedMethods.get(matches.get(0));
   }
   else {
      return null;
   }
}

四 . 补充 : @ExceptionHandler 的扫描和加载

这一部分来看一下 , @ExceptionHandler 是如何被扫描到容器中的.

// 在 ExceptionHandlerExceptionResolver 中存在2个Map 用于存放对应的关联关系

// 用于保存 Class 对应的 Resolver , 对应业务Controller 对应 Resolver
Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =new ConcurrentHashMap<>(64);

// 对应 Error 处理类对应 resolver
Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =new LinkedHashMap<>();

4.1 扫描和注入

// C- ExceptionHandlerExceptionResolver
public void afterPropertiesSet() {
   // 初始化 Advice  , 处理 ResponseBodyAdvice 
   initExceptionHandlerAdviceCache();

   if (this.argumentResolvers == null) {
      List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
      this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
   }
   if (this.returnValueHandlers == null) {
      List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
      this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
   }
}

private void initExceptionHandlerAdviceCache() {
   if (getApplicationContext() == null) {
      return;
   }
   
   // 所有的 Error 处理类
   List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
   for (ControllerAdviceBean adviceBean : adviceBeans) {
      Class<?> beanType = adviceBean.getBeanType();
      if (beanType == null) {
         throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
      }
      // 通过 BeanType 构建 Resolver
      ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
      
      // 将 Resolver 存入缓存
      if (resolver.hasExceptionMappings()) {
         this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
      }
      if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
         this.responseBodyAdvice.add(adviceBean);
      }
   }
}

总结

时间有限 , 整体来说还是过一下流程 ,方便后续的问题排查

image.png