SpringMVC工作原理之视图解析及自定义

本篇笔记记录分为两大部分:
第一部分主要记录SpringMVC如何解析、渲染视图并转发返回结果对象,主要是针对源码执行过程的追踪。
第二部分记录一个SpringMVC自定义视图步骤及过程。
本篇笔记主要分析SpringMVC 5.1.1 这个版本。

SpringMVC运行流程

一、Spring MVC 视图解析过程

1. ModelAndView

SpringMVC 内部最终会将返回的参数及视图名字封装成一个 ModelAndView 对象,这个对象包含两个部分:Model 是一个 HashMap 集合,View 一般则是一个 String 类型记录要跳转视图的名字或者是视图对象(当然如果是视图对象的话则直接跳过视图解析器的解析过程了)。

源码内部最终会根据执行 Controller 里面的方法生成的 ModelAndViewContainer 对象创建 ModelAndView 对象。
SpringMVC 内部最终是借助这个 ModelAndView 对象里面的 View 来选取视图解析器,解析出视图,然后将 Model 里面的键值写进 requestScope 里面,最终呈现给客户端渲染后的视图,不懂这的没关系,咱们接着往下看。

2. View & ViewResolver

在开始源码分析之前,我们先来看下两个基本概念,视图和视图解析器。

2.1 视图 View

视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户,其实就是 html、jsp 甚至 word、excel 文件;
为了实现视图模型和具体实现技术的解耦,SpringMVC 定义了一个高度抽象的 View 接口 org.springframework.web.servlet.View
视图对象由视图解析器负责实例化,由于他们是无状态的,所以不存在线程安全的问题。

下面来看下 View 接口实现类都有哪些

顺带说下 IDEA 查看接口实现类的方法

view2.png

我们挑几个常用的了解下

视图 说明
InternalResourceView 将 JSP 或其他资源封装成一个视图,一般 JSP 页面用该视图类
JstlView 继承自InternalResourceView,如果 JSP 页面使用了 JSTL 标签,则需要使用该视图类
AbstractPdfView PDF视图的抽象超类
AbstractXlsView 传统XLS格式的Excel文档视图的便捷超类,与Apache POI 3.5及更高版本兼容。
AbstractXlsxView Office 2007 XLSX格式的Excel文档视图的便捷超类,兼容Apache POI 3.5及更高版本。
MappingJackson2JsonView 将模型数据 通过 Jackson 开源框架的 ObjectMapper 以 JSON 方式输出
2.2 视图解析器 ViewResolver

SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring Web 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。

  • 每一种映射策略对应一个具体的视图解析器实现类。
  • 视图解析器的作用是将逻辑视图解析为一个具体的物理视图对象。
  • 所有的视图解析器都必须实现 ViewResolver 接口。
  • 可以选择一种或多种视图解析器,可以通过其 order 属性指定解析器的优先顺序,order 越小优先级越高。
  • SpringMVC 会按照视图解析器顺序的优先次序进行解析,直到返回视图对象。若无,则抛出 ServletException 异常。

下面来看下实现 ViewResolver 接口的类都有哪些

viewResolver.png

我们挑几个常用的了解下

视图解析器 说明
AbstractCachingViewResolver 一个抽象视图,继承该类可以让视图解析器具有缓存功能
XmlViewResolver 接受XML文件的视图解析器,默认配置文件在 /WEB-INF/views.xml
ResourceBundleViewResolver 使用properties配置文件的视图解析器,默认配置文件是类路径下的views.properties
UrlBasedViewResolver 一个简单的视图解析器,不做任何匹配,需要视图名和实际视图文件名相同
InternalResourceViewResolver UrlBasedViewResolver的一个子类,支持Servlet容器的内部类型(JSP、Servlet、以及JSTL等),可以使用setViewClass(..)指定具体的视图类型
FreeMarkerViewResolver 也是UrlBasedViewResolver的子类,用于FreeMarker视图技术
ContentNegotiatingViewResolver 用于解析基于请求文件名或Accept header的视图
BeanNameViewResolver 将逻辑视图名解析为一个 Bean,Bean 的 id 等于逻辑视图名

3. 视图解析过程源码分析

  1. 首先进入 DispatcherServlet.doDispatch( ) 方法,经过解析处理,找到了对应的 Controller 里面的方法,执行完成之后得到 ModeAndView 对象,开始视图渲染前后一些列工作
  1. 进入渲染方法,开始视图渲染前的工作
  1. 进入 render(..) 方法查看渲染源码

3.1. 查看下 View 对象创建过程,进入 resolveViewName(..) 方法

  1. 拿到 View 对象后开始视图上的渲染工作,执行 view.render(..) 方法,查看视图渲染的具体流程
  1. 进入 renderMergedOutputModel(..) 方法

5.1 顺带看下 Model数据写进 requestScope 过程,进入 exposeModelAsRequestAttributes(..) 方法

二. Spring MVC 自定义视图

表格导出在平时开发中经常用到,今天就记录一个导出数据成 Excel 表格形式的例子,下面我们按步骤开始做。

1. 首先导入 apache的 poi 的支持 jar 包

<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
  <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.17</version>
  </dependency>

  <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
  <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.17</version>
  </dependency>

poi:提供 microsoft office 旧版本支持 [eg .xls Excel]
poi-ooxml:提供 microsoft office 新版本支持 [eg .xlsx Excel]

2. 创建自定义视图类

创建自定义表格视图类需要继承自 AbstractXlsxView 表格视图抽象类,实现 buildExcelDocument(..) 方法,在该方法里面实现视图处理操作。

public class ExcelView extends AbstractXlsxView {

    @Override
    protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {

        String filename = "students.xlsx";
        response.setContentType("application/ms-excel;charset=UTF-8");
        response.setHeader("Content-Disposition", "inline; filename=" + filename);

        //根据工作簿创建Excel表
        Sheet sheet = workbook.createSheet("sheet1");

        Row row = sheet.createRow(0);
        row.createCell(0).setCellValue("id");
        row.createCell(1).setCellValue("姓名");
        row.createCell(2).setCellValue("年龄");
        row.createCell(3).setCellValue("生日");

        if (model == null) return;
        List<Student> list = (List<Student>) model.get("list");

        for (int i = 0; i < list.size(); i++) {
            Student student = list.get(i);

            Row tempRow = sheet.createRow(i+1);
            tempRow.createCell(0).setCellValue(student.getId());
            tempRow.createCell(1).setCellValue(student.getName());
            tempRow.createCell(2).setCellValue(student.getAge());
            tempRow.createCell(3).setCellValue(student.getBirthday());
        }

        OutputStream outputStream = response.getOutputStream();
        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();
    }
}
3. 编写Controller类里的方法

可以从数据库取数据,这里为了简单,这里模拟的假数据装进 Model 里面

@Controller
public class ExcelController {

    @GetMapping(value = "downloadList")
    public ModelAndView downloadStudentList() {
        System.out.println("准备下载学生列表");
        Student s1 = new Student(1, "Tom", 13, new Date());
        Student s2 = new Student(2, "Jerry", 14, new Date());
        Student s3 = new Student(3, "阿凡提", 20, new Date());
        Student s4 = new Student(4, "麦麦提", 24, new Date());
        ArrayList<Student> list = new ArrayList<>();
        list.add(s1); list.add(s2); list.add(s3); list.add(s4);

        ModelAndView mv = new ModelAndView();
        mv.addObject("list", list);
        View view = new ExcelView();
        mv.setView(view); //注意: 这里是将实例化的自定义视图对象当做参数传进入, 而不是视图名字
        return mv;
    }
}

最终请求到该方法里面便可完成下载。

注意这里 ModelAndView 模型里面的 view 属性承装的是自定义视图实体,而不是自定义视图的名字,如果直接写成返回视图名字的话需要注入 BeanNameViewResolver 视图解析器。

如果想写成视图名字返回的话需要如下配置

  1. Controller 里面更改如下
@RequestMapping("ec")
@Controller
public class ExcelController {

    @GetMapping(value = "downloadList")
    public String downloadStudentList(Model model) {
        System.out.println("准备下载学生列表");
        Student s1 = new Student(1, "Tom", 13, new Date());
        Student s2 = new Student(2, "Jerry", 14, new Date());
        Student s3 = new Student(3, "阿凡提", 20, new Date());
        Student s4 = new Student(4, "麦麦提", 24, new Date());
        ArrayList<Student> list = new ArrayList<>();
        list.add(s1); list.add(s2); list.add(s3); list.add(s4);

        model.addAttribute("list", list);
        return "excelView";
    }
}
  1. 在 Spring MVC 配置文件中添加 BeanNameViewResolver 视图解析器
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
    <property name="order" value="10"></property>
</bean>
  1. 在自定义视图类上加上 Component,把视图类的对象放Spring容器里。
@Component
public class ExcelView extends AbstractXlsxView {
  ...
}

其他相关文章

SpringMVC入门笔记
SpringMVC工作原理之处理映射[HandlerMapping]
SpringMVC工作原理之适配器[HandlerAdapter]
SpringMVC工作原理之参数解析
SpringMVC之自定义参数解析
SpringMVC工作原理之视图解析及自定义
SpingMVC之<mvc:annotation-driven/>标签

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容