概述
ResponseStatusExceptionResolver
是Spring MVC
内置实现的一个HandlerExceptionResolver
,其主要功能如下 :
- 如果所发生异常是
ResponseStatusException
,则从中解析status
,reason
,然后调用response.sendError
; - 否则如果所发生异常上使用了注解
@ResponseStatus
,则从中解析status
,reason
,然后调用response.sendError
; - 否则如果所发生异常的
cause
也是一个异常,则对其递归执行该流程;
以上主要功能主要体现在其方法#doResolveException
中。
如果ResponseStatusExceptionResolver
能够处理某个异常,最终调用了response.sendError
,则可以说ResponseStatusExceptionResolver
处理完了这个异常,它会返回一个空ModelAndView
对象,调用者得到此信息就不用再继续处理该异常了,通常这种情况下Servlet
容器会跳转到一个错误页面展示该status
,reason
错误信息。
如果ResponseStatusExceptionResolver
不能处理指定异常,则方法#doResolveException
会返回null
,表示调用者需要用其他手段继续处理该异常。
另外,ResponseStatusExceptionResolver
实现了接口MessageSourceAware
,也就是说它会接收一个MessageSource
用于解析reason
对应的消息,这也是一种国际化/本地化消息处理的一种体现。
虽然ResponseStatusExceptionResolver
的核心逻辑方法是#doResolveException
,但是HandlerExceptionResolver
约定的它对外提供服务的功能方法是#resolveException
,此方法实现在基类AbstractHandlerExceptionResolver
中 :
// AbstractHandlerExceptionResolver 代码片段
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
// 对缺省使用的 ResponseStatusExceptionResolver 实例来讲,shouldApplyTo 方法可以被认为总是返回 true
if (shouldApplyTo(request, handler)) {
// 对响应对象做一些准备工作,主要是根据是否要禁止缓存的指令设置相应的头部
prepareResponse(ex, response);
// 这里才是调用 ResponseStatusExceptionResolver 所实现的核心逻辑
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print warn message when warn logger is not enabled...
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// warnLogger with full stack trace (requires explicit config)
logException(ex, request);
}
return result;
}
else {
return null;
}
}
关于应用
1.引入
ResponseStatusExceptionResolver
是Spring MVC
缺省会启用的一个HandlerExceptionResolver
,关于该启用逻辑,可以参考Spring MVC
缺省配置机制WebMvcConfigurationSupport
中bean
定义方法handlerExceptionResolver
对方法#addDefaultHandlerExceptionResolvers
的调用。
2.使用
WebMvcConfigurationSupport
所定义的bean handlerExceptionResolver
实现类其实是HandlerExceptionResolverComposite
,它是多个HandlerExceptionResolver
对象的组合,缺省创建的ResponseStatusExceptionResolver
就在其中,这个HandlerExceptionResolverComposite bean
会被DispatcherServlet#processHandlerException
用于解析异常,HandlerExceptionResolverComposite
解析异常的过程如下 :
// HandlerExceptionResolverComposite 代码片段
@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler,Exception ex) {
if (this.resolvers != null) {
// ResponseStatusExceptionResolver 就是 this.resolvers 一员,
// 它在下面的for循环中会被使用到
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
源代码
源代码版本 :
spring-webmvc-5.1.5.RELEASE
package org.springframework.web.servlet.mvc.annotation;
// 省略 import 行
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver
implements MessageSourceAware {
@Nullable
private MessageSource messageSource;
// MessageSourceAware 接口定义的方法,
// 会在当前 bean 创建的合适时机被容器设置该属性
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Override
@Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
//第1种情况处理 : 异常自身就是 ResponseStatusException 异常的情况
if (ex instanceof ResponseStatusException) {
return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
}
//第2种情况处理 : 尝试从异常上获取注解 @ResponseStatus 信息
ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(
ex.getClass(), ResponseStatus.class);
if (status != null) {
return resolveResponseStatus(status, request, response, handler, ex);
}
//第3种情况处理 : 异常的 cause 还是异常,递归调用该方法进行处理
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;
}
/**
* Template method that handles the {@link ResponseStatus @ResponseStatus} annotation.
* <p>The default implementation delegates to {@link #applyStatusAndReason}
* with the status code and reason from the annotation.
* @param responseStatus the {@code @ResponseStatus} annotation
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the
* time of the exception, e.g. if multipart resolution failed
* @param ex the exception
* @return an empty ModelAndView, i.e. exception resolved
*/
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);
}
/**
* Template method that handles an {@link ResponseStatusException}.
* <p>The default implementation delegates to {@link #applyStatusAndReason}
* with the status code and reason from the exception.
* @param ex the exception
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the
* time of the exception, e.g. if multipart resolution failed
* @return an empty ModelAndView, i.e. exception resolved
* @since 5.0
*/
protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler)
throws Exception {
int statusCode = ex.getStatus().value();
String reason = ex.getReason();
return applyStatusAndReason(statusCode, reason, response);
}
/**
* Apply the resolved status code and reason to the response.
* <p>The default implementation sends a response error using
* {@link HttpServletResponse#sendError(int)} or
* {@link HttpServletResponse#sendError(int, String)} if there is a reason
* and then returns an empty ModelAndView.
* @param statusCode the HTTP status code
* @param reason the associated reason (may be {@code null} or empty)
* @param response current HTTP response
* @since 5.0
*/
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason,
HttpServletResponse response)
throws IOException {
if (!StringUtils.hasLength(reason)) {
// reason 为空的情况
response.sendError(statusCode);
}
else {
// reason 不为空的情况
// 如果也设置了消息源 this.messageSource, 尝试从其中解析消息
String resolvedReason = (this.messageSource != null ?
this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
reason);
response.sendError(statusCode, resolvedReason);
}
// 返回一个空 ModelAndView 对象,告知调用方请求已经被处理完,使用缺省视图解析和渲染机制
return new ModelAndView();
}
}
参考文章
- Spring MVC 概念模型 – 接口 HandlerExceptionResolver
- Spring MVC : 工具 DefaultHandlerExceptionResolver
- Spring MVC : 工具 ExceptionHandlerExceptionResolver
- Spring MVC : 工具 SimpleMappingExceptionResolver
- Spring MVC : WebMvcConfigurationSupport 中定义的 HandlerExceptionResolver 组件
- How Spring Boot Initializes the Spring MVC Application Context