SpringMVC常见组件之HandlerExceptionResolver分析-1

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringMVC常见组件之HandlerExceptionResolver分析-1

【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 resolvers,将具体任务委派给某个具体的resolver处理。

f0432eeb496b42d2baf3c8cccbaa6b55.png

解析器 说明
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,解析异常时遍历该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是视图名称,也就是其将异常类名映射到对应视图名。发生异常时 ,会跳转对应页面。


SimpleMappingExceptionResolver是org.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


如下所示,是一个典型的模板方法。由determineViewName、determineStatusCode、applyStatusCodeIfPossible以及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状态代码。


1.png

核心属性与构造方法

/**
 * 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);
}
目录
相关文章
|
容器
SpringMVC常见组件之HandlerExceptionResolver分析-2
SpringMVC常见组件之HandlerExceptionResolver分析-2
77 0
|
7月前
|
设计模式
SpringMVC常见组件之DataBinder数据绑定器分析
SpringMVC常见组件之DataBinder数据绑定器分析
345 0
|
XML 缓存 Java
SpringMVC常见组件之ViewResolver分析
本文我们尝试总结分析SpringMVC体系中的视图解析器-ViewResolver。其根据name解析视图View,通常鼓励实现类考虑国际化策略实现。
122 0
|
7月前
|
XML 存储 Java
SpringMVC常见组件之HandlerMapping分析
SpringMVC常见组件之HandlerMapping分析
176 0
|
7月前
|
XML 缓存 前端开发
SpringMVC常见组件之HandlerAdapter分析
SpringMVC常见组件之HandlerAdapter分析
100 0
|
XML 前端开发 Java
SpringMVC常见组件之View分析
SpringMVC常见组件之View分析
101 0
|
XML 存储 前端开发
解析 SpringMVC 父子容器及九大内置组件加载过程
解析 SpringMVC 父子容器及九大内置组件加载过程
122 1
java202304java学习笔记第六十二天-ssm-springMvc中组件分析
java202304java学习笔记第六十二天-ssm-springMvc中组件分析
47 0
|
存储 设计模式 前端开发
浅谈SpringMVC五大组件以及对执行原理的分析。
Spring MVC是包含在spring中的一个基于MVC设计思想的Web应用程序框架,目的是简化开发工作,提高开发效率。
浅谈SpringMVC五大组件以及对执行原理的分析。
|
Java Spring 容器
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)
享读SpringMVC源码5-异常处理HandlerExceptionResolver(上)