@ControllerAdvice配合@ExceptionHandler如何实现异常处理

417 阅读5分钟

一.异常处理器的初始化

首先说异常解析器,我们知道springmvc在抛出异常的时候都会被异常解析器处理

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
      implements ApplicationContextAware, InitializingBean

ExceptionHandlerExceptionResolver实现了InitializingBean接口,在spring容器装载的时候会进行初始化调用,InitializingBean接口如下

public interface InitializingBean {

   /**
    * Invoked by the containing {@code BeanFactory} after it has set all bean properties
    * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
    * <p>This method allows the bean instance to perform validation of its overall
    * configuration and final initialization when all bean properties have been set.
    * @throws Exception in the event of misconfiguration (such as failure to set an
    * essential property) or if initialization fails for any other reason
    */
   void afterPropertiesSet() throws Exception;

}

initExceptionHandlerAdviceCache(); 首先调用initExceptionHandlerAdviceCache方法完成 @ControllerAdvice注解相关的初始化

@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);
   }
}
private void initExceptionHandlerAdviceCache() {
   .....省略一些不重要的代码,只关注本次研究的重点
   // 第一步获取容器中所有标注了@ControllerAdvice的bean,收集起来
   List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
   // 遍历所有带@ControllerAdvice注解的bean
   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);
      }
      //最后还会判断是否实现了ResponseBodyAdvice接口,也保存在集合里
      if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
         this.responseBodyAdvice.add(adviceBean);
      }
   }
}

找到容器中所有带@ControllerAdvice注解的bean

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
   // 保存结果
   List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
   // BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class) 
   // 获取容器中的所有 bean name
   for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
      if (!ScopedProxyUtils.isScopedTarget(name)) {
         // 这是核心,找到容器中所有带@ControllerAdvice注解的bean
         ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
         if (controllerAdvice != null) {
            // Use the @ControllerAdvice annotation found by findAnnotationOnBean()
            // in order to avoid a subsequent lookup of the same annotation.
            adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
         }
      }
   }
   OrderComparator.sort(adviceBeans);
   return adviceBeans;
}

ExceptionHandlerMethodResolver

/**
 * A filter for selecting {@code @ExceptionHandler} methods.
 */
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
      AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
/**
 * A constructor that finds {@link ExceptionHandler} methods in the given type.
 * @param handlerType the type to introspect
 */
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
   // 找到给定类型的bean中所有带@ExceptionHandler注解的方法
   for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
      for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
         addExceptionMapping(exceptionType, method);
      }
   }
}

下面的方法是收集某个方法中,可以处理的异常集合

/**
 * Extract exception mappings from the {@code @ExceptionHandler} annotation first,
 * and then as a fallback from the method signature itself.
 */
@SuppressWarnings("unchecked")
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
   List<Class<? extends Throwable>> result = new ArrayList<>();
   detectAnnotationExceptionMappings(method, result);
   // 如果注解上没有指定异常,查看方法参数中有没有指定的异常也可以
   if (result.isEmpty()) {
      for (Class<?> paramType : method.getParameterTypes()) {
         if (Throwable.class.isAssignableFrom(paramType)) {
            result.add((Class<? extends Throwable>) paramType);
         }
      }
   }
   if (result.isEmpty()) {
      throw new IllegalStateException("No exception types mapped to " + method);
   }
   return result;
}

// 获取方法上@ExceptionHandler注解的value属性,value表示当前方法的处理异常有哪些
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
   ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
   Assert.state(ann != null, "No ExceptionHandler annotation");
   result.addAll(Arrays.asList(ann.value()));
}

将异常类型和处理的方法放到一个map中 mappedMethods属性

public class ExceptionHandlerMethodResolver {

   /**
    * A filter for selecting {@code @ExceptionHandler} methods.
    */
   public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
         AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);


   private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);

   private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);


   /**
    * A constructor that finds {@link ExceptionHandler} methods in the given type.
    * @param handlerType the type to introspect
    */
   public ExceptionHandlerMethodResolver(Class<?> handlerType) {
      for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
         for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
            addExceptionMapping(exceptionType, method);
         }
      }
   }
   
   }
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
   Method oldMethod = this.mappedMethods.put(exceptionType, method);
   if (oldMethod != null && !oldMethod.equals(method)) {
      throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
            exceptionType + "]: {" + oldMethod + ", " + method + "}");
   }
}

mappedMethods中key存放的是异常类型,value存放的是处理这个异常的Method对象,

image.png

/**
 * Whether the contained type has any exception mappings.
 */
public boolean hasExceptionMappings() {
   return !this.mappedMethods.isEmpty();
}

最后将结果保存在 exceptionHandlerAdviceCachemap中,

private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
      new LinkedHashMap<>();
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
   this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}

总结: ExceptionHandlerExceptionResolver异常解析器在初始化的时候会去容器中查找所有标记@ControllerAdvice注解的bean,

并对这些bean中标记@ExceptionHandler注解的方法进行解析,解析的目的是为了想要区分什么样的异常类型用那个方法处理,

将分析的结果保存在 ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCachemap属性中,

exceptionHandlerAdviceCachemap的key是对应的标注@ControllerAdvice的bean,

value是ExceptionHandlerMethodResolver对象,ExceptionHandlerMethodResolver对象中封装了这个bean中所有标记了@ExceptionHandler的方法

ExceptionHandlerMethodResolver对象中含有mappedMethods属性,存放的就是异常类型和处理方法对应的映射关系

private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);

到这里异常处理器的初始化就已经结束了.接下来就是当控制器发生异常了,就会被异常处理器拦截,进入对应的方法

二.异常处理器捕捉异常

所有请求经过dispatchServlet进行处理,处理流程可以看调用链,当有异常的时候就会被异常处理器捕获,解析处理

image.png

doDispatch(request, response);
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        mv = processHandlerException(request, response, handler, exception);
            ....
            

最後会选择一个合适的异常处理器进行处理,这里有一个HandlerExceptionResolverComposite类,里面代理了很多类型的异常处理器,最后会选择一个异常处理器进行处理,就是ExceptionHandlerExceptionResolver

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

   if (this.resolvers != null) {
      for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
         ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
         if (mav != null) {
            return mav;
         }
      }
   }
   return null;
}

ExceptionHandlerExceptionResolver的继承结构如下

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
      implements ApplicationContextAware, InitializingBean

ExceptionHandlerExceptionResolver--->AbstractHandlerMethodExceptionResolver

--->AbstractHandlerExceptionResolver---> HandlerExceptionResolver

最终调用ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法进行异常处理

/**
 * Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception.
 */
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
      HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

   ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
   if (exceptionHandlerMethod == null) {
      return null;
   }

   if (this.argumentResolvers != null) {
      // 设置参数解析器
      exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
   }
   if (this.returnValueHandlers != null) {
      // 设置返回值解析器
      exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
   }
   // 封装请求
   ServletWebRequest webRequest = new ServletWebRequest(request, response);
   ModelAndViewContainer mavContainer = new ModelAndViewContainer();
   try {
      .....
      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;
   }

   if (mavContainer.isRequestHandled()) {
      return new ModelAndView();
   }
   else {
      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());
      }
      if (model instanceof RedirectAttributes) {
         Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
         RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
      }
      return mav;
   }
}

getExceptionHandlerMethod方法做了什么?只做了一件事,根据异常的类型获取处理异常的method,将method对象包装成ServletInvocableHandlerMethod对象,ServletInvocableHandlerMethod对象,以供后来调用

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

   Class<?> handlerType = null;

   if (handlerMethod != null) {
      // Local exception handler methods on the controller class itself.
      // To be invoked through the proxy, even in case of an interface-based proxy.
      handlerType = handlerMethod.getBeanType();
      
      // 这里的逻辑是先从缓存中获取,获取不到在构建,构建之后在放入到缓存中,供下次使用
      // 因为 ExceptionHandlerMethodResolver的构建是比较费时的
      ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
      if (resolver == null) {
         resolver = new ExceptionHandlerMethodResolver(handlerType);
         this.exceptionHandlerCache.put(handlerType, resolver);
      }
      // 就是获取能够处理这个异常的方法method对象
      Method method = resolver.resolveMethod(exception);
      if (method != null) {
         // 封装成ServletInvocableHandlerMethod返回
         return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
      }
      // For advice applicability check below (involving base packages, assignable types
      // and annotation presence), use target class instead of interface-based proxy.
      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)) {
         ExceptionHandlerMethodResolver resolver = entry.getValue();
         Method method = resolver.resolveMethod(exception);
         if (method != null) {
            return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
         }
      }
   }

   return null;
}

Method method = resolver.resolveMethod(exception);

ExceptionHandlerMethodResolver可以根据异常类型获取处理异常的method通过resolveMethod()方法

@Nullable
public Method resolveMethod(Exception exception) {
   return resolveMethodByThrowable(exception);
}

/**
 * Find a {@link Method} to handle the given Throwable.
 * Use {@link ExceptionDepthComparator} if more than one match is found.
 * @param exception the exception
 * @return a Method to handle the exception, or {@code null} if none found
 * @since 5.0
 */
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
   Method method = resolveMethodByExceptionType(exception.getClass());
   if (method == null) {
      Throwable cause = exception.getCause();
      if (cause != null) {
         method = resolveMethodByExceptionType(cause.getClass());
      }
   }
   return method;
}

// 核心方法 上面的两个方法最终都会调用到resolveMethodByExceptionType()
// resolveMethodByExceptionType用了一个二级缓存,如果存在就从缓存中直接取了
/**
 * Find a {@link Method} to handle the given exception type. This can be
 * useful if an {@link Exception} instance is not available (e.g. for tools).
 * @param exceptionType the exception type
 * @return a Method to handle the exception, or {@code null} if none found
 */
@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;
}

// mappedMethods上一节说过存放的是异常类型和处理方法的映射,根据异常类型找到对应的处理方法
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;
   }
}
// ExceptionHandlerExceptionResolver对象中含有参数解析器和返回值解析器,至于何时初始化的,换个文章在讲
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

最终调用
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   // 调用到目标方法了
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   setResponseStatus(webRequest);

   if (returnValue == null) {
      if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
         disableContentCachingIfNecessary(webRequest);
         mavContainer.setRequestHandled(true);
         return;
      }
   }
   else if (StringUtils.hasText(getResponseStatusReason())) {
      mavContainer.setRequestHandled(true);
      return;
   }

   mavContainer.setRequestHandled(false);
   Assert.state(this.returnValueHandlers != null, "No return value handlers");
   try {
      // 将返回值用返回值处理器进行处理返回
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   catch (Exception ex) {
      if (logger.isTraceEnabled()) {
         logger.trace(formatErrorForReturnValue(returnValue), ex);
      }
      throw ex;
   }
}

经过上面两节的分析,controller的所有异常都可以在我们的控制下进行处理,当出现异常时,捕获异常,给前端返回标准统一的消息。

@ControllerAdvice配合@ExceptionHandler可以捕获想要的异常

后续可以分析下 1.异常处理器如何装载的 2.ExceptionHandlerExceptionResolver是如何被选择的,那么多处理器 2.返回值处理器如何处理返回值的。是如何分辨返回的是视图还是json.