SpringMVC中的异常处理器

SpringMVC中的异常处理器

一、概述

在使用SpringMVC的过程中,应用系统通常都会有需要统一处理未捕获异常的需求,为了将异常处理的逻辑与业务逻辑代码分离开,SpringMVC提供了@ExceptionHandler 统一异常处理的方式。

@ControllerAdvice+@ExceptionHandler是一起使用的,这样我们就可以在集中的地方处理未知异常,打印对应日志,封装返回结果等。

系统开发中处理异常的思路:

  • dao异常通常抛给Service
  • Service异常通常抛给Controller
  • Controller把异常抛给前端控制器(SpringMVC框架)
  • 由前端控制器把异常交给异常处理器进行处理

二、异常处理器初始化位置

默认策略

在DispatcherServlet这个类中,然后在initStrategies中完成了异常处理器的初始化:

protected void initStrategies(ApplicationContext context) {
  initMultipartResolver(context);
  initLocaleResolver(context);
  initThemeResolver(context);
  initHandlerMappings(context);
  initHandlerAdapters(context);
  //初始化异常处理器
  initHandlerExceptionResolvers(context); 
  initRequestToViewNameTranslator(context);
  initViewResolvers(context);
  initFlashMapManager(context);
}

看到这里,应该可以猜出大概的实现原理了。

非常类似HandlerMapping和HandlerAdapter的初始化过程:

private void initHandlerExceptionResolvers(ApplicationContext context) {
  this.handlerExceptionResolvers = null;

  if (this.detectAllHandlerExceptionResolvers) {
    // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
    // 从容器中查找是否有HandlerExceptionResolver类型的异常解析器
    Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
      .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
    if (!matchingBeans.isEmpty()) {
      this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
      // We keep HandlerExceptionResolvers in sorted order.
      AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
    }
  }
  else {
    try {
      // 没有的话,利用默认的!
      HandlerExceptionResolver her =
        context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
      this.handlerExceptionResolvers = Collections.singletonList(her);
    }
    catch (NoSuchBeanDefinitionException ex) {
      // Ignore, no HandlerExceptionResolver is fine too.
    }
  }
  // 再从DispatcherServlet.properties中找到默认的HandlerExceptionResolver
  // Ensure we have at least some HandlerExceptionResolvers, by registering
  // default HandlerExceptionResolvers if no other resolvers are found.
  if (this.handlerExceptionResolvers == null) {
    this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
    if (logger.isTraceEnabled()) {
      logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
                   "': using default strategies from DispatcherServlet.properties");
    }
  }
}
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

@ControllerAdvice工作原理

但是这个和我们的@ControllerAdvice+@ExceptionHandler注解没有找到任何的相关关系?

RequestMappingHandlerAdapter

经过网上资料查找,可以看到RequestMappingHandlerAdapter组件是实现了InitializingBean接口的,这个时候会来执行接口中的afterPropertiesSet方法。

直接来到:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet

public void afterPropertiesSet() {
  // Do this first, it may add ResponseBody advice beans
  // 重点是在这里
  initControllerAdviceCache();

  // 尽管不是当前的内容,但是可以看看都是在哪里来进行初始化的
  if (this.argumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
    this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
  }
  if (this.initBinderArgumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
    this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
  }
  if (this.returnValueHandlers == null) {
    List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
  }
}

那么来看一下initControllerAdviceCache方法

	private void initControllerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}
		// 直接从IOC容器中查找类上添加了@ControllerAdvice注解的bean
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
		
      	// 开始来对集合中的@ControllerAdvice类型的bean进行遍历
		for (ControllerAdviceBean adviceBean : adviceBeans) {
          	// 获取得到@ControllerAdvice类型的类型
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			}
          	
          	 // 收集没有@RequestMapping注解但是存在@ModelAttribute注解的方法
			Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
			if (!attrMethods.isEmpty()) {
				this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
			}
          	
          	 // 收集存在@InitBinder注解的方法          
			Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
				this.initBinderAdviceCache.put(adviceBean, binderMethods);
			}
          	 // 收集RequestBodyAdvice接口和ResponseBodyAdvice接口的bean添加到集合中
			if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
			}
		}

		if (!requestResponseBodyAdviceBeans.isEmpty()) {
			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
		}
		// print log
	}

这里将类上存在ControllerAdvice注解的bean进行分类:

  • 1、方法上存在@ModelAttribute注解且没有@RequestMapping注解;
  • 2、方法上存在@InitBinder注解;
  • 3、类是RequestBodyAdvice的子类;
  • 4、类是ResponseBodyAdvice的子类;

但是始终没有看到@ControllerAdvice+@ExceptionHandler注解的影子。

ExceptionHandlerExceptionResolver

因为ExceptionHandlerExceptionResolver实现了InitializingBean接口,所以重写了其中的方法org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet

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() {
  if (getApplicationContext() == null) {
    return;
  }

 // 遍历IOC容器,找到加了@ControllerAdvice注解的bean,解析其中注解成为ControllerAdviceBean
  List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
  AnnotationAwareOrderComparator.sort(adviceBeans);

  for (ControllerAdviceBean adviceBean : adviceBeans) {
    // 循环遍历每个@ControllerAdvice注解bean的原始类型
    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 (logger.isInfoEnabled()) {
        logger.info("Detected @ExceptionHandler methods in " + adviceBean);
      }
    }
    if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
      this.responseBodyAdvice.add(adviceBean);
      if (logger.isInfoEnabled()) {
        logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
      }
    }
  }
}

那么来看一下ExceptionHandlerMethodResolver的创建流程

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
  // 循环遍历类上存在@ExceptionHandler的方法
  for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
    // 1、获取得到@ExceptionHandler注解中的异常类型
    // 2、没有如果指定异常类型,那么遍历方法参数类型,将是异常类型的参数添加进俩
    for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
      addExceptionMapping(exceptionType, method);
    }
  }
}

看看具体的添加异常的过程:

private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
  // KEY为具体的异常,value是具体处理异常的方法
  // 但是这里是判断如果有重复的方法处理重复的异常是不可以的
  Method oldMethod = this.mappedMethods.put(exceptionType, method);
  if (oldMethod != null && !oldMethod.equals(method)) {
    throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
                                    exceptionType + "]: {" + oldMethod + ", " + method + "}");
  }
}

找到了处理异常的方法之后,然后看下一步的处理方式:

ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
// 如果mappedMethods不为空
if (resolver.hasExceptionMappings()) {
  // 将对应的异常处理类型和解析器保存起来
  this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
// 如果是当前类型的添加到另外一个集合中来
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
  this.responseBodyAdvice.add(adviceBean);
  if (logger.isInfoEnabled()) {
    logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
  }
}

那么容器中此时就有了这么多的异常处理器了。

在ExceptionHandlerExceptionResolver中的成员属性中

Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();

那么到底是在哪里发挥作用的呢?肯定是在异常处理阶段!!

那么来制造一个错误请求:

@RestController
@RequestMapping(path = "/intecepter")
public class IntecepterController { 
  @GetMapping(path = "/test")
  public String test(){
    int i = 1 / 0;
    return "success";
  }
}

然后看一下对应的异常处理器:

@RestControllerAdvice
public class CustomControllerAdvice {

  @ExceptionHandler(value = {Exception.class})
  public String handlerException(Exception exception){
    System.out.println("对应的异常信息是:"+exception);
    return "success";
  }
}

既然初始化已经看过了,那么接下来我们只需要看如何从exceptionHandlerAdviceCache中取出来对应的ExceptionHandlerMethodResolver中的方法来进行处理异常即可。

利用断点来进行说明:直接来到org.springframework.web.servlet.DispatcherServlet#processDispatchResult方法中

然后来到:org.springframework.web.servlet.DispatcherServlet#processHandlerException中

if (this.handlerExceptionResolvers != null) {
  for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
    exMv = resolver.resolveException(request, response, handler, ex);
    if (exMv != null) {
      break;
    }
  }
}

紧接着来到org.springframework.web.servlet.handler.HandlerExceptionResolverComposite#resolveException方法中:

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

然后来到:

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

  if (shouldApplyTo(request, handler)) {
    prepareResponse(ex, response);
    // 处理异常
    ModelAndView result = doResolveException(request, response, handler, ex);
    if (result != null) {
      // Explicitly configured warn logger in logException method.
      logException(ex, request);
    }
    return result;
  }
  else {
    return null;
  }
}

然后来到org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException方法中来:

protected final ModelAndView doResolveException(
  HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
  return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}

然后来到:

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
  // 如何找到的呢?
  ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
  // ...... 
}

来到org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#getExceptionHandlerMethod方法中来:

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

  Class<?> handlerType = null;

  if (handlerMethod != null) {
	// 获取得到异常发生所在controller的类
    handlerType = handlerMethod.getBeanType();
    // 从缓存中去查询,哪个异常处理类能够处理这里的异常
    ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
    if (resolver == null) {
      resolver = new ExceptionHandlerMethodResolver(handlerType);
      this.exceptionHandlerCache.put(handlerType, resolver);
    }
    // 将异常找出来,然后利用对应的异常处理器来进行处理
    Method method = resolver.resolveMethod(exception);
    if (method != null) {
      // 对象和方法都有了!那么就缺少参数了
      return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
    }
    // ......

  return null;
}

再来到org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException方法中来,找到如下代码:

exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);

来到org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle方法中来:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
                            Object... providedArgs) throws Exception {
  // 可以看到providedArgs参数中包含:exception和handlerMethod
  // 这也是方法参数中可以书写的内容
  Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
  // ......
}

开始利用反射来调用方法,最终执行到了@ControllerAdvice标注了的方法中来。

posted @ 2023-03-07 17:53  雩娄的木子  阅读(144)  评论(0编辑  收藏  举报