雖然我們在之前的示例中一直都是使用一個InternalResourceViewResolver進行視圖查找,但這並不意味着每個基於 Spring MVC的Web應用程序只能使用一個ViewResolver。實際上,Dispatcher- Servlet不但可以接受多個HandlerMapping以處理Web請求到具體Handler的映射,也可以接受多個ViewResolver以處理視圖的查找。
DispatcherServlet初始化時,將根據類型掃描自己的WebApplicationContext中定義的 ViewResolver。如果查找到存在多個ViewResolver的定義,DispatcherServlet將根據這些ViewResolver 的優先級進行排序,然後當需要根據邏輯視圖名查找具體的View實例的時候,將按照排序後的順序遍歷這些ViewResolver,只要期間任何一個 ViewResolver返回非空的View實例,當前查找即告結束。如果DispatcherServlet沒能在當前的 WebApplicationContext中找到任何的ViewResolver定義,它將使用 InternalResourceViewResolver作爲默認的ViewResolver使用。
ViewResolver的優先級的指定使用Ordered接口作爲標準,這已經成爲Spring框架內設定優先級方式的慣例了。假設我們希望主要使用ResourceBundleViewResolver進行邏輯視圖名到具體View實例的查找,如果沒能找到,再尋求 InternalResourceViewResolver的幫助。我們可以在Dispatcher- Servlet的WebApplicationContext中添加如下配置內容:
01 |
<bean id= "resourceBundleViewResolver"
|
02 |
class ="org.springframework.Web.servlet.view.
|
03 |
ResourceBundleViewResolver"> |
04 |
<property name= "order" value= "1" ></property>
|
05 |
</bean> |
06 |
<bean id= "viewResolver"
|
07 |
class ="org.springframework.Web.servlet.view.
|
08 |
InternalResourceViewResolver"> |
09 |
<property name= "prefix" value= "/WEB-INF/jsp/" />
|
10 |
<property name= "suffix" value= ".jsp" />
|
11 |
</bean> |
相應ViewResolver的bean定義對應的id或者name屬性值是任意的,DispatcherServlet將按照類型來獲取ViewResolver。如果沒有爲某個ViewResolver指定order值的話,默認值爲Integer.MAX_ VALUE,對應的是最低優先級。
如果爲DispatcherServlet指定多個ViewResolver的話,不要給予 InternalResour- ceViewResolver以及其他UrlBasedViewResolver子類過高的優先級,因爲這些ViewResolver即使找不到相應的視圖,也不會返回null以給我們輪詢下一個ViewResolver的機會,這樣,我們所指定的其他ViewResolver實際上就形同虛設。合理的處理方式是,給予ResourceBundleView- Resolver或者XmlViewResolver這種能夠通過返回null以表明無法找到相應視圖的ViewResolver較高的優先級,而只是將 InternalResourceViewResolver(或者其他類似行爲的ViewResolver)添加爲最低優先級ViewResolver,以作爲DispatcherServlet的後備查找對象。
----------------------------------------------------------------------------------------------
所有web應用的MVC框架都有它們定位視圖的方式。 Spring提供了視圖解析器供你在瀏覽器顯示模型數據,而不必被束縛在特定的視圖技術上。 Spring內置了對JSP,Velocity模版和XSLT視圖的支持。第 14 章 集成視圖技術這一章詳細說明了Spring如何與不同的視圖技術集成。
ViewResolver
和View
是Spring的視圖處理方式中特別重要的兩個接口。 ViewResolver
提供了從視圖名稱到實際視圖的映射。 View
處理請求的準備工作,並將該請求提交給某種具體的視圖技術。
正如前面(第 13.3 節 “控制器”)所討論的, SpringWeb框架的所有控制器都返回一個ModelAndView
實例。 Sprnig中的視圖以名字爲標識,視圖解析器通過名字來解析視圖。Spring提供了多種視圖解析器。我們將舉例加以說明。
ViewResolver
描述
AbstractCachingViewResolver |
抽象視圖解析器實現了對視圖的緩存。在視圖被使用之前,通常需要進行一些準備工作。 從它繼承的視圖解析器將對要解析的視圖進行緩存。 |
XmlViewResolver |
XmlViewResolver實現ViewResolver ,支持XML格式的配置文件。 該配置文件必須採用與Spring XML Bean Factory相同的DTD。默認的配置文件是 /WEB-INF/views.xml 。 |
ResourceBundleViewResolver |
ResourceBundleViewResolver實現ViewResolver , 在一個ResourceBundle 中尋找所需bean的定義。 這個bundle通常定義在一個位於classpath中的屬性文件中。默認的屬性文件是views.properties 。 |
UrlBasedViewResolver |
UrlBasedViewResolver實現ViewResolver , 將視圖名直接解析成對應的URL,不需要顯式的映射定義。 如果你的視圖名和視圖資源的名字是一致的,就可使用該解析器,而無需進行映射。 |
InternalResourceViewResolver |
作爲UrlBasedViewResolver 的子類, 它支持InternalResourceView (對Servlet和JSP的包裝), 以及其子類JstlView 和TilesView 。 通過setViewClass 方法,可以指定用於該解析器生成視圖使用的視圖類。 更多信息請參考UrlBasedViewResolver 的Javadoc。 |
VelocityViewResolver /FreeMarkerViewResolver
|
作爲UrlBasedViewResolver 的子類, 它能支持VelocityView (對Velocity模版的包裝)和FreeMarkerView 以及它們的子類。 |
舉例來說,當使用JSP作爲視圖層技術時,就可以使用UrlBasedViewResolver
。 這個視圖解析器會將視圖名解析成URL,並將請求傳遞給RequestDispatcher來顯示視圖。
<bean id="viewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/></bean>當返回的視圖名爲test
時, 這個視圖解析器將請求傳遞給RequestDispatcher
,RequestDispatcher
再將請求傳遞給/WEB-INF/jsp/test.jsp
。
當在一個web應用中混合使用不同的視圖技術時,可以使用ResourceBundleViewResolver
:
<bean id="viewResolver"> <property name="basename" value="views"/> <property name="defaultParentView" value="parentView"/></bean>ResourceBundleViewResolver
通過basename所指定的ResourceBundle
解析視圖名。 對每個待解析的視圖,ResourceBundle裏的[視圖名].class
所對應的值就是實現該視圖的類。 同樣,[視圖名].url
所對應的值是該視圖所對應的URL。 從上面的例子裏能夠發現,可以指定一個parent view,其它的視圖都可以從parent view擴展。用這種方法,可以聲明一個默認的視圖。
關於視圖緩存的注意事項 - 繼承AbstractCachingViewResolver
的解析器可以緩存它曾經解析過的視圖。 當使用某些視圖技術時,這可以大幅度的提升性能。 也可以關掉緩存功能,只要把cache
屬性設成false
就可以了。 而且,如果需要在系統運行時動態地更新某些視圖(比如,當一個Velocity模板被修改了), 可以調用removeFromCache(String viewName, Locale loc)
方法來達到目的。
Spring支持多個視圖解析器一起使用。可以把它們當作一個解析鏈。 這樣有很多好處,比如在特定情況下重新定義某些視圖。 定義視圖解析鏈很容易,只要在應用上下文中定義多個解析器就可以了。 必要時,也可以通過order
屬性來聲明每個解析器的序列。 要記住的是,某個解析器的order越高, 它在解析鏈中的位置越靠後。
下面這個例子展示了一個包含兩個解析器的解析鏈。 一個是InternalResourceViewResolver
,這個解析器總是被自動的放到鏈的末端。 另一個是XmlViewResolver
,它支持解析Excel視圖(而InternalResourceViewResolver
不可以)。
01 |
<bean id= "jspViewResolver" >
|
02 |
<property name= "viewClass" value= "org.springframework.web.servlet.view.JstlView" />
|
03 |
<property name= "prefix" value= "/WEB-INF/jsp/" />
|
04 |
<property name= "suffix" value= ".jsp" /></bean><bean id= "excelViewResolver" >
|
05 |
<property name= "order" value= "1" />
|
06 |
<property name= "location" value= "/WEB-INF/views.xml" />
|
07 |
</bean> |
08 |
<!-- in views.xml --> |
09 |
<beans> |
10 |
<bean name= "report" />
|
11 |
</beans> |
如果某個解析器沒有找到合適的視圖,Spring會在上下文中尋找是否配置了其它的解析器。 如果有,它會繼續進行解析,否則,Srping會拋出一個Exception
。
要記住,當一個視圖解析器找不到合適的視圖時,它可能 返回null值。 但是,不是每個解析器都這麼做。這是因爲,在某些情況下,解析器可能無法偵測出符合要求的視圖是否存在。 比如,InternalResourceViewResolver
在內部調用了RequestDispatcher
。 請求分發是檢查一個JSP文件是否存在的唯一方法,不幸的是,這個方法只能用一次。 同樣的問題在VelocityViewResolver
和其它解析器中也有。 當使用這些解析器時,最好仔細閱讀它們的Javadoc,看看需要的解析器是否無法發現不存在的視圖。 這個問題產生的副作用是,如果InternalResourceViewResolver
解析器沒有放在鏈的末端, InternalResourceViewResolver
後面的那些解析器根本得不到使用, 因爲InternalResourceViewResolver
總是返回一個視圖!
在前面我們提到過,一個控制器通常會返回視圖名,然後由視圖解析器解析到某種視圖實現。 對於像JSP這樣實際上由Servlet/JSP引擎處理的視圖, 我們通常使用InternalResourceViewResolver
和InternalResourceView
。 這種視圖實現最終會調用Servlet API的RequestDispatcher.forward(..)
方法或RequestDispatcher.include()
方法將用戶指向最終頁面。 對於別的視圖技術而言(比如Velocity、XSLT等等),視圖本身就會生成返回給用戶的內容。
有些時候,在視圖顯示以前,我們可能需要給用戶發一個HTTP redirect重定向指令。 比如,一個控制器成功的處理了一個表單提交(數據以HTTP POST的方式發送),它最終可能委託給另一個控制器來完成剩下的工作。 在這種情況下,如果我們使用內部forward,接手工作的那個控制器將會得到所有以POST方式提交的表單數據, 這可能會引起潛在的混淆,干擾那個控制器的正常工作。 另一個在顯示視圖之前返回HTTP redirect的原因是這可以防止用戶重複提交同一表單。 具體一點講,瀏覽器先用POST
的方式提交表單,然後它接收到重定向的指令,它繼續用GET
的方式去下載新的頁面。 從瀏覽器的角度看,這個新的頁面不是POST
的返回結果,而是GET
的。 這樣,用戶不可能在點擊刷新的時候不小心再次提交表單,因爲刷新的結果是再次用GET
去下載表單提交後的結果頁面,而不是重新提交初始的POST
數據。
在控制器中強制重定向的方法之一是讓控制器創建並返回一個Spring的RedirectView
的實例。 在這種情況下,DispatcherServlet
不會使用通常的視圖解析機制, 既然它已經拿到了一個(重定向)視圖,它就讓這個視圖去完成餘下的工作。
RedirectView
會調用HttpServletResponse.sendRedirect()
方法, 其結果是給用戶的瀏覽器發回一個HTTP redirect。所有的模型屬性都被轉換成以HTTP請求的訪問參數。 這意味着這個模型只能包含可以被簡便的轉換成string形式的HTTP請求訪問參數的對象,比如String或者可以被轉換成String的類型。
如果使用RedirectView
視圖,並且它是由控制器創建的, 重定向的URL最好是用Spring所提供的IoC功能注射到控制器中。 這樣這個URL就可以和視圖名一起在上下文中被聲明,而不是固化在控制器內。
儘管使用RedirectView
幫我們達到了目的,但是如果控制器生成RedirectView
的話, 控制器不可避免地要知道某個請求的結果是讓用戶重定向到另一個頁面。這不是最佳的實現,因爲這使得系統不同模塊之間結合得過於緊密。 其實控制器不應該過問返回結果是如何生成的,通常情況下,它應該只關心注入給它的視圖名稱。
解決上述問題的方法是依靠redirect:
前綴。 如果返回的視圖名包含redirect:前綴,UrlBasedViewResolver
(以及它的子類) 會知道系統要生成一個HTTP redirect。 視圖名其餘的部分會被當作重定向URL。
這樣做的最終結果跟控制器返回RedirectView
是一樣的,但現在控制器只需要和邏輯上的視圖名打交道。 redirect:/my/response/controller.html
這個邏輯視圖名中的URL是當前servlet context中的相對路徑。 與之相比,redirect:http://myhost.com/some/arbitrary/path.html
中的URL是絕對路徑。 重要的是,只要這個重定向視圖名和其他視圖名以相同的方式注入到控制器中,控制器根本不知道重定向是否發生。
類似的,我們也可以使用包含有forward:
前綴的視圖名。 這些視圖名會被UrlBasedViewResolver
和它的子類正確解析。 解析的內部實現是生成一個InternalResourceView
, 這個視圖最終會調用RequestDispatcher.forward()
方法,將forward視圖名的其餘部分作爲URL。 所以,當使用InternalResourceViewResolver
/InternalResourceView
, 並且你所用的視圖技術是JSP時,你沒有必要使用這個前綴。 但是,當你主要使用其它的視圖技術,但仍需要對Servlet/JSP engine處理的頁面強制forward時, 這個forward前綴還是很有用的(但就這個問題而言,如果不想使用forward前綴,也可以使用視圖解析鏈)。
和redirect:
前綴一樣,如果含有forward前綴的視圖名和其他視圖名一樣被注入控制器, 控制器根本不需要知道在處理響應的過程中是否發生任何特殊情況。
原文出處:http://www.dofind.net/prodevelop/32.html