本篇筆記記錄分為兩大部分:
第一部分主要記錄SpringMVC如何解析、渲染視圖并轉(zhuǎn)發(fā)返回結(jié)果對(duì)象,主要是針對(duì)源碼執(zhí)行過(guò)程的追蹤。
第二部分記錄一個(gè)SpringMVC自定義視圖步驟及過(guò)程。
本篇筆記主要分析SpringMVC 5.1.1 這個(gè)版本。

一、Spring MVC 視圖解析過(guò)程
1. ModelAndView
SpringMVC 內(nèi)部最終會(huì)將返回的參數(shù)及視圖名字封裝成一個(gè) ModelAndView 對(duì)象,這個(gè)對(duì)象包含兩個(gè)部分:Model 是一個(gè) HashMap 集合,View 一般則是一個(gè) String 類型記錄要跳轉(zhuǎn)視圖的名字或者是視圖對(duì)象(當(dāng)然如果是視圖對(duì)象的話則直接跳過(guò)視圖解析器的解析過(guò)程了)。

源碼內(nèi)部最終會(huì)根據(jù)執(zhí)行 Controller 里面的方法生成的 ModelAndViewContainer 對(duì)象創(chuàng)建 ModelAndView 對(duì)象。
SpringMVC 內(nèi)部最終是借助這個(gè) ModelAndView 對(duì)象里面的 View 來(lái)選取視圖解析器,解析出視圖,然后將 Model 里面的鍵值寫(xiě)進(jìn) requestScope 里面,最終呈現(xiàn)給客戶端渲染后的視圖,不懂這的沒(méi)關(guān)系,咱們接著往下看。
2. View & ViewResolver
在開(kāi)始源碼分析之前,我們先來(lái)看下兩個(gè)基本概念,視圖和視圖解析器。
2.1 視圖 View
視圖的作用是渲染模型數(shù)據(jù),將模型里的數(shù)據(jù)以某種形式呈現(xiàn)給客戶,其實(shí)就是 html、jsp 甚至 word、excel 文件;
為了實(shí)現(xiàn)視圖模型和具體實(shí)現(xiàn)技術(shù)的解耦,SpringMVC 定義了一個(gè)高度抽象的 View 接口 org.springframework.web.servlet.View。
視圖對(duì)象由視圖解析器負(fù)責(zé)實(shí)例化,由于他們是無(wú)狀態(tài)的,所以不存在線程安全的問(wèn)題。
下面來(lái)看下 View 接口實(shí)現(xiàn)類都有哪些

順帶說(shuō)下 IDEA 查看接口實(shí)現(xiàn)類的方法

我們挑幾個(gè)常用的了解下
| 視圖 | 說(shuō)明 |
|---|---|
| InternalResourceView | 將 JSP 或其他資源封裝成一個(gè)視圖,一般 JSP 頁(yè)面用該視圖類 |
| JstlView | 繼承自InternalResourceView,如果 JSP 頁(yè)面使用了 JSTL 標(biāo)簽,則需要使用該視圖類 |
| AbstractPdfView | PDF視圖的抽象超類 |
| AbstractXlsView | 傳統(tǒng)XLS格式的Excel文檔視圖的便捷超類,與Apache POI 3.5及更高版本兼容。 |
| AbstractXlsxView | Office 2007 XLSX格式的Excel文檔視圖的便捷超類,兼容Apache POI 3.5及更高版本。 |
| MappingJackson2JsonView | 將模型數(shù)據(jù) 通過(guò) Jackson 開(kāi)源框架的 ObjectMapper 以 JSON 方式輸出 |
2.2 視圖解析器 ViewResolver
SpringMVC 為邏輯視圖名的解析提供了不同的策略,可以在 Spring Web 上下文中配置一種或多種解析策略,并指定他們之間的先后順序。
- 每一種映射策略對(duì)應(yīng)一個(gè)具體的視圖解析器實(shí)現(xiàn)類。
- 視圖解析器的作用是將邏輯視圖解析為一個(gè)具體的物理視圖對(duì)象。
- 所有的視圖解析器都必須實(shí)現(xiàn)
ViewResolver接口。 - 可以選擇一種或多種視圖解析器,可以通過(guò)其 order 屬性指定解析器的優(yōu)先順序,order 越小優(yōu)先級(jí)越高。
- SpringMVC 會(huì)按照視圖解析器順序的優(yōu)先次序進(jìn)行解析,直到返回視圖對(duì)象。若無(wú),則拋出
ServletException異常。
下面來(lái)看下實(shí)現(xiàn) ViewResolver 接口的類都有哪些

我們挑幾個(gè)常用的了解下
| 視圖解析器 | 說(shuō)明 |
|---|---|
| AbstractCachingViewResolver | 一個(gè)抽象視圖,繼承該類可以讓視圖解析器具有緩存功能 |
| XmlViewResolver | 接受XML文件的視圖解析器,默認(rèn)配置文件在 /WEB-INF/views.xml |
| ResourceBundleViewResolver | 使用properties配置文件的視圖解析器,默認(rèn)配置文件是類路徑下的views.properties |
| UrlBasedViewResolver | 一個(gè)簡(jiǎn)單的視圖解析器,不做任何匹配,需要視圖名和實(shí)際視圖文件名相同 |
| InternalResourceViewResolver | UrlBasedViewResolver的一個(gè)子類,支持Servlet容器的內(nèi)部類型(JSP、Servlet、以及JSTL等),可以使用setViewClass(..)指定具體的視圖類型 |
| FreeMarkerViewResolver | 也是UrlBasedViewResolver的子類,用于FreeMarker視圖技術(shù) |
| ContentNegotiatingViewResolver | 用于解析基于請(qǐng)求文件名或Accept header的視圖 |
| BeanNameViewResolver | 將邏輯視圖名解析為一個(gè) Bean,Bean 的 id 等于邏輯視圖名 |
3. 視圖解析過(guò)程源碼分析
- 首先進(jìn)入
DispatcherServlet.doDispatch( )方法,經(jīng)過(guò)解析處理,找到了對(duì)應(yīng)的Controller里面的方法,執(zhí)行完成之后得到ModeAndView對(duì)象,開(kāi)始視圖渲染前后一些列工作

- 進(jìn)入渲染方法,開(kāi)始視圖渲染前的工作

- 進(jìn)入
render(..)方法查看渲染源碼

3.1. 查看下 View 對(duì)象創(chuàng)建過(guò)程,進(jìn)入 resolveViewName(..) 方法

- 拿到
View對(duì)象后開(kāi)始視圖上的渲染工作,執(zhí)行view.render(..)方法,查看視圖渲染的具體流程

- 進(jìn)入
renderMergedOutputModel(..)方法

5.1 順帶看下 Model數(shù)據(jù)寫(xiě)進(jìn) requestScope 過(guò)程,進(jìn)入 exposeModelAsRequestAttributes(..) 方法

二. Spring MVC 自定義視圖
表格導(dǎo)出在平時(shí)開(kāi)發(fā)中經(jīng)常用到,今天就記錄一個(gè)導(dǎo)出數(shù)據(jù)成 Excel 表格形式的例子,下面我們按步驟開(kāi)始做。
1. 首先導(dǎo)入 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. 創(chuàng)建自定義視圖類
創(chuàng)建自定義表格視圖類需要繼承自 AbstractXlsxView 表格視圖抽象類,實(shí)現(xiàn) buildExcelDocument(..) 方法,在該方法里面實(shí)現(xiàn)視圖處理操作。
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);
//根據(jù)工作簿創(chuàng)建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. 編寫(xiě)Controller類里的方法
可以從數(shù)據(jù)庫(kù)取數(shù)據(jù),這里為了簡(jiǎn)單,這里模擬的假數(shù)據(jù)裝進(jìn) Model 里面
@Controller
public class ExcelController {
@GetMapping(value = "downloadList")
public ModelAndView downloadStudentList() {
System.out.println("準(zhǔn)備下載學(xué)生列表");
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); //注意: 這里是將實(shí)例化的自定義視圖對(duì)象當(dāng)做參數(shù)傳進(jìn)入, 而不是視圖名字
return mv;
}
}
最終請(qǐng)求到該方法里面便可完成下載。
注意這里 ModelAndView 模型里面的 view 屬性承裝的是自定義視圖實(shí)體,而不是自定義視圖的名字,如果直接寫(xiě)成返回視圖名字的話需要注入 BeanNameViewResolver 視圖解析器。
如果想寫(xiě)成視圖名字返回的話需要如下配置
- Controller 里面更改如下
@RequestMapping("ec")
@Controller
public class ExcelController {
@GetMapping(value = "downloadList")
public String downloadStudentList(Model model) {
System.out.println("準(zhǔn)備下載學(xué)生列表");
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";
}
}
- 在 Spring MVC 配置文件中添加
BeanNameViewResolver視圖解析器
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="10"></property>
</bean>
- 在自定義視圖類上加上
Component,把視圖類的對(duì)象放Spring容器里。
@Component
public class ExcelView extends AbstractXlsxView {
...
}
其他相關(guān)文章
SpringMVC入門筆記
SpringMVC工作原理之處理映射[HandlerMapping]
SpringMVC工作原理之適配器[HandlerAdapter]
SpringMVC工作原理之參數(shù)解析
SpringMVC之自定義參數(shù)解析
SpringMVC工作原理之視圖解析及自定義
SpingMVC之<mvc:annotation-driven/>標(biāo)簽