SpringMVC源码分析6:SpringMVC的视图解析原理
title: SpringMVC源码分析6:SpringMVC的视图解析原理
date: 2018-06-07 11:03:19
tags:
- SpringMVC
categories:
- 后端
- SpringMVC
本系列文章首发于我的个人博客:https://h2pl.github.io/
欢迎阅览我的CSDN专栏:Spring源码解析 https://blog.csdn.net/column/details/21851.html
部分代码会放在我的的Github:https://github.com/h2pl/
目录
前言
SpringMVC是目前主流的Web MVC框架之一。
如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html
本文将分析SpringMVC的视图这部分内容,让读者了解SpringMVC视图的设计原理。
重要接口和类介绍
1. View接口
视图基础接口,它的各种实现类是无状态的,因此是线程安全的。 该接口定义了两个方法:

2. AbstractView抽象类
View接口的基础实现类。我们稍微介绍一下这个抽象类。
首先看下这个类的属性:

再看下抽象类中接口方法的实现:
getContentType方法直接返回contentType属性即可。

render方法:

3. AbstractUrlBasedView抽象类
继承自AbstractView抽象类,增加了1个类型为String的url参数。
4. InternalResourceView类
继承自AbstractUrlBasedView抽象类的类,表示JSP视图。
我们看下这个类的renderMergedOutputModel方法(AbstractView抽象类定义的抽象方法,为View接口提供的render方法服务)。

5. JstlView类
JSTL视图,继承自InternalResourceView,该类大致上与InternalResourceView类一致。
6. AbstractTemplateView抽象类
继承自AbstractUrlBasedView抽象类,重写了renderMergedOutputModel方法,在该方法中会调用renderMergedTemplateModel方法,renderMergedTemplateModel方法为新定义的抽象方法。
该抽象类有几个boolean属性exposeSessionAttributes,exposeRequestAttributes。 设置为true的话会将request和session中的键值和值丢入到renderMergedTemplateModel方法中的model这个Map参数中。
这个类是某些模板引擎视图类的父类。 比如FreemarkerView,VelocityView。
7. FreeMarkerView类
继承自AbstractTemplateView抽象类。
直接看renderMergedTemplateModel方法,renderMergedTemplateModel内部会调用doRender方法:

8. RedirectView类
继承自AbstractUrlBasedView,并实现SmartView接口。SmartView接口定义了1个boolean isRedirectView();方法。
该视图的renderMergedOutputModel方法主要就是通过response.sendRedirect进行重定向。
有关RedirectView方面的知识楼主另外写了1篇博客。http://www.cnblogs.com/fangjian0423/p/springMVC-redirectView-analysis.html
9. ViewResolver接口
视图解释器,用来解析视图View,与View接口配合使用。
该接口只有1个方法,通过视图名称viewName和Locale对象得到View接口实现类:
View resolveViewName(String viewName, Locale locale) throws Exception;
10. AbstractCachingViewResolver抽象类
带有缓存功能的ViewResolver接口基础实现抽象类,该类有个属性名为viewAccessCache的以 "viewName_locale" 为key, View接口为value的Map。
该抽象类实现的resolveViewName方法内部会调用createView方法,方法内部会调用loadView抽象方法。
11. UrlBasedViewResolver类
继承自AbstractCachingViewResolver抽象类、并实现Ordered接口的类,是ViewResolver接口简单的实现类。

该类复写了createView方法:

父类(AbstractCachingViewResolver)的createView方法内部会调用loadView抽象方法,UrlBasedViewResolver实现了这个抽象方法:


下面对UrlBasedViewResolver做1个test,配置如下:
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
<property name="viewNames">
<value type="java.lang.String">*</value>
</property>
<property name="contentType" value="text/html;charset=utf-8"/>
<property name="attributesMap">
<map>
<entry key="mytest" value="mytestvalue"/>
</map>
</property>
<property name="attributes">
<props>
<prop key="test">testvalue</prop>
</props>
</property>
</bean>
我们看到:以InternalResourceView这个JSP视图作为视图;viewNames我们设置了,这里的代表全部视图名(这个viewNames属性不设置也可以,代表全部视图名都处理);http响应头部contentType信息:text/html;charset=utf-8;attributesMap和attributes传入的Map和Properties参数都会被丢入到staticAttributes属性中,这个staticAttributes会被设置成AbstractView的staticAttributes属性,也就是request域中的参数。



我们看到request域中没有设置mytest和testvalue值。但是页面中会显示,因为我们配置了attributesMap和attributes参数。
如果我们把viewNames中的"*"改成"index1"。那么就报错了,因为处理视图名的时候index匹配不上index1。

12. InternalResourceViewResolver类
继承自UrlBasedViewResolver,以InternalResourceView作为视图,若项目中存在“javax.servlet.jsp.jstl.core.Config”该类,那么会以JstlView作为视图。重写了buildView方法,主要就是为了给InternalResourceView视图设置属性。
13. AbstractTemplateViewResolver类
继承自UrlBasedViewResolver,重写了buildView方法,主要就是构造AbstractTemplateView以及为它设置相应的属性。
14. FreeMarkerViewResolver类
继承自AbstractTemplateViewResolver,将视图设置为FreeMarkerView。
15. ModelAndView对象
顾名思义,带有视图和Model属性的一个模型和视图类。

值得注意的是,这个视图属性是一个Object类型的数据,可以直接是View接口的实现类或者视图名(字符串)。
源码分析
下面我们来分析SpringMVC处理视图的源码。
SpringMVC在处理请求的时候,通过RequestMappingHandlerMapping得到HandlerExecutionChain,然后通过RequestMappingHandlerAdapter得到1个ModelAndView对象,之后通过processDispatchResult方法处理。
processDispatchResult方法如下:


如果配置的ViewResolver如下:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
那么就是使用InternalResourceViewResolver来解析视图。
之前分析过,InternalResourceViewResolver重写了UrlBasedViewResolver的buildView方法。但是还是会调用UrlBasedViewResolver的buildView方法。

最终得到InternalResourceView或JstlView视图。这两个视图的render方法本文介绍重要接口及类的时候已分析。
PS:DispathcerServlet中的viewResolvers属性是个集合,如果存在多个ViewResolver对象,必定会有优先级的问题,这部分的内容请参考楼主的另一篇博客:
http://www.cnblogs.com/fangjian0423/p/spring-Ordered-interface.html
编码自定义的ViewResolver
下面,我们就来编写自定义的ViewResolver。
自定义的ViewResolver处理视图名的时候,遇到 "jsp:" 开头的会找jsp页面,遇到 "freemarker:" 开头的找freemarker页面。
public class CustomViewResolver extends UrlBasedViewResolver {
public static final String JSP_URL_PREFIX = "jsp:";
public static final String FTL_URL_PREFIX = "freemarker:";
private static final boolean jstlPresent = ClassUtils.isPresent(
"javax.servlet.jsp.jstl.core.Config", CustomViewResolver.class.getClassLoader());
private Boolean exposePathVariables = false;
private boolean exposeRequestAttributes = false;
private boolean allowRequestOverride = false;
private boolean exposeSessionAttributes = false;
private boolean allowSessionOverride = false;
private boolean exposeSpringMacroHelpers = true;
public CustomViewResolver() {
this.setViewClass(FreeMarkerView.class);
}
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
if(viewName.startsWith(FTL_URL_PREFIX)) {
return buildFreemarkerView(viewName.substring(FTL_URL_PREFIX.length()));
} else if(viewName.startsWith(JSP_URL_PREFIX)) {
Class viewCls = jstlPresent ? JstlView.class : InternalResourceView.class;
return buildView(viewCls, viewName.substring(JSP_URL_PREFIX.length()), getPrefix(), ".jsp");
} else {
//默认以freemarker处理
return buildFreemarkerView(viewName);
}
}
private AbstractUrlBasedView build(Class viewClass, String viewName, String prefix, String suffix) {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
view.setUrl(prefix + viewName + suffix);
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
if (this.exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}
private AbstractUrlBasedView buildFreemarkerView(String viewName) throws Exception {
AbstractTemplateView view = (AbstractTemplateView) build(FreeMarkerView.class, viewName, "", getSuffix());
view.setExposeRequestAttributes(this.exposeRequestAttributes);
view.setAllowRequestOverride(this.allowRequestOverride);
view.setExposeSessionAttributes(this.exposeSessionAttributes);
view.setAllowSessionOverride(this.allowSessionOverride);
view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
return view;
}
//get set方法省略
}
xml配置:
<bean class="org.format.demo.support.viewResolver.CustomViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".ftl"/>
<property name="contentType" value="text/html;charset=utf-8"/>
<property name="exposeRequestAttributes" value="true"/>
<property name="exposeSessionAttributes" value="true"/>
<property name="exposeSpringMacroHelpers" value="true"/>
<property name="requestContextAttribute" value="request"/>
</bean>
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/view/"/>
<property name="defaultEncoding" value="utf-8"/>
<property name="freemarkerSettings">
<props>
<prop key="template_update_delay">10</prop>
<prop key="locale">zh_CN</prop>
<prop key="datetime_format">yyyy-MM-dd</prop>
<prop key="date_format">yyyy-MM-dd</prop>
<prop key="number_format">#.##</prop>
</props>
</property>
</bean>
简单解释一下:CustomViewResolver解析视图名的时候,判断 "jsp:" 和 "freemarker:" 开头的名字,如果是 "jsp:" 开头的,如果有JSTL依赖,构造JSTLView视图,否则构造InternalResourceView视图。如果是 "freemarker:" 构造FreemarkerView。在构造视图之前分别会设置一些属性。
xml配置:配置prefix是为了给jsp视图用的,freemarker视图不需要prefix,因为FreemarkerView内部会使用配置的FreeMarkerConfigurer,并用FreeMarkerConfigurer内部的templateLoaderPath属性作为前缀,配置的suffix是为了让FreemarkerView使用,当后缀。
最后附上Controller代码:
@Controller
@RequestMapping(value = "/tvrc")
public class TestViewResolverController {
@RequestMapping("jsp")
public ModelAndView jsp(ModelAndView view) {
view.setViewName("jsp:trvc/index");
return view;
}
@RequestMapping("/ftl")
public ModelAndView freemarker(ModelAndView view) {
view.setViewName("freemarker:trvc/index");
return view;
}
}
视图 /WEB-INF/view/trvc/index.jsp 中的的内容是输出
This is jsp page
视图 /WEB-INF/view/trvc/index.ftl 中的的内容是输出
This is freemarker page


总结
本文分析了SpringMVC中的视图机制,View和ViewResolver这两个接口是视图机制的核心,并分析了几个重要的View和ViewResolver接口实现类,最终写了一个区别jsp和freemarker视图的ViewResolver实现类,让读者更加理解视图机制。
希望这篇文章能帮助读者了解SpringMVC视图机制。
文中难免有错误,希望读者能够指明出来。

SpringMVC源码分析6:SpringMVC的视图解析原理的更多相关文章
- springMVC源码分析--RequestToViewNameTranslator请求到视图名称的转换
RequestToViewNameTranslator可以在处理器返回的View为空时使用它根据Request获取viewName.RequestToViewNameTranslator提供的实现类只 ...
- 数据传递--------博客-----------springMVC源码分析--RequestToViewNameTranslator请求到视图名称的转换
参考来源:http://blog.csdn.net/qq924862077/article/details/54286976?utm_source=gold_browser_extension Req ...
- springMVC源码分析--视图AbstractView和InternalResourceView(二)
上一篇博客springMVC源码分析--视图View(一)中我们介绍了简单介绍了View的结构实现及运行流程,接下来我们介绍一下View的实现类做的处理操作. AbstractView实现了rende ...
- springMVC源码分析--视图View(一)
之前的博客springMVC源码分析--HttpMessageConverter数据转化(一)中我们已经介绍了数据返回值的处理,在博客springMVC源码分析--ViewResolver视图解析器( ...
- 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解
从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...
- springMVC源码分析--ViewNameMethodReturnValueHandler返回值处理器(三)
之前两篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)和springMVC源码分析--HandlerMethodReturnValu ...
- springMVC源码分析--RequestMappingHandlerAdapter(五)
上一篇博客springMVC源码分析--HandlerAdapter(一)中我们主要介绍了一下HandlerAdapter接口相关的内容,实现类及其在DispatcherServlet中执行的顺序,接 ...
- springMVC源码分析--HandlerInterceptor拦截器(一)
对SpringMVC有所了解的人肯定接触过HandlerInterceptor拦截器,HandlerInterceptor接口给我们提供了3个方法: (1)preHandle: 在执行controll ...
- SpringMVC源码分析--容器初始化(五)DispatcherServlet
上一篇博客SpringMVC源码分析--容器初始化(四)FrameworkServlet我们已经了解到了SpringMVC容器的初始化,SpringMVC对容器初始化后会进行一系列的其他属性的初始化操 ...
随机推荐
- Oracle 学习笔记二
一.oracle通用函数vnl(a,b) 用于任何类型,如果a的值不为null返回a的值否则返回b的值 条件判断oracle中可以使用 case 字段 when 条件1 then 表达式1 when ...
- 个人永久性免费-Excel催化剂功能第53波-无比期待的合并工作薄功能
合并工作薄.工作表功能,几乎每一款Excel插件都提供,而且系列衍生功能甚至有多达10多个.今天Excel催化剂重拾武器,在现有众多插件没提供到位的部分场景中,给予支持和补充,做到人有我优,人无我有的 ...
- [leetcode] 120. Triangle (Medium)
原题 思路: dp,从下往上依次取得最小的,取到最上面的,就是一条最小的路径. class Solution { public: int minimumTotal(vector<vector&l ...
- kubernetes的volume的权限设置(属主和属组)
apiVersion: v1kind: Podmetadata: name: hello-worldspec: containers: # specification of the pod's c ...
- 深挖Openstack Nova - Scheduler调度策略
深挖Openstack Nova - Scheduler调度策略 一. Scheduler的作用就是在创建实例(instance)时,为实例选择出合适的主机(host).这个过程分两步:过滤(F ...
- springboot - 登录+静态资源访问+国际化
1.项目目录结构 2.pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmln ...
- 使用钉钉对接禅道的bug系统,实现禅道提的bug实时在钉钉提醒并艾特对应的开发人员处理
现在公司测试中有一个痛点是每次测试人员提完bug后,需要定期去提醒开发人员查看禅道的bug记录及修复bug. 导致测试人员在项目测试中不仅要测试整个软件,还要负起实时监督提醒功能的“保姆角色”,身心疲 ...
- jenkins下使用python虚拟环境
jenkins下使用python虚拟环境碰到的一些坑: 1. 构建使用window批处理 - 坑1 c: cd c:\xxxxx\xxxxx\scripts activate c: cd c:\xxx ...
- 华为matebook14vm虚拟机错误
1.创建时显示不支持64位虚拟机 测试环境: 华为matebook14 window10 专业工作站版 1903 问题描述: 创建虚拟机时显示:此主机不支持64位解决方案 问题参考: 参考1 ...
- TCP重置报文段及RST常见场景分析
RST表示连接重置,用于关闭那些已经没有必要继续存在的连接.一般情况下表示异常关闭连接,区别与四次分手正常关闭连接. 产生RST的三个条件是: 目的地为某端口的SYN到达,然而在该端口上并没有正在监听 ...