SpringMVC源碼閱讀:視圖解析器

1.前言

SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html

本文將經過源碼(基於Spring4.3.7)分析,弄清楚SpringMVC如何完成視圖解析的java

2.源碼分析

SpringMVC源碼閱讀:攔截器分析過doDispatch的運行過程,這裏再分析一遍git

回到DispatcherServlet類的doDispatch方法,看看doDispatch如何獲取ModelAndViewgithub

HandlerMapping根據request得到HandlerExecutionChainweb

根據HandlerExecutionChain獲取HandlerAdapterspring

HandlerAdapter根據request,response和HandlerExecutionChain調用handle方法返回ModelAndViewapi

而後交由processDispatchResult處理瀏覽器

1023行檢測從doDispatch方法運行到此是否有異常mvc

1037行根據ModelAndView渲染視圖app

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/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

https://docs.spring.io/spring/docs/current/javadoc-api/

https://github.com/spring-projects/spring-framework

文中不免有不足,歡迎指出

相關文章
相關標籤/搜索