【Spring】抽丝剥茧SpringMVC-异常处理HandlerExceptionResolver

源码基于SpringMVC 5.2.7

HandlerExceptionResolver也是SpringMVC重要组成部分,其重要负责对请求处理过程中的异常处理。HandlerExceptionResolver返回的也是ModelAndView对象。

HandlerExceptionResolver

请求在处理过程中可能会因为各种各样的原因抛出异常,例如参数解析时类型不匹配、参数必传而请求没传。HandlerExceptionResolver处理完异常返回的也是ModelAndView对象。HandlerExceptionResolver是一个接口类,其中只有一个接口方法HandlerExceptionResolver#resolveException。所有异常处理器将实现该接口。

跟HandlerMapping、HandlerAdapter这些组件一样,HandlerExceptionResolver也是在系统初始化的时候装配到DispatcherServlet

  1. 如果"detectAllHandlerExceptionResolvers"打开,则从IOC容器中获取所有类型为HandlerExceptionResolver的实例;否则进入2
  2. 从IOC容器中获取name为"handlerExceptionResolver"的实例;
  3. 如果步骤1、2之后已经有HandlerExceptionResolver则装配过程结束,否则进入4;
  4. 通过DispatcherServlet默认装配策略中创建HandlerExceptionResolver实例,并装配给DispatcherServlet,装配过程结束。

默认情况“detectAllHandlerAdapters”是打开的,也就是说默认情况是从IOC容器中找到所有HandlerExceptionResolver的Bean。

DispatcherServlet默认装配策略在《抽丝剥茧MVC之RequestMappingHandlerMapping》中有介绍,这里就不赘述。默认策略装配的异常处理器有3个

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.DefaultHandlerExceptionResolve

DispatcherServlet异常处理

请求处理处理结束(有可能是因为异常终止请求),DispatcherServlet会执行方法processDispatchResult。如果有异常则那么进入到DispatcherServlet#processHandlerException处理异常。

  1. 依次遍历执行DispatcherServlet中装配好的异常处理器,任何一个异常处理器返回非NULL,则终止遍历
  2. 如果所有异常处理器都没有返回非空对象,则抛异常表示异常处理本身没成功,否则进入3
  3. 判断ModelAndView对象是否是Empty(view和model都没有),如果是则返回NULL(告诉后面视图解析逻辑不用执行了,因为异常器已经将响应写会给客户端了)否则进入4
  4. 如果ModelAndView对象只有model没有view,则设置默认的view。默认的view生成规则去除头尾的"/"和扩展名,参考DefaultRequestToViewNameTranslator#transformPath
public class DispatcherServlet extends FrameworkServlet {
... ...

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

		// Success and error responses may use different content types
		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
        
        //笔者注:遍历所有的异常处理器,有一个返回非NULL则终止遍历
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
    
        //笔者注:返回的ModelAndView对象非NULL
		if (exMv != null) {
             //笔者注ModelAndView对象的view和model都是空,则返回NULL,告诉后面视图解析逻辑不用处理,因为异常器已经将响应写会给客户端了。
			if (exMv.isEmpty()) {
				request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
				return null;
			}
			// We might still need view name translation for a plain error model...
            //笔者注:只有model没有view,则获取默认的view
			if (!exMv.hasView()) {
				String defaultViewName = getDefaultViewName(request);
				if (defaultViewName != null) {
					exMv.setViewName(defaultViewName);
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using resolved error view: " + exMv, ex);
			}
			else if (logger.isDebugEnabled()) {
				logger.debug("Using resolved error view: " + exMv);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

         //笔者注:返回的ModelAndView是NULL,则抛异常表示异常处理本身出问题
		throw ex;
	}
... ...
}

@ExceptionHandler

    开发者可以自己实现一些异常处理器。SpringMVC则提供@ExceptionHandler机制,让开发者可以专注异常处理的逻辑。为了处理注解@ExceptionHandler,SpringMVC向系统中注册了异常处理器ExceptionHandlerExceptionResolver。简单说就是@ExceptionHandler给方法打标,表示该方法能处理异常。ExceptionHandlerExceptionResolver根据异常类型匹配到相应的@ExceptionHandler方法,反射执行该方法,返回ModleAndView对象。执行@ExceptionHandler方法与执行请求处理方法一样,也要用到参数解析和返回值处理,只不过两者的参数解析器和返回值处理器不同。

ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver结构

先来看一下ExceptionHandlerExceptionResolver的继承关系

主要成员属性

argumentResolvers:HandlerMethodArgumentResolverComposite类型,ExceptionHandlerExceptionResolver的参数解析器集合

returnValueHandlers:HandlerMethodReturnValueHandlerComposite类型,ExceptionHandlerExceptionResolver的返回处理器集合

messageConverters:HttpMessageConverter类型集合,某些参数解析器及返回处理器需要该组件从request body中读取对象或往response body中写入对象

contentNegotiationManager:ContentNegotiationManager类型,某些参数处理器及返回处理器需要该组件来判断MediaType

exceptionHandlerCache:ExceptionHandlerMethodResolver集合,来自于@Controller,针对当前@Controller的异常处理

exceptionHandlerAdviceCache:ExceptionHandlerMethodResolver集合,来自于@ControllerAdvice的实例,针对所有请求处理器的异常处理

responseBodyAdvice:ResponseBodyAdvice类型集合,某些返回处理器将响应写入response body前后执行的一些逻辑

ExceptionHandlerExceptionResolver初始化

ExceptionHandlerExceptionResolver初始化分两个阶段:构造函数、afterPropertiesSet

构造函数

ExceptionHandlerExceptionResolver构造函数给messageConverters添加了4个元素,分别是:

  1. org.springframework.http.converter.ByteArrayHttpMessageConverter
  2. org.springframework.http.converter.StringHttpMessageConverter
  3. org.springframework.http.converter.xml.SourceHttpMessageConverter
  4. org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter

afterPropertiesSet

afterPropertiesSet是Spring IOC容器创建bean实例的一个阶段,Spring IOC设置完实例的属性后执行afterPropertiesSet。在ExceptionHandlerExceptionResolver#afterPropertiesSet中,做3件事情

其一,初始化exceptionHandlerAdviceCache和responseBodyAdvice

  1. 从Sring IOC容器中获取所有注解@ControllerAdvice的Bean实例
  2. 依次遍历,如果实例中有@ExceptionHandler的方法,则添加到exceptionHandlerAdviceCache;如果实例类型是ResponseBodyAdvice及其子类型,则添加到responseBodyAdvice

其二,如果argumentResolvers为NULL,加载ExceptionHandlerExceptionResolver默认的参数解析器

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());

其三,如果returnValueHandlers为NULL,加载ExceptionHandlerExceptionResolver默认的返回处理器

// 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 ModelAttributeMethodProcessor(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 ModelAttributeMethodProcessor(true));

ExceptionHandlerExceptionResolver处理异常

1 找到能处理当前异常的@ExceptionHandler方法,注意只找第一个匹配的,返回ServletInvocableHandlerMethod对象

    1.1 先从当前请求处理器的异常处理方法(exceptionHandlerCache)中找

    1.2 再从全局异常处理方法(exceptionHandlerAdviceCache)中找

2 给ServletInvocableHandlerMethod对象设置参数解析器、返回处理器

3 执行ServletInvocableHandlerMethod#invokeAndHandle处理异常,该过程与RequestMappingHandlerAdapter类似,参考《RequestMappingHandlerAdapter》

    3.1 参数解析

    3.2 调用异常方法

    3.3 返回值处理

4 返回ModelAndView对象

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
		implements ApplicationContextAware, InitializingBean {
... ...
    
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
			HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

        //笔者注:找到能处理该异常的@ExceptionHandler方法 (1)当前请求处理器中找 (2)全局异常处理方法中找(@ControllerAdvice)
		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 {
			if (logger.isDebugEnabled()) {
				logger.debug("Using @ExceptionHandler " + 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 {
			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());
			}

            //笔者注: FlashMap机制,传参给下一个请求
			if (model instanceof RedirectAttributes) {
				Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
				RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
			}
			return mav;
		}
	}

    ... ...
}

配置

RequestMappingHandlerAdapter配置形式与RequestMappingHandlerMapping一样。RequestMappingHandlerMapping支持的配置方式,RequestMappingHandlerAdapter也都支持。这里不在赘述。参考《RequestMappingHandlerMapping》



目录 目录

上一篇 异步请求WebAsyncManager

下一篇 视图解析及渲染ViewResolver&View

再下一篇 ContentNegotiationManager

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值