SpringMVC常见组件之HandlerExceptionResolver分析-2

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringMVC常见组件之HandlerExceptionResolver分析-2

【8】AbstractHandlerMethodExceptionResolver

AbstractHandlerMethodExceptionResolver与子类ExceptionHandlerExceptionResolver一起处理HandlerMethod在处理过程中抛出的异常。如何解析异常呢?通常是通过标注了@ExceptionHandler注解的方法处理异常。


ExceptionHandlerExceptionResolver解析器支持你添加使用自定义参数解析器和返回结果处理器,当然你也可以通过setArgumentResolvers和setReturnValueHandlers(List)方法完全自定义参数解析和返回结果处理器。

① AbstractHandlerMethodExceptionResolver

如下所示,该抽象类有三个方法:


  • 重写了shouldApplyTo方法,进行了HandlerMethod判断;
  • doResolveException也是个模板方法,具体逻辑交给子类处理
  • doResolveHandlerMethodException是个抽象方法,由子类实现
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
  //重写了shouldApplyTo,这里主要检查了HandlerMethod
  @Override
  protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    if (handler == null) {
      return super.shouldApplyTo(request, null);
    }
    // 如果是HandlerMethod,则获取其所属bean然后调用委派给父类处理
    else if (handler instanceof HandlerMethod) {
      HandlerMethod handlerMethod = (HandlerMethod) handler;
      handler = handlerMethod.getBean();
      return super.shouldApplyTo(request, handler);
    }
    else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) {
      return super.shouldApplyTo(request, handler);
    }
    else {
      return false;
    }
  }
  //此解析器是否具有全局异常处理程序,例如,
  //未在引发异常的{@code HandlerMethod}的同一类中声明,因此可以应用于任何处理程序。
  protected boolean hasGlobalExceptionHandlers() {
    return false;
  }
// 异常解析的入口方法,其也是一个模板方法,给子类实现doResolveHandlerMethodException
  @Override
  @Nullable
  protected final ModelAndView doResolveException(
      HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
    return doResolveHandlerMethodException(request, response, handlerMethod, ex);
  }
  // 异常解析核心方法,由子类实现
  @Nullable
  protected abstract ModelAndView doResolveHandlerMethodException(
      HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
}

② ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver是通过解析标注了@ExceptionHandler注解的方法进行异常处理的。


如下所示,其继承自AbstractHandlerMethodExceptionResolvers实现了ApplicationContextAware、InitializingBean接口。


实现了ApplicationContextAware意味着其会拥有applicationContext引用。


实现了InitializingBean接口意味着在实例化时会调用其afterPropertiesSet方法。


b7fa238caa234e62bf421cb2aae47842.png

① 核心属性和构造方法

// 自定义的参数解析器
@Nullable
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
// 参数解析器的组合包装
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
//自定义返回结果处理器
@Nullable
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
// 返回结果处理器的组合包装
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
// 转换器 处理请求响应
private List<HttpMessageConverter<?>> messageConverters;
// 内容协商管理器,处理MediaType
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
// 维护了ResponseBodyAdvice列表 
private final List<Object> responseBodyAdvice = new ArrayList<>();
@Nullable
private ApplicationContext applicationContext;
// 缓存,adviceBean:ExceptionHandlerMethodResolver
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
    new ConcurrentHashMap<>(64);
// 缓存,根据ControllerAdviceBean获取对应的异常解析器
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
    new LinkedHashMap<>();
//构造器,添加了ByteArrayHttpMessageConverter、StringHttpMessageConverter
//与SourceHttpMessageConverter和AllEncompassingFormHttpMessageConverter
public ExceptionHandlerExceptionResolver() {
  this.messageConverters = new ArrayList<>();
  this.messageConverters.add(new ByteArrayHttpMessageConverter());
  this.messageConverters.add(new StringHttpMessageConverter());
  if(!shouldIgnoreXml) {
    try {
      this.messageConverters.add(new SourceHttpMessageConverter<>());
    }
    catch (Error err) {
      // Ignore when no TransformerFactory implementation is available
    }
  }
  this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}

② afterPropertiesSet

该方法会在对象实例化过程中被调用。如下所示其是一个模板方法,主要由initExceptionHandlerAdviceCache(初始化获取ResponseBodyAdvice Bean)、getDefaultArgumentResolvers(获取默认的参数解析器)与getDefaultReturnValueHandlers(获取默认的返回结果处理器)组成。

@Override
public void afterPropertiesSet() {
  // Do this first, it may add ResponseBodyAdvice beans
  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);
  }
}
① initExceptionHandlerAdviceCache


如下所示,这里的核心逻辑就是从容器中获取标注了@ControllerAdvice注解的bean,称之为ControllerAdviceBean。接下来会对adviceBeans 进行遍历循环:

如果new ExceptionHandlerMethodResolver(beanType)实例有标注了@ExceptionHandler注解的方法,则放入exceptionHandlerAdviceCache={adviceBean:ExceptionHandlerMethodResolver};

如果adviceBean是ResponseBodyAdvice类型,则放入responseBodyAdvice 列表中。

private void initExceptionHandlerAdviceCache() {
  if (getApplicationContext() == null) {
    return;
  }
  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);
    }
    ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
    if (resolver.hasExceptionMappings()) {
      this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
    }
    if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
      this.responseBodyAdvice.add(adviceBean);
    }
  }
  // 一些日志打印
}
② getDefaultArgumentResolvers

实例化一些默认参数解析器,并尝试放入用户自己定义的参数解析器

protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
  List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
  // Annotation-based argument resolution
  resolvers.add(new SessionAttributeMethodArgumentResolver());
  resolvers.add(new RequestAttributeMethodArgumentResolver());
  // Type-based argument resolution
  resolvers.add(new ServletRequestMethodArgumentResolver());
  resolvers.add(new ServletResponseMethodArgumentResolver());
  resolvers.add(new RedirectAttributesMethodArgumentResolver());
  resolvers.add(new ModelMethodProcessor());
  // Custom arguments
  if (getCustomArgumentResolvers() != null) {
    resolvers.addAll(getCustomArgumentResolvers());
  }
  return resolvers;
}


③ getDefaultReturnValueHandlers

实例化一些返回结果处理器放入List handlers中,并尝试放入用户自定义返回结果处理器。

protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
  List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
  // Single-purpose return value types
  handlers.add(new ModelAndViewMethodReturnValueHandler());
  handlers.add(new ModelMethodProcessor());
  handlers.add(new ViewMethodReturnValueHandler());
  handlers.add(new HttpEntityMethodProcessor(
      getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
  // Annotation-based return value types
  handlers.add(new ServletModelAttributeMethodProcessor(false));
  handlers.add(new RequestResponseBodyMethodProcessor(
      getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
  // Multi-purpose return value types
  handlers.add(new ViewNameMethodReturnValueHandler());
  handlers.add(new MapMethodProcessor());
  // Custom return value types
  if (getCustomReturnValueHandlers() != null) {
    handlers.addAll(getCustomReturnValueHandlers());
  }
  // Catch-all 这个很有意思哦,将会作为复杂类型的默认处理器
  handlers.add(new ServletModelAttributeMethodProcessor(true));
  return handlers;
}

③ 核心方法doResolveHandlerMethodException

如下所示,这里有这样几个关键步骤:


① getExceptionHandlerMethod获取ServletInvocableHandlerMethod ,获取不到直接返回null;

② exceptionHandlerMethod.invokeAndHandle进行实际处理,其实就是解析请求参数、调用目标方法以及最终处理返回结果,这块前面已经很熟悉了;

③ 对ModelAndView 进行处理;

@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
    HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 获取ServletInvocableHandlerMethod 
  ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
  if (exceptionHandlerMethod == null) {//为null就直接返回null
    return null;
  }
// 设置参数解析器
  if (this.argumentResolvers != null) {
    exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
  }
  // 设置返回结果处理器
  if (this.returnValueHandlers != null) {
    exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
  }
// 实例化ServletWebRequest  与ModelAndViewContainer 
  ServletWebRequest webRequest = new ServletWebRequest(request, response);
  ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  try {
    if (logger.isDebugEnabled()) {
      logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
    }
    // 这里首先尝试对cause 调用exceptionHandlerMethod方法
    //如果cause为null,则对exception调用exceptionHandlerMethod方法
    Throwable cause = exception.getCause();
    if (cause != null) {
      // Expose cause as provided argument as well
      exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
    }
    else {
      // Otherwise, just the given exception as-is
      exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
    }
  }
  catch (Throwable invocationEx) {
    // Any other than the original exception (or its cause) is unintended here,
    // probably an accident (e.g. failed assertion or the like).
    if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
      logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
    }
    // Continue with default processing of the original exception...
    return null;
  }
// 如果请求已经被处理完毕,则直接返回空的ModelAndView
  if (mavContainer.isRequestHandled()) {
    return new ModelAndView();
  }
  else {
  // 如果请求没有处理完毕,则封装ModelAndView 
    ModelMap model = mavContainer.getModel();
    HttpStatus status = mavContainer.getStatus();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
    mav.setViewName(mavContainer.getViewName());
    if (!mavContainer.isViewReference()) {
      mav.setView((View) mavContainer.getView());
    }
    // 如果model是RedirectAttributes ,则放入outputFlashMap
    if (model instanceof RedirectAttributes) {
      Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
      RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
    }
    return mav;
  }
}

那么这里我们着重分析getExceptionHandlerMethod,我们看其是如何获取异常对应的method的。


getExceptionHandlerMethod方法简单来说就是首先从当前handlerMethod所在的类继承树中找到合适的method。如果找不到或者handlerMethod为null,则从其ControllerAdvice 类中尝试寻找合适的method。

@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
    @Nullable HandlerMethod handlerMethod, Exception exception) {
  Class<?> handlerType = null;
// 如果handlerMethod 不为null
  if (handlerMethod != null) {
    // 获取handlerMethod所属的bean type
    handlerType = handlerMethod.getBeanType();
    // 尝试从exceptionHandlerCache获取handlerType对应的ExceptionHandlerMethodResolver 
    ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
    if (resolver == null) {
//如果resolver 为null,则实例化ExceptionHandlerMethodResolverb并放入exceptionHandlerCache
      resolver = new ExceptionHandlerMethodResolver(handlerType);
      this.exceptionHandlerCache.put(handlerType, resolver);
    }
    // 获取Method 然后返回ServletInvocableHandlerMethod
    Method method = resolver.resolveMethod(exception);
    if (method != null) {
      return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
    }
    // 如果是代理,则获取被代理的类型-targetClass,以便下方advice 检测
    if (Proxy.isProxyClass(handlerType)) {
      handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
    }
  }
// 如果handlerMethod为null,则从其他ControllerAdviceBean 寻找合适的method
  for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
    ControllerAdviceBean advice = entry.getKey();
    // 如果当前advice 适用于handlerType
    if (advice.isApplicableToBeanType(handlerType)) {
    // 获取resolverf然后解析得到method
      ExceptionHandlerMethodResolver resolver = entry.getValue();
      Method method = resolver.resolveMethod(exception);
      if (method != null) {
        return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
      }
    }
  }
  return null;
}

代码逻辑总结如下:

  • ① 如果handlerMethod 不为null:

② 获取handlerMethod所属的bean type,根据bean type尝试从exceptionHandlerCache获取handlerType对应的ExceptionHandlerMethodResolver

③ 如果获取不到resolver就new一个ExceptionHandlerMethodResolver 然后放入exceptionHandlerCache

④ resolver.resolveMethod(exception);获取异常对应的method,如果不为null直接返回new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);

⑤ 如果handlerType是代理,则获取其targetClass,以便下面advice使用

⑥ 如果handlerMethod为null或者上面没有获取到合适的method,则遍历exceptionHandlerAdviceCache中的ControllerAdviceBean,尝试找到一个合适的method

⑦ 如果最终得不到method,则返回null。

【9】 ExceptionHandlerMethodResolver

上面我们在遍历List adviceBeans提到了ExceptionHandlerMethodResolver,那么这里我们见到看一下这个解析器。


① 核心属性和构造方法

 // 一个方法过滤器,筛选标注了@ExceptionHandler注解的方法
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
    AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
// 维护了异常:Method的map ,初始大小是16
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
// 缓存,便于快捷查找异常对应的method
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
// 核心构造器,mappedMethods 就是在这里初始化。
// 1.筛选标注了@ExceptionHandler注解的方法,然后遍历方法
//2.获取方法中注解标注的异常映射,遍历放入mappedMethods中
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
  for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
    for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
      addExceptionMapping(exceptionType, method);
    }
  }
}


②获取method

如下所示其获取异常对应的method是一个递归方法,如果不能直接根据异常类型获取到对应的method,那么就会获取其cause,然后根据cause再次递归自身。

//resolveMethod仅仅只是调用了resolveMethodByThrowable
@Nullable
public Method resolveMethod(Exception exception) {
  return resolveMethodByThrowable(exception);
}
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
  Method method = resolveMethodByExceptionType(exception.getClass());
  if (method == null) {
    Throwable cause = exception.getCause();
    if (cause != null) {
      method = resolveMethodByThrowable(cause);
    }
  }
  return method;
}


resolveMethodByExceptionType方法如下所示,首先从exceptionLookupCache获取异常对应的Method ,获取不到则调用getMappedMethod方法获取,然后放入到exceptionLookupCache中。

@Nullable
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;
}


getMappedMethod核心逻辑则是获取mappedMethods所有key,如果key是exceptionType类型则放入matches中,最后对matches进行排序返回第一个元素。

@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
  List<Class<? extends Throwable>> matches = new ArrayList<>();
  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;
  }
}


目录
相关文章
|
7月前
|
设计模式
SpringMVC常见组件之DataBinder数据绑定器分析
SpringMVC常见组件之DataBinder数据绑定器分析
338 0
|
XML 缓存 Java
SpringMVC常见组件之ViewResolver分析
本文我们尝试总结分析SpringMVC体系中的视图解析器-ViewResolver。其根据name解析视图View,通常鼓励实现类考虑国际化策略实现。
116 0
|
7月前
|
XML 存储 Java
SpringMVC常见组件之HandlerMapping分析
SpringMVC常见组件之HandlerMapping分析
174 0
|
7月前
|
XML 缓存 前端开发
SpringMVC常见组件之HandlerAdapter分析
SpringMVC常见组件之HandlerAdapter分析
98 0
|
XML 前端开发 Java
SpringMVC常见组件之View分析
SpringMVC常见组件之View分析
101 0
|
JSON 前端开发 Java
SpringMVC常见组件之HandlerExceptionResolver分析-1
SpringMVC常见组件之HandlerExceptionResolver分析-1
214 0
|
XML 存储 前端开发
解析 SpringMVC 父子容器及九大内置组件加载过程
解析 SpringMVC 父子容器及九大内置组件加载过程
122 1
java202304java学习笔记第六十二天-ssm-springMvc中组件分析
java202304java学习笔记第六十二天-ssm-springMvc中组件分析
46 0
|
存储 设计模式 前端开发
浅谈SpringMVC五大组件以及对执行原理的分析。
Spring MVC是包含在spring中的一个基于MVC设计思想的Web应用程序框架,目的是简化开发工作,提高开发效率。
浅谈SpringMVC五大组件以及对执行原理的分析。
|
Java Spring 容器
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)