前言:
前面的篇章我们分析了 Spring MVC 工作流程中的 HandlerMapping、HandlerAdapter 的适配过程、拦截器的工作流程,以及处理业务请求的过程,本篇我们分析一下处理完业务解析视图的方法,也就是 DispatcherServlet#processDispatchResult 方法。
Spring MVC 知识传送门:
Spring MVC 源码分析之 DispatcherServlet#getHandler 方法
Spring MVC 源码分析之 DispatcherServlet#getHandlerAdapter 方法
Spring MVC 源码分析之 AbstractHandlerMethodAdapter#handle 方法
DispatcherServlet#processDispatchResult 方法源码分析
DispatcherServlet#processDispatchResult 方法名直译就是初始调度结果,其实就是解析 ModelAndView,源码也很清晰,先是判断是否有异常,有异常就解析异常视图,否则就开始解析 ModelAndView 的流程。
//org.springframework.web.servlet.DispatcherServlet#processDispatchResult
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) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
//获取异常视图
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
//获取异常解析器
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
//异常视图解析
mv = this.processHandlerException(request, response, handler, exception);
//异常视图不为空 赋值 errorView 为 true
errorView = mv != null;
}
}
//模型视图是否为空 模型视图是否被标识为清空
if (mv != null && !mv.wasCleared()) {
//解析并渲染视图 重点关注
this.render(mv, request, response);
//errorView
if (errorView) {
//清除错误请求属性
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isTraceEnabled()) {
this.logger.trace("No view rendering, null ModelAndView returned.");
}
//判断是否是异步处理
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
//不是异步处理
if (mappedHandler != null) {
//注册后置拦截器
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
DispatcherServlet#processHandlerException 方法源码分析
DispatcherServlet#processHandlerException 方法的作用就是解析异常视图,获取所有异常解析器,遍历解析视图,解析到视图就停止循环,设置视图相关属性返回。
//org.springframework.web.servlet.DispatcherServlet#processHandlerException
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
//删除请求中的 HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
ModelAndView exMv = null;
//异常解析器为空判断
if (this.handlerExceptionResolvers != null) {
//迭代遍历
Iterator var6 = this.handlerExceptionResolvers.iterator();
while(var6.hasNext()) {
HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
//解析
exMv = resolver.resolveException(request, response, handler, ex);
//解析到视图 就跳出循环
if (exMv != null) {
break;
}
}
}
//视图为 null 判断
if (exMv != null) {
//视图不为空
if (exMv.isEmpty()) {
//设置异常属性
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
//返回
return null;
} else {
//为空 判断是否有视图
if (!exMv.hasView()) {
//没有视图 获取默认视图名称
String defaultViewName = this.getDefaultViewName(request);
//默认视图名称为空判断
if (defaultViewName != null) {
//设置视图名称
exMv.setViewName(defaultViewName);
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Using resolved error view: " + exMv, ex);
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Using resolved error view: " + exMv);
}
//设置请求相关的属性
WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
//返回视图
return exMv;
}
} else {
//视图为空抛出异常
throw ex;
}
}
DispatcherServlet#render 方法源码分析
DispatcherServlet#render 方法主要可以分为两步,分别是创建视图 View 和 解析渲染视图 view.render。
//org.springframework.web.servlet.DispatcherServlet#render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
//确认语言环境 常说的国际化
Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
//为响应设置 Locale
response.setLocale(locale);
//获取视图名称
String viewName = mv.getViewName();
View view;
//视图名称为 null 判断
if (viewName != null) {
//解析视图名称 重点关注
view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
//视图为空判断
if (view == null) {
//视图为空 抛出异常
throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
}
} else {
//视图名称为 null
//从模型视图中获取 视图
view = mv.getView();
//视图为 null 判断
if (view == null) {
//视图为 null 抛出异常
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Rendering view [" + view + "] ");
}
try {
//模型视图的状态判断
if (mv.getStatus() != null) {
//不为空 把模型视图的状态设置给 response
response.setStatus(mv.getStatus().value());
}
//视图解析渲染 重点关注
view.render(mv.getModelInternal(), request, response);
} catch (Exception var8) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Error rendering view [" + view + "]", var8);
}
throw var8;
}
}
DispatcherServlet#resolveViewName 方法源码分析
DispatcherServlet#resolveViewName 方法的主要作用是通过遍历视图解析器获取到视图,视图获取成功则停止遍历,返回视图。
//org.springframework.web.servlet.DispatcherServlet#resolveViewName
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
//视图解析器为空判断 这里的视图解析器 就是 DispatcherServlet.properties 中的视图解析器 InternalResourceViewResolver
if (this.viewResolvers != null) {
//迭代遍历视图解析器
Iterator var5 = this.viewResolvers.iterator();
while(var5.hasNext()) {
//获取视图解析器
ViewResolver viewResolver = (ViewResolver)var5.next();
//通过视图名称和语言环境 得到视图 重点关注
View view = viewResolver.resolveViewName(viewName, locale);
//为null判断
if (view != null) {
//返回视图
return view;
}
}
}
return null;
}
AbstractCachingViewResolver#resolveViewName 方法源码分析
AbstractCachingViewResolver#resolveViewName 方法主要就是创建视图,它会先判断是否允许使用缓存,然后去创建视图或者说从缓存中去获取视图,如果允许使用缓存,最终会把创建好的视图加入到缓存中,我们重点关注 createView 即可。
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
//是否允许缓存
if (!this.isCache()) {
//不允许 直接根据 viewName 和语言环境 创建一个 View 重点关注
return this.createView(viewName, locale);
} else {
//允许缓存
//根据 viewName 和语言环境 从缓存中获取 view
Object cacheKey = this.getCacheKey(viewName, locale);
View view = (View)this.viewAccessCache.get(cacheKey);
//view 为 null 判断
if (view == null) {
//缓存没有获取到 同步锁 保证线程安全
synchronized(this.viewCreationCache) {
//再次去 viewCreationCache 缓存中获取
view = (View)this.viewCreationCache.get(cacheKey);
//view 为空判断
if (view == null) {
//还是为空 就创建一个 view 重点关注
view = this.createView(viewName, locale);
//为空判断 cacheUnresolved 默认为 true
if (view == null && this.cacheUnresolved) {
//赋值为空视图 是一个没有任何实现的视图
view = UNRESOLVED_VIEW;
}
//view 不为空 加入缓存
if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
}
}
}
} else if (this.logger.isTraceEnabled()) {
this.logger.trace(formatKey(cacheKey) + "served from cache");
}
return view != UNRESOLVED_VIEW ? view : null;
}
}
UrlBasedViewResolver#createView 方法源码分析
UrlBasedViewResolver#createView 方法会判断当前请求的类型,看是请求转发、重定向、普通请求的哪一种,不同请求类型会有不同的创建 View 的方法。
protected View createView(String viewName, Locale locale) throws Exception {
//UrlBasedViewResolver 是否可以处理
if (!this.canHandle(viewName, locale)) {
return null;
} else {
//转发 url
String forwardUrl;
//视图名称是否是否 redirect: 打头
if (viewName.startsWith("redirect:")) {
//截取掉 redirect: 获取真正的 url
forwardUrl = viewName.substring("redirect:".length());
//创建一个重定向视图
RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
//获取重定向 host
String[] hosts = this.getRedirectHosts();
//为 null 判断
if (hosts != null) {
//给视图设置 view
view.setHosts(hosts);
}
//应用给生命周期方法 重点关注
return this.applyLifecycleMethods("redirect:", view);
} else if (viewName.startsWith("forward:")) {
//请求转发 获取转发 url
forwardUrl = viewName.substring("forward:".length());
//创建 InternalResourceView
InternalResourceView view = new InternalResourceView(forwardUrl);
//应用给生命周期方法 其实就是使用 ApplicationContext 完成 view 的初始化 重点关注
return this.applyLifecycleMethods("forward:", view);
} else {
//不是 redirect 也不是 forward 就是普通视图 这里会调用 重点关注
return super.createView(viewName, locale);
}
}
}
UrlBasedViewResolver#applyLifecycleMethods 方法源码分析
UrlBasedViewResolver#applyLifecycleMethods 应用给生命周期方法,其实就是使用 ApplicationContext 完成 View 的初始化 。
//org.springframework.web.servlet.view.UrlBasedViewResolver#applyLifecycleMethods
protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
//获取 ApplicationContext
ApplicationContext context = this.getApplicationContext();
//为空 判断
if (context != null) {
//获取 beanFactory 完成 view 初始化
Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
//初始化完了 是否是 view 类型
if (initialized instanceof View) {
//返回 view
return (View)initialized;
}
}
return view;
}
AbstractCachingViewResolver#createView 方法源码分析
AbstractCachingViewResolver#createView 方法是普通请求创建 View 的方法,它调用的是父类 AbstractCachingViewResolver 的 createView 方法 ,该方法并没有什么实际操作,接着调用了 loadView 方法,loadView 方法完成了 View 的创建及初始化、属性见擦汗等。
//org.springframework.web.servlet.view.AbstractCachingViewResolver#createView
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {
return this.loadView(viewName, locale);
}
//org.springframework.web.servlet.view.UrlBasedViewResolver#loadView
protected View loadView(String viewName, Locale locale) throws Exception {
//根据viewName 构建view 重点关注
AbstractUrlBasedView view = this.buildView(viewName);
//完成view 初始化
View result = this.applyLifecycleMethods(viewName, view);
//view.checkResource(locale) 检查view属性 默认返回 true
return view.checkResource(locale) ? result : null;
}
AbstractCachingViewResolver#buildView 方法源码分析
AbstractCachingViewResolver#buildView 方法获取 View 的 Class 类型,反射创建了 View,并给 View 设置了各种属性,是正真创建 View 的方法。
//org.springframework.web.servlet.view.UrlBasedViewResolver#buildView
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
//获取view 的class
Class<?> viewClass = this.getViewClass();
//断言判断 class 是否为 null
Assert.state(viewClass != null, "No view class");
//根据 view 的 class 创建一个 view
AbstractUrlBasedView view = (AbstractUrlBasedView)BeanUtils.instantiateClass(viewClass);
//根据前缀+名称+后缀 拼接成视图信息
view.setUrl(this.getPrefix() + viewName + this.getSuffix());
//view 设置 Attribute
view.setAttributesMap(this.getAttributesMap());
//获取 contentType
String contentType = this.getContentType();
if (contentType != null) {
//view 设置 contentType
view.setContentType(contentType);
}
//获取 RequestContextAttribute
String requestContextAttribute = this.getRequestContextAttribute();
if (requestContextAttribute != null) {
//设置 RequestContextAttribute
view.setRequestContextAttribute(requestContextAttribute);
}
//是否暴露 PathVariables Request请求中的 url 属性
Boolean exposePathVariables = this.getExposePathVariables();
if (exposePathVariables != null) {
//是否将这些属性暴露在视图中
view.setExposePathVariables(exposePathVariables);
}
//是否将 bean 暴露在视图中
Boolean exposeContextBeansAsAttributes = this.getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
//设置到视图中
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
//需要暴露的 bean name
String[] exposedContextBeanNames = this.getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
//设置到视图中
view.setExposedContextBeanNames(exposedContextBeanNames);
}
//返回 视图
return view;
}
AbstractView#render 方法源码分析
上文说了 DispatcherServlet#render 方法主要可以分为两步,分别是创建视图 View 和 解析渲染视图 view.render,创建 View 的步骤我们已经分析完了,我们来分析一下 view.render 方法,也就是 AbstractView#render 方法,该方法会将 Model、request、response 封装成成一个 Map 对象给后面的视图渲染使用,同时会对下载请求做一些处理,然后就开始进行视图渲染。
//org.springframework.web.servlet.view.AbstractView#render
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (this.logger.isDebugEnabled()) {
this.logger.debug("View " + this.formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
//合并成一个 Map 对象
Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
//响应前处理 其实就是判断这是否是一个下载请求 默认不是下载请求
this.prepareResponse(request, response);
//渲染合并数据模型 重点关注
this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}
InternalResourceView#renderMergedOutputModel 方法源码分析
InternalResourceView#renderMergedOutputModel 方法就是进行视图渲染了,对于不同的请求类型有不同的渲染方式。
//org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//将模型的数据全部写去到 request 中 就是一个 request.setAttribute 的过程
this.exposeModelAsRequestAttributes(model, request);
//暴露一个助手 默认空实现 可以理解为一个扩展点
this.exposeHelpers(request);
//确定请求的路径
String dispatcherPath = this.prepareForRendering(request, response);
//获取可以用于 include、forward 的 RequestDispatcher
RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
//为 null 判断
if (rd == null) {
//为 null 抛出异常
throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");
} else {
//判断当前是否为include请求
//include方法使原先的 Servlet 和转发到的 Servlet 都可以输出响应信息 即原先的 Servlet 还可以继续输出响应信息
if (this.useInclude(request, response)) {
//是 include 请求
//设置 ContentType
response.setContentType(this.getContentType());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Including [" + this.getUrl() + "]");
}
//include 包含的意思
// 调用 include()方法进行文件引入
rd.include(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Forwarding to [" + this.getUrl() + "]");
}
//请求转发 直接使用 forward 请求将当前请求转发到目标文件路径中 渲染该视图
rd.forward(request, response);
}
}
}
至此,视图渲染部分的核心流程已经分析完毕,其实整个流程就两个要点,一个是通过视图解析器去创建 View,一个就是 View 的渲染过程,视图渲染分析完毕,也代表着整个 Spring MVC 的工作流程分析完毕,源码告诉了我们 Spirng MVC 是怎样去处理一个请求的,希望可以帮助到有需要的小伙伴。
欢迎提出建议及对错误的地方指出纠正。