title: SpringMVC源码分析6:SpringMVC的视图解析原理
date: 2018-06-07 11:03:19
tags:html
- SpringMVC
categories:java
- 后端 - SpringMVC
转自 [SpringMVC视图机制详解[附带源码分析]](https://www.cnblogs.com/fangj...git
本系列文章首发于个人我的博客:https://h2pl.github.io/github
欢迎阅览个人CSDN专栏:Spring源码解析 https://blog.csdn.net/column/...web
部分代码会放在个人的Github:https://github.com/h2pl/
<!-- more -->spring
SpringMVC是目前主流的Web MVC框架之一。 segmentfault
若是有同窗对它不熟悉,那么请参考它的入门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处理视图名的时候,遇到 "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 中的的内容是输出<h2>This is jsp page</h2>
视图 /WEB-INF/view/trvc/index.ftl 中的的内容是输出<h2>This is freemarker page</h2>
本文分析了SpringMVC中的视图机制,View和ViewResolver这两个接口是视图机制的核心,并分析了几个重要的View和ViewResolver接口实现类,最终写了一个区别jsp和freemarker视图的ViewResolver实现类,让读者更加理解视图机制。
但愿这篇文章能帮助读者了解SpringMVC视图机制。
文中不免有错误,但愿读者可以指明出来。