前两节分析了HandlerAdapter的handler方法过程,接下来回到doDispatch方法里来看其后续处理工作都还有哪些:
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
// ModelAndView不为空,但是没有View对象则尝试为其生成一个默认的视图名
if (mv != null && !mv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
mv.setViewName(defaultViewName);
}
}
}
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}
public String getViewName(HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
return (this.prefix + transformPath(lookupPath) + this.suffix);
}
protected String transformPath(String lookupPath) {
String path = lookupPath;
if (this.stripLeadingSlash && path.startsWith(SLASH)) {
path = path.substring(1);
}
if (this.stripTrailingSlash && path.endsWith(SLASH)) {
path = path.substring(0, path.length() - 1);
}
if (this.stripExtension) {
path = StringUtils.stripFilenameExtension(path);
}
if (!SLASH.equals(this.separator)) {
path = StringUtils.replace(path, SLASH, this.separator);
}
return path;
}
具体工作委托给了RequestToViewNameTranslator接口的实现类,该方法比较简单。
/**
* 应用已注册拦截器的后置方法。
*
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
以上这两步都比较简单,接下来看返回视图结果的处理。
private void processDispatchResult(
HttpServletRequest request,
HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 处理异常信息
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
// 尝试解析视图和模型;
// wasCleared:判断当前模型和视图是否已经被标识为清空,且当前视图和模型是否同时为空
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) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
processDispatchResult处理程序选择和处理程序调用的结果,该结果要么是一个ModelAndView,要么是一个要解析为ModelAndView的异常。该方法的核心是render方法,用来解析并呈现视图和模型。这也是一次完整请求最后要处理的部分。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
// 确定请求的区域设置并将其应用于响应。
Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
// 获取视图名
String viewName = mv.getViewName();
// 未能获取视图名,则解析视图名
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
// 获取到视图名,再次判断当前ModelAndView对象中是否包含真正的View对象,
// 因为接下来需要调用View对象的render方法
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() + "'");
}
}
// 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());
}
// 调用View对象的render方法完成视图解析
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
其核心处理委托给了View对象的render方法:
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
// 合并模型
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
// 如果当前请求为下载的话,预先处理请求头
prepareResponse(request, response);
// 为客户端返回视图
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
这里我们以InternalResourceView为例看看一下具体的返回过程:
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
// 曝光模型
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
// 空的模板方法 //todo
exposeHelpers(request);
// Determine the path for the request dispatcher.
// 获取转发路径
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
// 获取可应用于 forward/include 的RequestDispatcher
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// 处理include
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
// 处理转发
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
对于返回的普通的视图,如JSP等,最后还是调用的RequestDispatcher.forward方法进行转发而已。
到这里除了一些后续处理,一次完成的SpringMVC请求工作就完成了。当然这里只分析了普通的ModelAndView对象,大家可以多结合自己的实例,多多分析。