SpringMVC常见组件之HandlerExceptionResolver分析

【1】处理handler返回结果

前置流程,在DispatcherServlet处理请求的过程最后处理结果时,会判断处理过程中异常是否存在,如果异常存在就会尝试对异常进行处理。

DispatcherServlet#doDispatch -->
DispatcherServlet#processDispatchResult-->
DispatcherServlet#processHandlerException

处理handler的返回结果源码:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
	@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
	@Nullable Exception exception) throws Exception {

boolean errorView = false;
// 如果异常不为null,判断异常是否为ModelAndViewDefiningException进行不同处理
if (exception != null) {
	if (exception instanceof ModelAndViewDefiningException) {
		logger.debug("ModelAndViewDefiningException encountered", exception);
		mv = ((ModelAndViewDefiningException) exception).getModelAndView();
	}
	else {
	// 获取handler 
		Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
		// 处理异常  获取MV  可能为null哦
		mv = processHandlerException(request, response, handler, exception);
		errorView = (mv != null);
	}
}

// mv.wasCleared = view==null&&model is empty
// !mv.wasCleared()很关键哦,因为下游可能会返回空的mv
if (mv != null && !mv.wasCleared()) {
	render(mv, request, response);
	if (errorView) {
		WebUtils.clearErrorRequestAttributes(request);
	}
}
else {
	if (logger.isTraceEnabled()) {
		logger.trace("No view rendering, null ModelAndView returned.");
	}
}

if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
	// Concurrent handling started during a forward
	return;
}

if (mappedHandler != null) {
	// Exception (if any) is already handled..
	mappedHandler.triggerAfterCompletion(request, response, null);
}
}

简单来说就是判断是否有异常,如果有异常就尝试进行处理获取MV。如果MV不为null,就进行视图渲染。最后会进行一些辅助操作如WebUtils.clearErrorRequestAttributes(request);清理request中关于error的属性,mappedHandler.triggerAfterCompletion(request, response, null);-调用每个拦截器的afterCompletion方法。

处理异常逻辑代码如下所示:

@Nullable
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) {
	// (this.view == null && CollectionUtils.isEmpty(this.model));
		if (exMv.isEmpty()) {
// EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION"
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		// We might still need view name translation for a plain error model...
		if (!exMv.hasView()) { // this.view != null
			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;
}

代码解释如下:

  • ① 从request移除属性HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
  • ② 遍历handlerExceptionResolvers ,对异常进行处理,尝试得到一个exMv 。如果返回的是json,那么exMv 为null。
  • ③ 如果exMv 为null,则直接抛出ex;
  • ④ 如果exMV 不为null,则会根据exMV是否为空以及是否有view进行逻辑处理。最后会暴露错误属性给request。

exposeErrorRequestAttributes暴露属性源码如下所示:

public static void exposeErrorRequestAttributes(HttpServletRequest request, Throwable ex,
		@Nullable String servletName) {

	exposeRequestAttributeIfNotPresent(request, ERROR_STATUS_CODE_ATTRIBUTE, HttpServletResponse.SC_OK);
	exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_TYPE_ATTRIBUTE, ex.getClass());
	exposeRequestAttributeIfNotPresent(request, ERROR_MESSAGE_ATTRIBUTE, ex.getMessage());
	exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_ATTRIBUTE, ex);
	exposeRequestAttributeIfNotPresent(request, ERROR_REQUEST_URI_ATTRIBUTE, request.getRequestURI());
	if (servletName != null) {
		exposeRequestAttributeIfNotPresent(request, ERROR_SERVLET_NAME_ATTRIBUTE, servletName);
	}
}

接下来我们分析异常解析器是如何处理异常的。

【2】HandlerExceptionResolver

① 异常解析器接口

HandlerExceptionResolver接口只有一个方法resolveException,返回结果是ModelAndView ,当然你也可以将错误处理结果写到response中,这时ModelAndView就为null。

public interface HandlerExceptionResolver {
// 尝试解析handler处理流程中抛出的异常,通常返回一个具体的错误页面。
// 返回的ModelAndView可能是null,也就意味着异常已经被处理,没有view需要被渲染,
//比如设置status code。
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

如下图所示,其家族树图示很清晰,除了HandlerExceptionResolverComposite外,其他都是AbstractHandlerMethodExceptionResolver的子类。HandlerExceptionResolverComposite本身并不负责异常解析处理,其内部维护了List<HandlerExceptionResolver> resolvers,将具体任务委派给某个具体的resolver处理。
在这里插入图片描述

解析器说明
SimpleMappingExceptionResolver它将异常类名映射到对应视图名。发送异常时 ,会跳转对应页面。
DefaultHandlerExceptionResolver解析spring mvc标准异常并转换为HTTP status codes
ResponseStatusExceptionResolver在异常或异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。
HandlerExceptionResolverComposite解析器组合器/委派器,将任务委派出去
AbstractHandlerMethodExceptionResolver处理HandlerMethod抛出的异常
ExceptionHandlerExceptionResolver是AbstractHandlerMethodExceptionResolver 的子类 ,可以通过@ExceptionHandler注解的方法解决异常

异常处理顺序

  • 如果是特定异常使用DefaultHandlerExceptionResolver

  • 如果存在@ExceptionHandler({RuntimeException.class})注解的方法,将执行。

  • 如果同时存在@ExceptionHandler({RuntimeException.class})和类似于@ExceptionHandler({ArithmeticException.class}),则近者优先!

  • 如果不存在@ExceptionHandler,则异常尝试使用ResponseStatusExceptionResolver

  • 最后使用SimpleMappingExceptionResolver

③ 不同环境下异常解析器

SpringMVC环境下按序注入了如下三个解析器:
在这里插入图片描述
如果此时某个异常没有解析器能够处理,则会抛出如下提示:
在这里插入图片描述

【3】HandlerExceptionResolverComposite

如下源码所示,其维护了一个List<HandlerExceptionResolver>,解析异常时遍历该list去处理,如果返回的mav != null,就作为最终结果返回。

@Nullable
private List<HandlerExceptionResolver> resolvers;

private int order = Ordered.LOWEST_PRECEDENCE;

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

【4】AbstractHandlerExceptionResolver

抽象基类,定义了属性与入口模板方法。也就是说所有异常解析的第一步入口就是该解析器的resolveException方法,在该方法内部提供了doResolveException方法供子类实现。

① 核心属性

private static final String HEADER_CACHE_CONTROL = "Cache-Control";

/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());

// order用来决定顺序
private int order = Ordered.LOWEST_PRECEDENCE;

// 映射的handler,也就是异常解析器应该处理哪些handler
@Nullable
private Set<?> mappedHandlers;

// 映射的handler class 也就是异常解析器应该处理哪些class
@Nullable
private Class<?>[] mappedHandlerClasses;

@Nullable
private Log warnLogger;

// 是否阻止响应缓存,默认false
private boolean preventResponseCaching = false;

如果不设置mappedHandlers或者mappedHandlerClasses,那么exception mappingsdefault error view将会针对所有的handlers。这二者又是什么呢?参考如下配置

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	 <!-- 定义默认的异常处理页面 -->
    <property name="defaultErrorView" value="default/error"/>
	<property name="exceptionAttribute" value="exception"></property>
	<!--特殊异常的处理方法-->
	<property name="exceptionMappings">
		<props>
		<!--数组越界异常,视图--error.jsp -->
			<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
		</props>
	</property>
</bean>	

② 核心方法

异常解析方法

@Override
@Nullable
public ModelAndView resolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
// 检测当前解析器是否能够应用该handler
	if (shouldApplyTo(request, handler)) {
// 判断是否阻止缓存响应,如果是则response.addHeader(HEADER_CACHE_CONTROL, "no-store");
		prepareResponse(ex, response);
		ModelAndView result = doResolveException(request, response, handler, ex);
		if (result != null) {
			// Print debug message when warn logger is not enabled.
			if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
				logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
			}
			// Explicitly configured warn logger in logException method.
			logException(ex, request);
		}
		return result;
	}
	else {
		return null;
	}
}

这里提供了一个抽象方法doResolveException让子类实现具体的异常解析逻辑,这也是核心入口方法。

@Nullable
protected abstract ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

检测当前解析器是否能够应用该handler

protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
	if (handler != null) {
		if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
			return true;
		}
		if (this.mappedHandlerClasses != null) {
			for (Class<?> handlerClass : this.mappedHandlerClasses) {
				if (handlerClass.isInstance(handler)) {
					return true;
				}
			}
		}
	}
	return !hasHandlerMappings();
}

代码解释如下:

  • ① 如果handler不为null,则进行如下判断
    • ② 判断mappedHandlers 是否存在且是否包含handler,如果是返回true;
    • ③ 判断mappedHandlerClasses 是否存在,如果存在则遍历判断handler是否为其实例
  • ④ 如果①中没有返回结果或者handler为null,则如果mappedHandlers != null || mappedHandlerClasses != null就返回false,否则返回true。

【5】SimpleMappingExceptionResolver

① 基础定义

如果想统一管理异常,就使用SimpleMappingExceptionResolver,它维护了Properties exceptionMappings,其key是异常类型,value是视图名称,也就是其将异常类名映射到对应视图名。发生异常时 ,会跳转对应页面。

SimpleMappingExceptionResolverorg.springframework.web.servlet.HandlerExceptionResolver实现,该实现允许将异常类名映射到视图名,无论是对于一组给定的处理程序还是DispatcherServlet中的所有处理程序。错误视图类似于错误页面JSP,但可以用于任何类型的异常,包括任何选中的异常,以及特定处理程序的细粒度映射。

常见配置如下所示:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	 <!-- 定义默认的异常处理页面 -->
    <property name="defaultErrorView" value="default/error"/>
	<property name="exceptionAttribute" value="exception"></property>
	<!--特殊异常的处理方法-->
	<property name="exceptionMappings">
		<props>
		<!--数组越界异常,视图--error.jsp -->
			<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
		</props>
	</property>
</bean>	

② 核心属性

/** The default name of the exception attribute: "exception". */
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";

// 维护了异常和视图映射配置
@Nullable
private Properties exceptionMappings;

// 表示排除哪些异常
@Nullable
private Class<?>[] excludedExceptions;

//默认error view name
@Nullable
private String defaultErrorView;
// 默认的status code
@Nullable
private Integer defaultStatusCode;

// Keys are view names; values are status codes  即视图与状态码映射
private Map<String, Integer> statusCodes = new HashMap<>();

// 默认异常属性名 exception
@Nullable
private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;

③ 核心方法

① doResolveException

如下所示,是一个典型的模板方法。由determineViewNamedetermineStatusCodeapplyStatusCodeIfPossible以及getModelAndView组成。这里首先会尝试获取viewName ,如果viewName 存在则理解为有对应的视图页面存在继续进行解析,否则直接就返回null。

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

	// ① 获取viewName
	String viewName = determineViewName(ex, request);
	if (viewName != null) {
		// Apply HTTP status code for error views, if specified.
		// Only apply it if we're processing a top-level request.
		Integer statusCode = determineStatusCode(request, viewName);
		if (statusCode != null) {
		// 设置响应码
			applyStatusCodeIfPossible(request, response, statusCode);
		}
		// 获取ModelAndView
		return getModelAndView(viewName, ex, request);
	}
	else {
		return null;
	}
}

② 解析视图名称determineViewName

代码如下所示,分为三个部分:

  • ① 检测ex.getClass是否在excludedExceptions 范围内,如果是则直接返回null;
  • ② 如果exceptionMappings 不为null,则尝试找到一个匹配的viewName;
  • ③ 如果viewName为null但是defaultErrorView 不为null,则使用defaultErrorView
@Nullable
protected String determineViewName(Exception ex, HttpServletRequest request) {
	String viewName = null;
	// ① 检测ex.getClass是否在excludedExceptions 范围内,如果是则直接返回null;
	if (this.excludedExceptions != null) {
		for (Class<?> excludedEx : this.excludedExceptions) {
			if (excludedEx.equals(ex.getClass())) {
				return null;
			}
		}
	}
//	② 如果exceptionMappings 不为null,则尝试找到一个匹配的viewName;
	// Check for specific exception mappings.
	if (this.exceptionMappings != null) {
		viewName = findMatchingViewName(this.exceptionMappings, ex);
	}
//③ 如果viewName为null但是defaultErrorView 不为null,则使用defaultErrorView 
	if (viewName == null && this.defaultErrorView != null) {
		if (logger.isDebugEnabled()) {
			logger.debug("Resolving to default view '" + this.defaultErrorView + "'");
		}
		viewName = this.defaultErrorView;
	}
	return viewName;
}

③ 解析状态码determineStatusCode

如下所示,根据viewName从statusCodes中获取,如果获取不到就返回defaultStatusCode。

@Nullable
protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
	if (this.statusCodes.containsKey(viewName)) {
		return this.statusCodes.get(viewName);
	}
	return this.defaultStatusCode;
}

applyStatusCodeIfPossible方法如下所示,如果获取到statusCode后,将会给响应设置状态码并设置属性到request中。

protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
	if (!WebUtils.isIncludeRequest(request)) {
		if (logger.isDebugEnabled()) {
			logger.debug("Applying HTTP status " + statusCode);
		}
		response.setStatus(statusCode);
		request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
	}
}

④ 获取ModelAndView

如下所示,new一个ModelAndView实例并向model中添加属性this.exceptionAttribute, ex

protected ModelAndView getModelAndView(String viewName, Exception ex) {
	ModelAndView mv = new ModelAndView(viewName);
	if (this.exceptionAttribute != null) {
		mv.addObject(this.exceptionAttribute, ex);
	}
	return mv;
}

【6】DefaultHandlerExceptionResolver

① 基础定义

HandlerExceptionResolver的默认实现,解析标准Spring MVC异常并将其转换为相应的HTTP状态代码。

ExceptionHTTP Status Code
HttpRequestMethodNotSupportedException405 (SC_METHOD_NOT_ALLOWED)
HttpMediaTypeNotSupportedException415 (SC_UNSUPPORTED_MEDIA_TYPE)
HttpMediaTypeNotAcceptableException406 (SC_NOT_ACCEPTABLE)
MissingPathVariableException500 (SC_INTERNAL_SERVER_ERROR)
MissingServletRequestParameterException400 (SC_BAD_REQUEST)
ServletRequestBindingException400 (SC_BAD_REQUEST)
ConversionNotSupportedException500 (SC_INTERNAL_SERVER_ERROR)
TypeMismatchException400 (SC_BAD_REQUEST)
HttpMessageNotReadableException400 (SC_BAD_REQUEST)
HttpMessageNotWritableException500 (SC_INTERNAL_SERVER_ERROR)
MethodArgumentNotValidException400 (SC_BAD_REQUEST)
MissingServletRequestPartException400 (SC_BAD_REQUEST)
BindException400 (SC_BAD_REQUEST)
NoHandlerFoundException404 (SC_NOT_FOUND)
AsyncRequestTimeoutException503 (SC_SERVICE_UNAVAILABLE)

核心属性与构造方法

/**
 * Log category to use when no mapped handler is found for a request.
 * @see #pageNotFoundLogger
 */
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";

/**
 * Additional logger to use when no mapped handler is found for a request.
 * @see #PAGE_NOT_FOUND_LOG_CATEGORY
 */
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);

//这里需要注意的是order,也就是次序,决定了springmvc遍历时首先使用哪个异常解析器
//Ordered.LOWEST_PRECEDENCE = Integer.MAX_VALUE
// HIGHEST_PRECEDENCE = Integer.MIN_VALUE
public DefaultHandlerExceptionResolver() {
	setOrder(Ordered.LOWEST_PRECEDENCE);
	setWarnLogCategory(getClass().getName());
}

AbstractHandlerExceptionResolver类的order默认值是Ordered.LOWEST_PRECEDENCE

② 核心方法异常解析

逻辑很清晰,根据异常类型进行不同的处理。

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

	try {
		if (ex instanceof HttpRequestMethodNotSupportedException) {
			return handleHttpRequestMethodNotSupported(
					(HttpRequestMethodNotSupportedException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMediaTypeNotSupportedException) {
			return handleHttpMediaTypeNotSupported(
					(HttpMediaTypeNotSupportedException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMediaTypeNotAcceptableException) {
			return handleHttpMediaTypeNotAcceptable(
					(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
		}
		else if (ex instanceof MissingPathVariableException) {
			return handleMissingPathVariable(
					(MissingPathVariableException) ex, request, response, handler);
		}
		else if (ex instanceof MissingServletRequestParameterException) {
			return handleMissingServletRequestParameter(
					(MissingServletRequestParameterException) ex, request, response, handler);
		}
		else if (ex instanceof ServletRequestBindingException) {
			return handleServletRequestBindingException(
					(ServletRequestBindingException) ex, request, response, handler);
		}
		else if (ex instanceof ConversionNotSupportedException) {
			return handleConversionNotSupported(
					(ConversionNotSupportedException) ex, request, response, handler);
		}
		else if (ex instanceof TypeMismatchException) {
			return handleTypeMismatch(
					(TypeMismatchException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMessageNotReadableException) {
			return handleHttpMessageNotReadable(
					(HttpMessageNotReadableException) ex, request, response, handler);
		}
		else if (ex instanceof HttpMessageNotWritableException) {
			return handleHttpMessageNotWritable(
					(HttpMessageNotWritableException) ex, request, response, handler);
		}
		else if (ex instanceof MethodArgumentNotValidException) {
			return handleMethodArgumentNotValidException(
					(MethodArgumentNotValidException) ex, request, response, handler);
		}
		else if (ex instanceof MissingServletRequestPartException) {
			return handleMissingServletRequestPartException(
					(MissingServletRequestPartException) ex, request, response, handler);
		}
		else if (ex instanceof BindException) {
			return handleBindException((BindException) ex, request, response, handler);
		}
		else if (ex instanceof NoHandlerFoundException) {
			return handleNoHandlerFoundException(
					(NoHandlerFoundException) ex, request, response, handler);
		}
		else if (ex instanceof AsyncRequestTimeoutException) {
			return handleAsyncRequestTimeoutException(
					(AsyncRequestTimeoutException) ex, request, response, handler);
		}
	}
	catch (Exception handlerEx) {
		if (logger.isWarnEnabled()) {
			logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
		}
	}
	return null;
}

这里以HttpRequestMethodNotSupportedException为例,如下所示当异常为HttpRequestMethodNotSupportedException类型时其首先尝试获取支持的方法,如果获取到就放到响应头。然后调用response.sendError方法,最后返回一个空的ModelAndView实例。

protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {

	String[] supportedMethods = ex.getSupportedMethods();
	if (supportedMethods != null) {
		response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
	}
	// 405
	response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
	return new ModelAndView();
}

【7】ResponseStatusExceptionResolver

① 基础定义

在异常或异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。

HandlerExceptionResolver使用@ResponseStatus注解将异常映射到HTTP STATUS CODE。默认在DispatcherServlet、MVC java配置以及MVC namespace,都可以使用该异常解析器。从4.2开始,该解析器还递归查找原因异常中存在的@ResponseStatus,从4.2.2开始,该解析器支持自定义组合注解中@ResponseStatus的属性重写。从5.0开始,该解析器支持ResponseStatusException。

ResponseStatus注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus {

	@AliasFor("code")
	HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;

	@AliasFor("value")
	HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;

	String reason() default "";

}

该注解使用status code和异常原因reason来标记一个方法或者异常类。当调用处理程序方法时,状态码将应用于HTTP响应,并通过其他方式覆盖状态信息,如 ResponseEntity或请求重定向redirect:

当在异常类上使用该注解或者设置注解属性reason时,将会使用HttpServletResponse.sendError方法。对于HttpServletResponse.sendError,响应被视为已完成,不应写入任何其他文件。此外,Servlet容器通常会编写一个HTML错误页面,因此使用reason不适合REST API。对于这种情况,最好使用org.springframework.http.ResponseEntity作为返回类型,并避免使用@ResponseStatus

注意,控制器类也可以用@ResponseStatus注解,然后由所有@RequestMapping注解的方法继承。

② 核心解析方法

如下所示,这里根据异常类型不同,尝试走三种不同的逻辑:

  • resolveResponseStatusException
  • resolveResponseStatus
  • doResolveException,没有看错就是递归调用了自己哦
@Override
@Nullable
protected ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

	try {
	// 如果异常是ResponseStatusException类型,直接走resolveResponseStatusException逻辑
		if (ex instanceof ResponseStatusException) {
			return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
		}
// 否则尝试获取注解@ResponseStatus 
		ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
		if (status != null) {
		// 如果获取到注解,则执行resolveResponseStatus
			return resolveResponseStatus(status, request, response, handler, ex);
		}
// 否则,如果其cause是Exception类型,不是error,就调用doResolveException
		if (ex.getCause() instanceof Exception) {
			return doResolveException(request, response, handler, (Exception) ex.getCause());
		}
	}
	catch (Exception resolveEx) {
	// 如果解析过程中抛出了异常,打印日志
		if (logger.isWarnEnabled()) {
			logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
		}
	}
	// 最后,如果处理不了异常,就返回null
	return null;
}

① resolveResponseStatusException

如下所示这是一个模板方法处理ResponseStatusException,默认实现则是获取响应头然后委派给applyStatusAndReason方法(附带着statusCode和reason)。

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

	ex.getResponseHeaders().forEach((name, values) ->
			values.forEach(value -> response.addHeader(name, value)));

	return applyStatusAndReason(ex.getRawStatusCode(), ex.getReason(), response);
}

② applyStatusAndReason

这里我们看一下applyStatusAndReason的默认实现:

  • 如果reason为空,则调用response.sendError(statusCode);
  • 否则的话获取resolvedReason ,然后调用response.sendError(statusCode, resolvedReason);
  • 最后返回一个空的ModelAndView实例。
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
		throws IOException {

	if (!StringUtils.hasLength(reason)) {
		response.sendError(statusCode);
	}
	else {
		String resolvedReason = (this.messageSource != null ?
				this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
				reason);
		response.sendError(statusCode, resolvedReason);
	}
	return new ModelAndView();
}

response.sendError:发送一个错误响应给客户端。服务端会创建一个默认html格式的错误页,页面包含了指定的信息。格式为“text/html”,如果该status code在web应用中声明了error page,则会重定向到指定的error page。如果response已经被提交,那这个方法则会抛出IllegalStateException。

③ resolveResponseStatus

如下所示其是一个模板方法用来处理@ResponseStatus注解,获取注解的statusCode 和reason ,然后委派给方法applyStatusAndReason。

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

	int statusCode = responseStatus.code().value();
	String reason = responseStatus.reason();
	return applyStatusAndReason(statusCode, reason, response);
}

【8】AbstractHandlerMethodExceptionResolver

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

ExceptionHandlerExceptionResolver解析器支持你添加使用自定义参数解析器和返回结果处理器,当然你也可以通过setArgumentResolverssetReturnValueHandlers(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实现了ApplicationContextAwareInitializingBean接口。

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

  • 实现了InitializingBean接口意味着在实例化时会调用其afterPropertiesSet方法。
    在这里插入图片描述

① 核心属性和构造方法

// 自定义的参数解析器
@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<HandlerMethodReturnValueHandler> 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<ControllerAdviceBean> 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;
	}
}

参考博文:SpringMVC中异常处理与ControllerAdvice捕捉全局异常

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值