?
This document uses PHP Chinese website manual Release
所有web應(yīng)用的MVC框架都有它們定位視圖的方式。 Spring提供了視圖解析器供你在瀏覽器顯示模型數(shù)據(jù),而不必被束縛在特定的視圖技術(shù)上。 Spring內(nèi)置了對JSP,Velocity模版和XSLT視圖的支持。 第?14?章 集成視圖技術(shù) 這一章詳細(xì)說明了Spring如何與不同的視圖技術(shù)集成。
ViewResolver
和View
是Spring的視圖處理方式中特別重要的兩個接口。
ViewResolver
提供了從視圖名稱到實際視圖的映射。
View
處理請求的準(zhǔn)備工作,并將該請求提交給某種具體的視圖技術(shù)。
正如前面(第?13.3?節(jié) “控制器”)所討論的,
SpringWeb框架的所有控制器都返回一個ModelAndView
實例。
Sprnig中的視圖以名字為標(biāo)識,視圖解析器通過名字來解析視圖。Spring提供了多種視圖解析器。我們將舉例加以說明。
表?13.4.?視圖解析器
ViewResolver |
描述 |
---|---|
AbstractCachingViewResolver |
抽象視圖解析器實現(xiàn)了對視圖的緩存。在視圖被使用之前,通常需要進(jìn)行一些準(zhǔn)備工作。 從它繼承的視圖解析器將對要解析的視圖進(jìn)行緩存。 |
XmlViewResolver |
XmlViewResolver實現(xiàn)ViewResolver ,支持XML格式的配置文件。
該配置文件必須采用與Spring XML Bean Factory相同的DTD。默認(rèn)的配置文件是
/WEB-INF/views.xml 。
|
ResourceBundleViewResolver |
ResourceBundleViewResolver實現(xiàn)ViewResolver ,
在一個ResourceBundle 中尋找所需bean的定義。
這個bundle通常定義在一個位于classpath中的屬性文件中。默認(rèn)的屬性文件是views.properties 。
|
UrlBasedViewResolver |
UrlBasedViewResolver實現(xiàn)ViewResolver ,
將視圖名直接解析成對應(yīng)的URL,不需要顯式的映射定義。
如果你的視圖名和視圖資源的名字是一致的,就可使用該解析器,而無需進(jìn)行映射。
|
InternalResourceViewResolver |
作為UrlBasedViewResolver 的子類,
它支持InternalResourceView (對Servlet和JSP的包裝),
以及其子類JstlView 和TilesView 。
通過setViewClass 方法,可以指定用于該解析器生成視圖使用的視圖類。
更多信息請參考UrlBasedViewResolver 的Javadoc。
|
VelocityViewResolver /
FreeMarkerViewResolver
|
作為UrlBasedViewResolver 的子類,
它能支持VelocityView (對Velocity模版的包裝)和FreeMarkerView 以及它們的子類。
|
舉例來說,當(dāng)使用JSP作為視圖層技術(shù)時,就可以使用UrlBasedViewResolver
。
這個視圖解析器會將視圖名解析成URL,并將請求傳遞給RequestDispatcher來顯示視圖。
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
當(dāng)返回的視圖名為test
時,
這個視圖解析器將請求傳遞給RequestDispatcher
,RequestDispatcher
再將請求傳遞給/WEB-INF/jsp/test.jsp
。
當(dāng)在一個web應(yīng)用中混合使用不同的視圖技術(shù)時,可以使用ResourceBundleViewResolver
:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> <property name="defaultParentView" value="parentView"/> </bean>
ResourceBundleViewResolver
通過basename所指定的ResourceBundle
解析視圖名。
對每個待解析的視圖,ResourceBundle里的[視圖名].class
所對應(yīng)的值就是實現(xiàn)該視圖的類。
同樣,[視圖名].url
所對應(yīng)的值是該視圖所對應(yīng)的URL。
從上面的例子里能夠發(fā)現(xiàn),可以指定一個parent view,其它的視圖都可以從parent view擴(kuò)展。用這種方法,可以聲明一個默認(rèn)的視圖。
關(guān)于視圖緩存的注意事項 - 繼承AbstractCachingViewResolver
的解析器可以緩存它曾經(jīng)解析過的視圖。
當(dāng)使用某些視圖技術(shù)時,這可以大幅度的提升性能。
也可以關(guān)掉緩存功能,只要把cache
屬性設(shè)成false
就可以了。
而且,如果需要在系統(tǒng)運行時動態(tài)地更新某些視圖(比如,當(dāng)一個Velocity模板被修改了),
可以調(diào)用removeFromCache(String viewName, Locale loc)
方法來達(dá)到目的。
Spring支持多個視圖解析器一起使用??梢园阉鼈儺?dāng)作一個解析鏈。
這樣有很多好處,比如在特定情況下重新定義某些視圖。
定義視圖解析鏈很容易,只要在應(yīng)用上下文中定義多個解析器就可以了。
必要時,也可以通過order
屬性來聲明每個解析器的序列。
要記住的是,某個解析器的order越高, 它在解析鏈中的位置越靠后。
下面這個例子展示了一個包含兩個解析器的解析鏈。
一個是InternalResourceViewResolver
,這個解析器總是被自動的放到鏈的末端。
另一個是XmlViewResolver
,它支持解析Excel視圖(而InternalResourceViewResolver
不可以)。
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="order" value="1"/>
<property name="location" value="/WEB-INF/views.xml"/>
</bean>
<!-- in views.xml
-->
<beans>
<bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>
如果某個解析器沒有找到合適的視圖,Spring會在上下文中尋找是否配置了其它的解析器。
如果有,它會繼續(xù)進(jìn)行解析,否則,Srping會拋出一個Exception
。
要記住,當(dāng)一個視圖解析器找不到合適的視圖時,它可能 返回null值。
但是,不是每個解析器都這么做。這是因為,在某些情況下,解析器可能無法偵測出符合要求的視圖是否存在。
比如,InternalResourceViewResolver
在內(nèi)部調(diào)用了RequestDispatcher
。
請求分發(fā)是檢查一個JSP文件是否存在的唯一方法,不幸的是,這個方法只能用一次。
同樣的問題在VelocityViewResolver
和其它解析器中也有。
當(dāng)使用這些解析器時,最好仔細(xì)閱讀它們的Javadoc,看看需要的解析器是否無法發(fā)現(xiàn)不存在的視圖。
這個問題產(chǎn)生的副作用是,如果InternalResourceViewResolver
解析器沒有放在鏈的末端,
InternalResourceViewResolver
后面的那些解析器根本得不到使用,
因為InternalResourceViewResolver
總是返回一個視圖!
在前面我們提到過,一個控制器通常會返回視圖名,然后由視圖解析器解析到某種視圖實現(xiàn)。
對于像JSP這樣實際上由Servlet/JSP引擎處理的視圖,
我們通常使用InternalResourceViewResolver
和InternalResourceView
。
這種視圖實現(xiàn)最終會調(diào)用Servlet API的RequestDispatcher.forward(..)
方法或RequestDispatcher.include()
方法將用戶指向最終頁面。
對于別的視圖技術(shù)而言(比如Velocity、XSLT等等),視圖本身就會生成返回給用戶的內(nèi)容。
有些時候,在視圖顯示以前,我們可能需要給用戶發(fā)一個HTTP redirect重定向指令。
比如,一個控制器成功的處理了一個表單提交(數(shù)據(jù)以HTTP POST的方式發(fā)送),它最終可能委托給另一個控制器來完成剩下的工作。
在這種情況下,如果我們使用內(nèi)部forward,接手工作的那個控制器將會得到所有以POST方式提交的表單數(shù)據(jù),
這可能會引起潛在的混淆,干擾那個控制器的正常工作。
另一個在顯示視圖之前返回HTTP redirect的原因是這可以防止用戶重復(fù)提交同一表單。
具體一點講,瀏覽器先用POST
的方式提交表單,然后它接收到重定向的指令,它繼續(xù)用GET
的方式去下載新的頁面。
從瀏覽器的角度看,這個新的頁面不是POST
的返回結(jié)果,而是GET
的。
這樣,用戶不可能在點擊刷新的時候不小心再次提交表單,因為刷新的結(jié)果是再次用GET
去下載表單提交后的結(jié)果頁面,而不是重新提交初始的POST
數(shù)據(jù)。
在控制器中強(qiáng)制重定向的方法之一是讓控制器創(chuàng)建并返回一個Spring的RedirectView
的實例。
在這種情況下,DispatcherServlet
不會使用通常的視圖解析機(jī)制,
既然它已經(jīng)拿到了一個(重定向)視圖,它就讓這個視圖去完成余下的工作。
RedirectView
會調(diào)用HttpServletResponse.sendRedirect()
方法,
其結(jié)果是給用戶的瀏覽器發(fā)回一個HTTP redirect。所有的模型屬性都被轉(zhuǎn)換成以HTTP請求的訪問參數(shù)。
這意味著這個模型只能包含可以被簡便的轉(zhuǎn)換成string形式的HTTP請求訪問參數(shù)的對象,比如String或者可以被轉(zhuǎn)換成String的類型。
如果使用RedirectView
視圖,并且它是由控制器創(chuàng)建的,
重定向的URL最好是用Spring所提供的IoC功能注射到控制器中。
這樣這個URL就可以和視圖名一起在上下文中被聲明,而不是固化在控制器內(nèi)。
盡管使用RedirectView
幫我們達(dá)到了目的,但是如果控制器生成RedirectView
的話,
控制器不可避免地要知道某個請求的結(jié)果是讓用戶重定向到另一個頁面。這不是最佳的實現(xiàn),因為這使得系統(tǒng)不同模塊之間結(jié)合得過于緊密。
其實控制器不應(yīng)該過問返回結(jié)果是如何生成的,通常情況下,它應(yīng)該只關(guān)心注入給它的視圖名稱。
解決上述問題的方法是依靠redirect:
前綴。
如果返回的視圖名包含redirect:前綴,UrlBasedViewResolver
(以及它的子類)
會知道系統(tǒng)要生成一個HTTP redirect。 視圖名其余的部分會被當(dāng)作重定向URL。
這樣做的最終結(jié)果跟控制器返回RedirectView
是一樣的,但現(xiàn)在控制器只需要和邏輯上的視圖名打交道。
redirect:/my/response/controller.html
這個邏輯視圖名中的URL是當(dāng)前servlet context中的相對路徑。
與之相比,redirect:http://myhost.com/some/arbitrary/path.html
中的URL是絕對路徑。
重要的是,只要這個重定向視圖名和其他視圖名以相同的方式注入到控制器中,控制器根本不知道重定向是否發(fā)生。
類似的,我們也可以使用包含有forward:
前綴的視圖名。
這些視圖名會被UrlBasedViewResolver
和它的子類正確解析。
解析的內(nèi)部實現(xiàn)是生成一個InternalResourceView
,
這個視圖最終會調(diào)用RequestDispatcher.forward()
方法,將forward視圖名的其余部分作為URL。
所以,當(dāng)使用InternalResourceViewResolver
/InternalResourceView
,
并且你所用的視圖技術(shù)是JSP時,你沒有必要使用這個前綴。
但是,當(dāng)你主要使用其它的視圖技術(shù),但仍需要對Servlet/JSP engine處理的頁面強(qiáng)制forward時,
這個forward前綴還是很有用的(但就這個問題而言,如果不想使用forward前綴,也可以使用視圖解析鏈)。
和redirect:
前綴一樣,如果含有forward前綴的視圖名和其他視圖名一樣被注入控制器,
控制器根本不需要知道在處理響應(yīng)的過程中是否發(fā)生任何特殊情況。