012-Spring MVC 异常处理源码解析

异常源码解析

DispatcherServlet的doDispatch方法最后调用了如下方法。
最后一个参数就是我们执行过程中出现的异常。

this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

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) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }

        if (mv != null && !mv.wasCleared()) {
            this.render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        } else if (this.logger.isTraceEnabled()) {
            this.logger.trace("No view rendering, null ModelAndView returned.");
        }

        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
            }

        }
    }

如果异常是ModelAndViewDefiningException或其子类异常,则调用其getModelAndView方法进行处理。此方式我们用得较少。

其他异常会调用DispatcherServlet的processHandlerException方法

processHandlerException方法

	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;
		if (this.handlerExceptionResolvers != null) {
			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);
				}
			}
			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;
		}

		throw ex;
	}

源码看到这里,终于到了HandlerExceptionResolver异常解析器,而且是一个解析器链。通过HandlerExceptionResolver的resolveException方法处理异常,该方法返回ModelAndView对象。

在这里插入图片描述
如图:此处只有两个HandlerExceptionResolver,第一个是默认的DefaultErrorAttributes,是查看源码,非常简单,就是将异常存放到request的attribute。
另一个是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;
	}

我们可以看到HandlerExceptionResolverComposite不是直接处理,而是又维护了一个resolvers 列表,该列表包含的Resolver如下:

在这里插入图片描述

代码会依次调用这些resolver,遇到一个resolveException方法不返回null则直接返回对应的mv对象。

内置的 HandlerExceptionResolver

如果在请求映射期间发生异常或从请求处理程序(例如@Controller)抛出异常,则DispatcherServlet把异常委托给HandlerExceptionResolver 异常处理器链来解决异常并返回错误响应。

HandlerExceptionResolver描述
SimpleMappingExceptionResolver异常类名称和错误视图名称之间的映射。用于在浏览器应用程序中呈现错误页面。
DefaultHandlerExceptionResolver解决 Spring MVC 引发的异常并将它们映射到 HTTP 状态代码。
ResponseStatusExceptionResolver使用@ResponseStatus注释解决异常,并根据注释中的值将它们映射到 HTTP 状态代码。
ExceptionHandlerExceptionResolver通过调用@ExceptionHandler、@Controller或 @ControllerAdvice类中的方法来解决异常。

@ControllerAdvice和@ExceptionHandler处理

@ControllerAdvice和@ExceptionHandler是通过ExceptionHandlerExceptionResolver来处理的,debug后最终会执行到 ExceptionHandlerExceptionResolver 的 doResolveHandlerMethodException 方法。该方法最重要的就是第一句

ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);

此方法就是获取异常的最终处理方法,也就是我们定义的 @ExceptionHandler 注解的方法。

getExceptionHandlerMethod 方法定义

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

此方法中只有两个cache我们需要关注,exceptionHandlerCache 和 exceptionHandlerAdviceCache

exceptionHandlerCache

exceptionHandlerCache 在 new ExceptionHandlerMethodResolver(handlerType); 构造方法中我们可以知道,它会扫描当前Controller的所有方法,并且将方法中带有@ExceptionHandler注解的方法放入按异常的类型放入mappedMethods,最终在执行resolver.resolveMethod方法时做匹配处理,得到最终的异常处理方法。

exceptionHandlerAdviceCache

ExceptionHandlerExceptionResolver 在初始化的时候就会扫描 @ControllerAdvice 注解的 bean。初始化的时候也会根据@ControllerAdvice 注解的bean来new ExceptionHandlerMethodResolver放入该缓存。

通过源码我们可以知道,如果在对应的请求所在的 Controller 有 @ExceptionHandler 注解的方法能出来该异常,那么优先使用该方法来处理异常。如果没有则使用 @ControllerAdvice 注解的 bean 中对应的处理方法。如何还没有匹配的处理方法,那么spring MVC将抛出给 servlet 容器处理。

上一篇:Spring MVC全局异常处理
下一篇:CORS跨域介绍

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值