【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处理。
解析器 | 说明 |
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 mappings
和default 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状态代码。
核心属性与构造方法
/** * 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); }