SpringMVC源码阅读:视图解析器
1.前言
SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧
本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何完成视图解析的
2.源码分析
在SpringMVC源码阅读:拦截器分析过doDispatch的运行过程,这里再分析一遍
回到DispatcherServlet类的doDispatch方法,看看doDispatch如何获取ModelAndView
HandlerMapping根据request获得HandlerExecutionChain
根据HandlerExecutionChain获取HandlerAdapter
HandlerAdapter根据request,response和HandlerExecutionChain调用handle方法返回ModelAndView
然后交由processDispatchResult处理

1023行检测从doDispatch方法运行到此是否有异常
1037行根据ModelAndView渲染视图

1259根据视图名称解析成View对象
1282行调用AbstractView的render方法进行渲染
点进去,这里以解析ftl做测试

301行创建包含动态值和静态属性的Map
302为待渲染的响应做准备
303行调用子类实现方法渲染这个包含动态值和静态属性的Map
接下来会进入AbstractTemplateView的renderMergedOutputModel方法,AbstractTemplateView是AbstractView的子类
AbstractTemplateView的renderMergedOutputModel方法会调用内部renderMergedTemplateModel方法,该方法被子类FreemarkerView实现
打开FreemarkerView的renderMergedTemplateModel方法

进入doRender方法,该方法根据response渲染FreeMarker视图

275行exposeModelAsRequestAttributes方法在父类AbstractView被实现,该方法把Model设置到request中
277行创建Freemarker模板模型
284行处理、加工Freemarker模板给response
再看看FreeMarkerViewResolver做了什么,打开类接口继承图

构造函数设置了前缀和后缀,再看看AbstractTemplateViewResolver

104~108行给视图设置属性
我们发现103行super引用了父类,点开进入UrlBasedViewResolver的buildView方法

532行拼接前缀+视图名称+后缀,例如"employee/form.ftl",并设置url
534~553行判断属性是否为空,非空则设置到View中
再看createView方法

460行如果以"redirect:"开头,交给RedirectView处理
467行如果以"forward:"开头,交给InternalResourceView处理
Freemarker解析逻辑至此分析完毕
现在看JSP的解析流程
回到AbstractView类的render方法,刚刚我们分析过

304行此时renderMergedOutputModel方法被子类InternalResourceView实现

139行将Model设置到Request中
142将辅助性参数设置到Request中
145行获取视图文件路径
148行获取请求分发器
155到170行如果response已经提交,则把资源文件纳入到response中,否则调用forward方法转发
再看下InternalResourceViewResolver,该类辅助InternalResourceView,为其设置属性,其余的ViewResolver的实现类分析同Freemarker
3.自定义视图解析
3.1 自定义支持Freemarker和Jsp的视图解析器
自定义视图解析器,"jsp:"开头构造InternalResourceView解析jsp,以"freemarker:"开头则构造FreemarkerView解析ftl
这样做的好处是可以区分同名的jsp和ftl
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 build(viewCls, viewName.substring(JSP_URL_PREFIX.length()), getPrefix(), ".jsp");
} else {
//默认以freemarker处理
return buildFreemarkerView(viewName);
}
}
/**
* @Author 谷天乐
* @Description 使用UrlBasedViewResolver的buildView方法
* 因为CustomViewResolver重写了buildView,不再执行UrlBasedViewResolver的buildView方法
* @Date 2019/1/17 11:26
* @Param [viewClass, viewName, prefix, suffix]
* @return org.springframework.web.servlet.view.AbstractUrlBasedView
**/
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;
}
/**
* @Author 谷天乐
* @Description 使用AbstractTemplateViewResolver的buildView,为view设置属性
* Freemarker解析所需
*
* @Date 2019/2/7 10:22
* @Param [viewName]
* @return org.springframework.web.servlet.view.AbstractUrlBasedView
**/
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;
}
public boolean isExposeRequestAttributes() {
return exposeRequestAttributes;
}
public void setExposeRequestAttributes(boolean exposeRequestAttributes) {
this.exposeRequestAttributes = exposeRequestAttributes;
}
public boolean isExposeSessionAttributes() {
return exposeSessionAttributes;
}
public void setExposeSessionAttributes(boolean exposeSessionAttributes) {
this.exposeSessionAttributes = exposeSessionAttributes;
}
public boolean isExposeSpringMacroHelpers() {
return exposeSpringMacroHelpers;
}
public void setExposeSpringMacroHelpers(boolean exposeSpringMacroHelpers) {
this.exposeSpringMacroHelpers = exposeSpringMacroHelpers;
}
}
在dispatcher-servlet.xml加入自定义视图解析器
<bean class="org.format.demo.custom.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>
引入freemarker和jsp的配置,配置在前遇到同名情况具备更高的优先级,比如index.ftl和index.jsp会优先解析index.ftl
<!--遇到同名的ftl和jsp文件,配置在前的解析器优先级更高-->
<import resource="classpath:springConfig/viewConfig/freemarker.xml"/>
<import resource="classpath:springConfig/viewConfig/jsp.xml"/>
测试Controller
@Controller
@RequestMapping(value = "/tvrc")
public class TestViewResolverController { @RequestMapping("jsp")
public ModelAndView jsp(ModelAndView view) {
view.setViewName("jsp:tvrc/test");
return view;
} @RequestMapping("/ftl")
public ModelAndView freemarker(ModelAndView view) {
view.setViewName("freemarker:tvrc/test");
return view;
} }
浏览器输入http://localhost:8080/springmvcdemo/tvrc/jsp

浏览器输入http://localhost:8080/springmvcdemo/tvrc/ftl

3.2 自定义Pdf视图
测试Controller
@Controller
@RequestMapping("otherview")
public class OtherViewController {
@RequestMapping("/pdf")
public ModelAndView mypdf() {
ModelAndView mav = new ModelAndView();
//添加自定义视图
mav.setView(new MyPdfView());
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add(i+"");
}
mav.addObject("list", list);
return mav;
} @RequestMapping("/excel")
public ModelAndView myexcle() {
ModelAndView mav = new ModelAndView();
//添加视图
mav.setView(new MyExcelView());
List list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add(i+"");
}
mav.addObject("list", list);
return mav;
}
}
创建测试类继承AbstractPdfView,重写buildPdfDocument方法
public class MyPdfView extends AbstractPdfView {
@Override
protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,
HttpServletRequest request, HttpServletResponse response) throws Exception {
List list = (List) model.get("list");
for (int i = 0; i < list.size(); i++) {
//将数据加载到视图上
document.add(new Paragraph((String)(list.get(i))));
}
}
}
浏览器输入http://localhost:8080/springmvcdemo/otherview/pdf

3.3 自定义Excel视图
创建测试类继承AbstractExcelView,重写buildExcelDocument方法
public class MyExcelView extends AbstractExcelView {
@Override
protected void buildExcelDocument(Map<String, Object> model, HSSFWorkbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception {
HSSFSheet sheet;//工作簿的名字
HSSFCell cell;//单元格
sheet = workbook.createSheet("spring");
sheet.setDefaultColumnWidth(12);
List list = (List) model.get("list");
for (int i = 0; i < list.size(); i++) {
cell = getCell(sheet, 0, i);
setText(cell, (String) list.get(i));
}
}
}
浏览器输入http://localhost:8080/springmvcdemo/otherview/excel

4.总结
View作为视图接口,AbstractView的render方法创建待渲染的model,调用子类,如果待解析视图类型是jsp,则调用InternalResourceView,如果是ftl,则调用FreemarkerView,执行渲染
ViewResolver作为视图解析接口,主要为View提供支持,createView方法创建View,buildView根据路径构造View,并为View设置一系列属性
DispatcherServlet根据ModelAndView调用View实现类的render方法进行视图渲染,ViewResolver实现类起到辅助作用,为View设置属性(前缀、后缀等)
5.参考
https://docs.spring.io/spring/docs/current/javadoc-api/
https://github.com/spring-projects/spring-framework
文中难免有不足,欢迎指出
SpringMVC源码阅读:视图解析器的更多相关文章
- SpringMVC源码阅读:拦截器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读系列汇总
1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...
- SpringMVC源码阅读:过滤器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:异常解析器
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:核心分发器DispatcherServlet
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将介绍SpringMVC的核 ...
- SpringMVC源码阅读:Controller中参数解析
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:定位Controller
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码分析,弄清楚Spr ...
- SpringMVC源码阅读:属性编辑器、数据绑定
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
- SpringMVC源码阅读:Json,Xml自动转换
1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将通过源码(基于Spring ...
随机推荐
- 自适应XAML布局经验总结 (一)原则和页面结构设计
XAML布局回顾 Grid和StackPanel是核心布局,尤其以Grid最为重要. Grid是网格布局,XAML的设计者有可能参考了Html里的Table设计了Grid布局,但进行了改进.Html中 ...
- Win10系统下编译GEOS3.6.2
环境说明 1.Win10专业版.64位: 2.VS2012旗舰版: 3.cmake-3.9.0.64位: 资源准备 GEOS管网(http://trac.osgeo.org/geos)下载3.4.3版 ...
- Metronic-最优秀的基于Bootstrap的响应式网站模版
在所有我看到过的基于Bootstrap的网站模版中,Metronic是我认为最优秀的,其外观之友好.功能之全面让人惊叹.Metronic 是一个自适应的HTML模版,提供后台管理模版和前端内容网页模版 ...
- c#开发sqlite
教程:https://www.cnblogs.com/icebutterfly/p/7850689.html 下载sqlite-netFx40-setup-bundle-x64-2010-1.0.96 ...
- jzoj1407
首先設在整個數組內可以直接到達n+1的最左邊的點點為x 則x+1-n區間都可以一次或2次到達n+1 如果某一個點i可以一次到達n+1,則其i+a[i]大於等於n+1 否則可以先跳到i再跳到n+1,需要 ...
- Good Bye 2017 G. New Year and Original Order
G. New Year and Original Order time limit per test 2 seconds memory limit per test 256 megabytes inp ...
- Redis的Pub/Sub机制存在的问题以及解决方案
Redis的Pub/Sub机制使用非常简单的方式实现了观察者模式,但是在使用过程中我们发现,它仅仅是实现了发布订阅机制,但是很多的场景没有考虑到.例如一下的几种场景: 1.数据可靠性无法保证 一个re ...
- [JavaScript] js获取当前页面url网址信息
在WEB开发中,时常会用到javascript来获取当前页面的url网址信息,在这里是我的一些获取url信息的小总结. 下面我们举例一个URL,然后获得它的各个组成部分:http://i.cnblog ...
- Android之AppWidget
1.Widget设计步骤 需要修改三个XML,一个class: 1)第一个xml是布局XML文件(如:main.xml),是这个widget的.一般来说如果用这个部件显示时间,那就只在这个布局XML中 ...
- jvm(2)类的初始化(二)和实例化
深入理解Java对象的创建过程:类的初始化与实例化 对象实例化内存分析: 对内存分配情况分析最常见的示例便是对象实例化: Object obj = new Object(); 这段代码的执行会涉及ja ...