本系列上一篇 : DispatcherServlet 请求处理主逻辑 : 3. 通过 HandlerAdapter 执行 Handler
经过DispatcherServlet
请求处理主逻辑#doDispatch
以上各个步骤的执行(Handler
选择和执行),现在请求已经被处理。处理分为两种情况 :
- 处理过程没有遇到错误/异常,处理结果为一个
ModelAndView mv
; - 处理过程遇到错误/异常,该错误/异常被统一为一个
Exception dispatchException
;
以上两种情况不可能同时出现,要么是第一种,要么是第二种。针对这两种情况,DispatcherServlet
做了统一的处理,具体的调用语句如下 :
// DispatcherServlet#doDispatch 代码片段
// 参数介绍 :
// 1. processedRequest 是正在处理中的异常
// 2. response 是响应对象
// 3. mappedHandler 是处理当前请求的 Handler, 以 HandlerExecutionChain 形式存在
// 4. mv 是以上步骤无异常时执行 Handler 处理请求的结果
// 5. dispatchException 时以上步骤遇到异常时异常对象
// 注意 :
// 1. 这里 mv 和 dispatchException 不会同时有值,至少有一个是 null
// 2. 这里 mv 和 dispatchException 也可能都是 null, 比如控制器方法使用了 @ResponseBody 注解直接返回了 JSON/XML数据,
// 而无需视图渲染的情况
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
该过程主要是消费之前Handler
处理请求的结果mv
/dispatchException
,渲染相应的视图,开发人员指定的视图,缺省视图,或者错误视图,并将最终结果写入到响应对象中去。
该过程对应方法processDispatchResult
实现如下 :
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 记录是否所要渲染的视图是一个错误页面视图,用于一些属性标记和清除动作
boolean errorView = false;
if (exception != null) {
// 以上 Hander 选择/执行遇到错误/异常的情况
// 注意这种情况下,此方法调用开始时参数 mv 一定为 null,这是由DispatcherServlet#doDispatch
// 方法的逻辑流程所决定的。
// 针对这种出现错误/异常的情况,试图根据错误/异常信息分析得到相应的数据模型和错误视图,
// 包装为一个 ModelAndView, 记录到 mv。
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
// 尝试从异常分析得到对应的错误数据模型和错误页面视图ModelAndView,也保存到 mv
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 该方法以上代码对异常存在的情况做了特殊处理:从异常信息中解析相应的ModelAndView到mv,
// 这样,针对Handler选择/执行中异常和正常两种情况,该方法就将它们可以统一到同样的处理模式了:
// 解析和渲染 mv
// 此时还有一点需要注意 : 当前 mv 也可能是 null,比如控制器方法直接返回 JSON/XML而无需
// 视图渲染的情况,这种情况下当前方法在这里就算结束了,仅为它进入不了下面的 if 语句块
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 解析和渲染 mv 到响应对象
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) {
// 调用各个 HandlerInterceptor 的完成时拦截处理逻辑
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
从以上代码可以看出,该过程主要的任务是 :
- 如果是
Handler
选择/执行出现错误/异常的情况,尝试根据错误/异常信息分析得到相应的ModelAndView
到mv
;如果不是是
Handler
选择/执行出现错误/异常的情况,这一步跳过; - 使用
ModelAndView mv
解析和渲染视图,将结果写入到响应对象;
为了完成任务,processDispatchResult
方法又使用了两个重要的方法 :
ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
根据异常信息解析相应的渲染错误视图的
ModelAndView
void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
解析视图和渲染视图
我们接下来对这两个重要方法做分析。
1. processHandlerException
翻译异常到模型和视图ModelAndView
/**
* Determine an error ModelAndView via the registered HandlerExceptionResolvers.
* @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
* (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding ModelAndView to forward to
* @throws Exception if no error ModelAndView found
*/
@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...
// 检查 DispatcherServlet 的属性 handlerExceptionResolvers,看看其中的哪个
// HandlerExceptionResolver 能够处理该异常。如果某个 HandlerExceptionResolver
// 能处理指定的异常,他会返回一个非 null ModelAndView 对象。该非 null
// ModelAndView 对象又有两种状态 :
// 1. 为空, #isEmpty 方法返回true,表示该异常可被处理但是没有相应的错误页面视图
// 2. 不为空,#isEmpty 方法返回false,表示该异常可被处理且有相应的错误页面视图
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历 handlerExceptionResolvers 中的每个 HandlerExceptionResolver 看它能否
// 解析指定异常
// 这里 handlerExceptionResolvers 是在 DispatcherServlet 初始化时被填充的
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
// 返回 exMv 不为 null 表示当前HandlerExceptionResolver能够解析该异常,则跳出循环;
// 返回 exMv 为 null 表示当前HandlerExceptionResolver不能够解析该异常,继续循环下一个;
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
// 异常能被某个HandlerExceptionResolver解析的情况
if (exMv.isEmpty()) {
// 异常能被某个HandlerExceptionResolver解析,但是不存在对应的错误页面的情况
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
// 如果 exMv 中没有 View,也尝试应用缺省视图
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
// 异常能被某一个HandlerExceptionResolver解析,并且有相应的错误页面的情况,
// 返回相应的错误数据模型和错误页面视图 ModelAndView exMv
return exMv;
}
// 异常不能被任何一个HandlerExceptionResolver解析, 继续抛出该异常
throw ex;
}
2. render
解析和渲染视图
/**
* Render the given ModelAndView.
* This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// Determine locale for request and apply it to the response.
// 本地化处理, 属性 this.localeResolver 在 DispatcherServlet 初始化时由初始化方法 initLocaleResolver 填充
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
// 尝试从 ModelAndView mv中分析得到 View view
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 根据视图名称找到相应的视图对象,该方法使用到了 this.viewResolvers 属性中注册的各个
// ViewResolver 尝试解析该视图名称,如果某个 ViewResolver 能解析该视图名称,
// 则使用该 ViewResolver 将视图名称解析成功一个 View 对象返回
// this.viewResolvers 是 DispatcherServlet 初始化时通过初始化方法 initViewResolvers
// 填充的,常见的一些视图解析器 :
// ContentNegotiatingViewResolver
// BeanNameViewResolver
// FreeMarkerViewResolver (仅在开发人员使用了Freemarker模板引擎时才被注册)
// ViewResolverComposite
// InternalResourceViewResolver
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
// 如果不存在能够解析当前视图名称的 ViewResolver,则抛出该异常
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// 现在已经从 ModelAndView 中分析得到 View 对象了,现在使用它进行结合要渲染的数据进行视图渲染
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
// 设置响应状态
response.setStatus(mv.getStatus().value());
}
// 视图渲染 :
// 结合数据模型和视图模板形成最终的视图数据写入到响应对象,
// 不过这里视图数据写入后并不会发送给请求者。将响应数据最终
// 发送给请求者也就是浏览器端的动作,会在随后调用控制返回给
// 应用服务器时,由应用服务器执行 :
// 1. 从用户直观的感受来讲,当前方法结束时,用户在浏览器上看不到写入的视图数据。
// 2. 针对应用服务器是Tomcat的情况,是在 CoyoteAdapter#service 中请求被Spring MVC
// 处理完之后调用 response.finishResponse() 才真正将数据返回给请求者。
// view 类型分析 :
// 1. 如果是页面跳转,此处 view 是 RedirectView
// 2. 如果是一般视图模板,比如Freemarker 模板,此处是
// 3. Spring Boot + Spring MVC 应用中,缺省情况下,如果是错误页面,
// 这里是 ErrorMvcAutoConfiguration$StaticView
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
相关文章
- Spring MVC : DispatcherServlet请求处理的主逻辑(文字版)
- Spring MVC DispatcherServlet 策略初始化 – initHandlerExceptionResolvers
- DispatcherServlet 请求处理主逻辑 : 1. 选择 Handler
- DispatcherServlet 请求处理主逻辑 : 2. 选择 Handler 对应的 HandlerAdapter
- DispatcherServlet 请求处理主逻辑 : 3. 通过 HandlerAdapter 执行 Handler
- HandlerExceptionResolver有哪些类型,分别做什么?