SpringMVC工作原理之視圖解析及自定義

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

SpringMVC運(yùn)行流程

一、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)類的方法

view2.png

我們挑幾個(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 接口的類都有哪些

viewResolver.png

我們挑幾個(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ò)程源碼分析

  1. 首先進(jìn)入 DispatcherServlet.doDispatch( ) 方法,經(jīng)過(guò)解析處理,找到了對(duì)應(yīng)的 Controller 里面的方法,執(zhí)行完成之后得到 ModeAndView 對(duì)象,開(kāi)始視圖渲染前后一些列工作
  1. 進(jìn)入渲染方法,開(kāi)始視圖渲染前的工作
  1. 進(jìn)入 render(..) 方法查看渲染源碼

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

  1. 拿到 View 對(duì)象后開(kāi)始視圖上的渲染工作,執(zhí)行 view.render(..) 方法,查看視圖渲染的具體流程
  1. 進(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ě)成視圖名字返回的話需要如下配置

  1. 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";
    }
}
  1. 在 Spring MVC 配置文件中添加 BeanNameViewResolver 視圖解析器
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
    <property name="order" value="10"></property>
</bean>
  1. 在自定義視圖類上加上 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)簽

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容