1. 程式人生 > >13.SpringMVC 檢視解析

13.SpringMVC 檢視解析

基本概念

所有的 MVC 框架都有一套它自己的解析檢視的機制,SpringMVC 也不例外。

它使用 ViewResolver 進行檢視解析,讓使用者在瀏覽器中渲染模型。

ViewResolver 介面在檢視名稱和真正的檢視之間提供對映,它是一種開箱即用的技術,能夠解析 JSP、Velocity 模板和 XSLT 等多種檢視:

檢視解析器在 SpringMVC 中配置如下(以 InternalResourceViewResolver 為例 ):

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/page/" /> <property name="suffix" value=".jsp" /> </bean>
  • bean:表示指定的檢視解析器

  • viewClass: 表示要解析的檢視型別

  • prefix/suffix: 表示路徑字首/字尾,假設 ViewName 為 hello,則完整路徑為 /WEB-INF/page/hello.jsp

內部構造

ViewResolver ,即檢視解析器,該介面的主要作用就是根據檢視名 (ViewName),返回檢視 (View)。下面來它的原始碼:

public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

再它的繼承關係:

這裡寫圖片描述

AbstractCachingViewResolver

該類是 ViewResolver 介面的抽象實現類,用來表示一個帶有快取功能的檢視解析器。

下面來看它的 resolveViewName:

// 成員變數
private boolean cacheUnresolved = true;

public View resolveViewName(String viewName, Locale locale) throws Exception {

    // 1.判斷快取是否開啟
    if (!isCache()) {
        // 2.快取關閉,則建立檢視
        return createView(viewName, locale);
    } else {

        // 3.1.快取開啟,從 viewAccessCache 取出檢視
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);

        // 3.2.為空,從 viewCreationCache 取出檢視
        if (view == null) {
            synchronized (this.viewCreationCache) {
                // 3.3.仍然為空,則建立檢視
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    view = createView(viewName, locale);

                    // 3.3.1.仍然為空,標記為[不可解析檢視]
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }

                    // 3.3.2.不為空,新增進快取
                    if (view != null) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                        // 省略部分程式碼...
                    }
                }
            }
        }

        return ( view != UNRESOLVED_VIEW ? view : null );
    }
}

再來看看它的 createView 方法,該方法負責檢視的建立。

protected View createView(String viewName, Locale locale) throws Exception {
    // 空方法
    return loadView(viewName, locale);
}

觀察程式碼,可以該類的檢視解析過程如下:

  • 快取關閉,則建立檢視。

  • 快取開啟,則步驟如下:

    • 依次從 viewCreationCache,viewCreationCache 快取中查詢。
    • 存在,則返回檢視;不存在,則建立檢視。
    • 建立檢視完再次判斷,為空則標記為不可解析檢視,否則新增到兩個快取。

UrlBasedViewResolver

繼承自 AbstractCachingViewResolver 抽象類、並實現 Ordered 介面的類,是 ViewResolver 介面簡單的實現類,該類重寫了 createView、loadView 這兩個方法。

首先來看它的簽名:

public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered

1.createView

public static final String REDIRECT_URL_PREFIX = "redirect:";
public static final String FORWARD_URL_PREFIX = "forward:";

protected View createView(String viewName, Locale locale) throws Exception {

    // 是否支援該檢視的名的處理
    if (!canHandle(viewName, locale)) {
        return null;
    }

    // 判斷檢視名是否以 redirect: 開頭
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {

        // 獲取真正的檢視名
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());

        RedirectView view = new RedirectView(redirectUrl,
            isRedirectContextRelative(), isRedirectHttp10Compatible());

        return applyLifecycleMethods(viewName, view);
    }

    // 判斷檢視名是否以 forward: 開頭
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());

        // 返回檢視
        return new InternalResourceView(forwardUrl);
    }

    // 其他情況由父類處理,其實就是呼叫該類的 loadView 方法
    return super.createView(viewName, locale);
}

再來看看 applyLifecycleMethods 方法:

private View applyLifecycleMethods(String viewName, AbstractView view) {
    // 從 SpringMVC 容器中手動獲取 Bean
    return (View) getApplicationContext().getAutowireCapableBeanFactory().
        initializeBean(view, viewName);
}

2.loadView

再來看看它 的 loadView 方法,如果檢視名不包含 redirect,forward 時,呼叫父類的 createView 的時會呼叫到它。

protected View loadView(String viewName, Locale locale) throws Exception {
    // 建立 View
    AbstractUrlBasedView view = buildView(viewName);

    // 從 SpringMVC 容器中獲取 View
    View result = applyLifecycleMethods(viewName, view);

    return ( view.checkResource(locale) ? result : null );
}

再來看看 buildView 方法:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {

    // 1.建立 View ,利用反射例項類
    AbstractUrlBasedView view = 
        (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());

    // 2.設定 View 的 url
    view.setUrl(getPrefix() + viewName + getSuffix());

    // 3.設定 View 的 contentType  
    String contentType = getContentType();
    if (contentType != null) {
        view.setContentType(contentType);
    }

    // 4.設定 View 的其他相關屬性

    view.setRequestContextAttribute(getRequestContextAttribute());
    view.setAttributesMap(getAttributesMap());

    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
        view.setExposePathVariables(exposePathVariables);
    }

    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }

    String [ ] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
        view.setExposedContextBeanNames(exposedContextBeanNames);
    }

    return view;
}

例項探究

利用 ContentNegotiatingViewResolver 整合多種檢視

  • 首先來看配置檔案(這裡整合 vm,jsp,json,xml 這四種檢視顯示)
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <!-- 內容協商管理器 -->
    <property name="contentNegotiationManager"  ref="contentNegotiationManager"/>
    <!-- 檢視解析器  -->
    <property name="viewResolvers">
        <list>
            <!--Veocity 檢視解析器  -->
            <bean class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
                <property name="order" value="0"/>
                <property name="cache" value="false" />
                <property name="suffix" value=".vm" />
            </bean>
            <!--JSP 檢視解析器  -->
            <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
                <property name="prefix" value="/WEB-INF/page/"/>
                <property name="suffix" value=".jsp"></property>
            </bean>
        </list>
    </property>

    <!-- 預設檢視 -->
    <property name="defaultViews">
        <list>
            <!-- json 檢視解析 -->
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
            <!-- xml 檢視解析  -->
            <bean class="org.springframework.web.servlet.view.xml.MarshallingView" >
                <property name="marshaller">
                    <bean class="org.springframework.oxm.xstream.XStreamMarshaller"/>
                </property>
            </bean>
        </list>
    </property>
</bean>

<bean id="contentNegotiationManager"  class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <!-- 根據請求引數或拓展名對映到相應的MIME型別 -->
    <property name="mediaTypes">
        <map>
            <entry key="json"  value="application/json"/>
            <entry key="xml"  value="application/xml"/>
        </map>
    </property>
    <!-- 設定預設的MIME型別,如果沒有指定拓展名或請求引數,則使用此預設MIME型別解析檢視 -->
    <property name="defaultContentType"  value="text/html"/> 
    <!-- 是否不適用請求頭確定MIME型別 -->
    <property name="ignoreAcceptHeader"  value="true"/> 
    <!-- 是否根據路徑拓展名確定MIME型別 -->
    <property name="favorPathExtension"  value="true"/> 
     <!-- 是否使用引數來確定MIME型別 -->
    <property name="favorParameter" value="false" /> 
</bean>

<!-- Veocity 模版配置 -->
<bean class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="configLocation" value="/WEB-INF/velocity.properties" />
</bean>
  • Velocity 屬性檔案配置
input.encoding=UTF-8
output.encoding=UTF-8
contentType=text/html;charset=UTF-8  
resource.loader=webapp
webapp.resource.loader.class=org.apache.velocity.tools.view.WebappResourceLoader
webapp.resource.loader.path=/WEB-INF/velocity/
  • 測試
// 先用 velocity 檢視解析(order = 0),然後再用 jsp 檢視解析
http://localhost:8080/demo/hello

// 返回 json 檢視
http://localhost:8080/demo/hello.json

// 返回 xml 檢視
http://localhost:8080/demo/hello.xml

注意:

  • 這裡利用路徑副檔名(favorPathExtension)區分 MIME 型別,以此確定要返回的檢視型別。也可以利用引數(預設為 fomart)來區分。

  • VelocityViewResolver(velocity 檢視解析)的 order 優先順序必須比 InternalResourceViewResolver(jsp 檢視解析器)高。因為 InternalResourceViewResolver 不管路徑下是否存在指定 jsp 檔案都會返回 View(檢視),這樣會導致 VelocityViewResolver 不生效。

參考