SpringMVC源碼分析6:SpringMVC的視圖解析原理

---title: SpringMVC源碼分析6:SpringMVC的視圖解析原理date: 2018-06-07 11:03:19tags:html

  • SpringMVC
    categories:
  • 後端
  • SpringMVC
    ---

轉自 [SpringMVC視圖機制詳解[附帶源碼分析]](https://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html)java

本系列文章首發於個人我的博客:https://h2pl.github.io/git

歡迎閱覽個人CSDN專欄:Spring源碼解析 https://blog.csdn.net/column/details/21851.htmlgithub

部分代碼會放在個人的Github:https://github.com/h2pl/ web

目錄

前言

SpringMVC是目前主流的Web MVC框架之一。 spring

若是有同窗對它不熟悉,那麼請參考它的入門blog:www.cnblogs.com/fangjian042…後端

本文將分析SpringMVC的視圖這部份內容,讓讀者瞭解SpringMVC視圖的設計原理。緩存

重要接口和類介紹

1. View接口安全

  視圖基礎接口,它的各類實現類是無狀態的,所以是線程安全的。 該接口定義了兩個方法: session

2. AbstractView抽象類

View接口的基礎實現類。咱們稍微介紹一下這個抽象類。

  首先看下這個類的屬性:

  再看下抽象類中接口方法的實現:

  getContentType方法直接返回contentType屬性便可。

   render方法:

3. AbstractUrlBasedView抽象類

  繼承自AbstractView抽象類,增長了1個類型爲String的url參數。

4. InternalResourceView類

  繼承自AbstractUrlBasedView抽象類的類,表示JSP視圖。

  咱們看下這個類的renderMergedOutputModel方法(AbstractView抽象類定義的抽象方法,爲View接口提供的render方法服務)。

5. JstlView類

  JSTL視圖,繼承自InternalResourceView,該類大體上與InternalResourceView類一致。

6. AbstractTemplateView抽象類

  繼承自AbstractUrlBasedView抽象類,重寫了renderMergedOutputModel方法,在該方法中會調用renderMergedTemplateModel方法,renderMergedTemplateModel方法爲新定義的抽象方法。

  該抽象類有幾個boolean屬性exposeSessionAttributes,exposeRequestAttributes。 設置爲true的話會將request和session中的鍵值和值丟入到renderMergedTemplateModel方法中的model這個Map參數中。

  這個類是某些模板引擎視圖類的父類。 好比FreemarkerView,VelocityView。

7. FreeMarkerView類

  繼承自AbstractTemplateView抽象類。

  直接看renderMergedTemplateModel方法,renderMergedTemplateModel內部會調用doRender方法:

8. RedirectView類

  繼承自AbstractUrlBasedView,並實現SmartView接口。SmartView接口定義了1個boolean isRedirectView();方法。

  該視圖的renderMergedOutputModel方法主要就是經過response.sendRedirect進行重定向。

  有關RedirectView方面的知識樓主另外寫了1篇博客。www.cnblogs.com/fangjian042…

9. ViewResolver接口

  視圖解釋器,用來解析視圖View,與View接口配合使用。

  該接口只有1個方法,經過視圖名稱viewName和Locale對象獲得View接口實現類:

View resolveViewName(String viewName, Locale locale) throws Exception;複製代碼

10. AbstractCachingViewResolver抽象類

  帶有緩存功能的ViewResolver接口基礎實現抽象類,該類有個屬性名爲viewAccessCache的以 "viewName_locale" 爲key, View接口爲value的Map。

  該抽象類實現的resolveViewName方法內部會調用createView方法,方法內部會調用loadView抽象方法。

11. UrlBasedViewResolver類

  繼承自AbstractCachingViewResolver抽象類、並實現Ordered接口的類,是ViewResolver接口簡單的實現類。

  該類複寫了createView方法:

  父類(AbstractCachingViewResolver)的createView方法內部會調用loadView抽象方法,UrlBasedViewResolver實現了這個抽象方法:

  下面對UrlBasedViewResolver作1個test,配置以下:

<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  <property name="prefix" value="/WEB-INF/view/"/>
  <property name="suffix" value=".jsp"/>
  <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
  <property name="viewNames">
      
      <value type="java.lang.String">*</value>  
        
  </property>  
  <property name="contentType" value="text/html;charset=utf-8"/>
  <property name="attributesMap">
    <map>
      <entry key="mytest" value="mytestvalue"/>
    </map>
  </property>
  <property name="attributes">
    <props>
      <prop key="test">testvalue</prop>
    </props>
  </property>
</bean>
複製代碼

  咱們看到:以InternalResourceView這個JSP視圖做爲視圖;viewNames咱們設置了,這裏的表明所有視圖名(這個viewNames屬性不設置也能夠,表明所有視圖名都處理);http響應頭部contentType信息:text/html;charset=utf-8;attributesMap和attributes傳入的Map和Properties參數都會被丟入到staticAttributes屬性中,這個staticAttributes會被設置成AbstractView的staticAttributes屬性,也就是request域中的參數。

  咱們看到request域中沒有設置mytest和testvalue值。可是頁面中會顯示,由於咱們配置了attributesMap和attributes參數。

  若是咱們把viewNames中的"*"改爲"index1"。那麼就報錯了,由於處理視圖名的時候index匹配不上index1。

12. InternalResourceViewResolver類

  繼承自UrlBasedViewResolver,以InternalResourceView做爲視圖,若項目中存在「javax.servlet.jsp.jstl.core.Config」該類,那麼會以JstlView做爲視圖。重寫了buildView方法,主要就是爲了給InternalResourceView視圖設置屬性。

13. AbstractTemplateViewResolver類

  繼承自UrlBasedViewResolver,重寫了buildView方法,主要就是構造AbstractTemplateView以及爲它設置相應的屬性。

14. FreeMarkerViewResolver類

  繼承自AbstractTemplateViewResolver,將視圖設置爲FreeMarkerView。

15. ModelAndView對象

  顧名思義,帶有視圖和Model屬性的一個模型和視圖類。

  值得注意的是,這個視圖屬性是一個Object類型的數據,能夠直接是View接口的實現類或者視圖名(字符串)。

源碼分析

下面咱們來分析SpringMVC處理視圖的源碼。

SpringMVC在處理請求的時候,經過RequestMappingHandlerMapping獲得HandlerExecutionChain,而後經過RequestMappingHandlerAdapter獲得1個ModelAndView對象,以後經過processDispatchResult方法處理。

processDispatchResult方法以下:

若是配置的ViewResolver以下:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/view/"/>
  <property name="suffix" value=".jsp"/>
</bean>
複製代碼

那麼就是使用InternalResourceViewResolver來解析視圖。

以前分析過,InternalResourceViewResolver重寫了UrlBasedViewResolver的buildView方法。可是仍是會調用UrlBasedViewResolver的buildView方法。

最終獲得InternalResourceView或JstlView視圖。這兩個視圖的render方法本文介紹重要接口及類的時候已分析。

PS:DispathcerServlet中的viewResolvers屬性是個集合,若是存在多個ViewResolver對象,一定會有優先級的問題,這部分的內容請參考樓主的另外一篇博客:

www.cnblogs.com/fangjian042…

編碼自定義的ViewResolver

下面,咱們就來編寫自定義的ViewResolver。

自定義的ViewResolver處理視圖名的時候,遇到 "jsp:" 開頭的會找jsp頁面,遇到 "freemarker:" 開頭的找freemarker頁面。

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 buildView(viewCls, viewName.substring(JSP_URL_PREFIX.length()), getPrefix(), ".jsp");
    } else {
        //默認以freemarker處理
        return buildFreemarkerView(viewName);
    }
  }

  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;    
  }

  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;      
  }

  //get set方法省略

}
複製代碼

xml配置:

<bean class="org.format.demo.support.viewResolver.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>
複製代碼

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
  <property name="templateLoaderPath" value="/WEB-INF/view/"/>
  <property name="defaultEncoding" value="utf-8"/>
  <property name="freemarkerSettings">
    <props>
      <prop key="template_update_delay">10</prop>
      <prop key="locale">zh_CN</prop>
      <prop key="datetime_format">yyyy-MM-dd</prop>
      <prop key="date_format">yyyy-MM-dd</prop>
      <prop key="number_format">#.##</prop>
    </props>
  </property>
</bean>
複製代碼

簡單解釋一下:CustomViewResolver解析視圖名的時候,判斷 "jsp:" 和 "freemarker:" 開頭的名字,若是是 "jsp:" 開頭的,若是有JSTL依賴,構造JSTLView視圖,不然構造InternalResourceView視圖。若是是 "freemarker:" 構造FreemarkerView。在構造視圖以前分別會設置一些屬性。

xml配置:配置prefix是爲了給jsp視圖用的,freemarker視圖不須要prefix,由於FreemarkerView內部會使用配置的FreeMarkerConfigurer,並用FreeMarkerConfigurer內部的templateLoaderPath屬性做爲前綴,配置的suffix是爲了讓FreemarkerView使用,當後綴。

最後附上Controller代碼:

@Controller
@RequestMapping(value = "/tvrc")
public class TestViewResolverController {

    @RequestMapping("jsp")
    public ModelAndView jsp(ModelAndView view) {
        view.setViewName("jsp:trvc/index");
        return view;
    }

    @RequestMapping("/ftl")
    public ModelAndView freemarker(ModelAndView view) {
        view.setViewName("freemarker:trvc/index");
        return view;
    }

}複製代碼

視圖 /WEB-INF/view/trvc/index.jsp 中的的內容是輸出

This is jsp page

視圖 /WEB-INF/view/trvc/index.ftl 中的的內容是輸出

This is freemarker page

總結

本文分析了SpringMVC中的視圖機制,View和ViewResolver這兩個接口是視圖機制的核心,並分析了幾個重要的View和ViewResolver接口實現類,最終寫了一個區別jsp和freemarker視圖的ViewResolver實現類,讓讀者更加理解視圖機制。

但願這篇文章能幫助讀者瞭解SpringMVC視圖機制。

文中不免有錯誤,但願讀者可以指明出來。

圖片描述

相關文章
相關標籤/搜索