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/>标签

推荐阅读更多精彩内容

  • 文/晨风 歌唱盛夏的不仅仅是蝉 还有夕阳的晚霞时踏着回家的路 拖着疲惫的身体 父亲的形象 已不如往日那般高大 一组...
    Aurora_Lcx阅读 182评论 1 4
  • 话说,鱼玄机在被斩头时只说过一句话:"我幼薇一生风流,单单只爱过一人那就是温庭筠。"当时温庭筠也在法场。 有很多人...
    棠语阅读 172评论 0 0
  • 百年孤独我百年的寂寞 大草原上你忧郁的心结 是谁抛弃了你纯净的心灵 把一个个包裹压给了沙驼 草原羊羔洁白的云朵 一...
    垄上行云阅读 176评论 4 5
  • 张红云 焦点讲师二期班 平顶山 坚持分享第88天( 原创总第256天 2017年8月18日 星期五) 早...
    红云_杨柳清风阅读 168评论 0 0