SpringMVC(9) - 解析檢視
所有MVC框架都為Web應用程式提供了一種處理檢視的方法。 Spring提供了檢視解析器,可以在瀏覽器中呈現模型,而無需與特定的檢視技術聯絡起來。開箱即用,Spring允許使用JSP,Velocity模板和XSLT檢視。
對Spring處理檢視的方式很重要的兩個介面是ViewResolver和View。 ViewResolver提供檢視名稱和實際檢視之間的對映。 View介面解決了請求的準備問題,並將請求交給其中一種檢視技術。
1. 使用ViewResolver介面解析檢視
Spring Web MVC控制器中的所有處理器方法必須顯式地(例如,通過返回String、View或ModelAndView)或隱式地(基於約定)來解析為邏輯檢視名稱。 Spring中的檢視由邏輯檢視名稱處理,並由檢視解析器解析。 Spring帶有相當多的檢視解析器。該表列出了其中大部分內容;下面是幾個例子。
ViewResolver | Description |
---|---|
|
快取檢視的抽象檢視解析器。檢視通常需要準備才能使用;擴充套件此檢視解析器提供快取。 |
|
ViewResolver的實現,它接受XML中編寫的配置檔案,該配置檔案使用與Spring的XML bean工廠相同的DTD。預設配置檔案是/WEB-INF/views.xml。 |
|
ViewResolver的實現,它使用由bundle base name指定的ResourceBundle中定義的bean。通常,在屬性檔案中定義捆綁包,該檔案位於類路徑中。預設檔名是views.properties。 |
|
ViewResolver介面的簡單實現,它可以直接將邏輯檢視名稱解析為URL,而無需顯式對映定義。如果邏輯名稱與檢視資源的名稱直接匹配,則這是合適的,而不需要任意對映。 |
|
UrlBasedViewResolver的便捷子類,支援InternalResourceView(實際上是Servlet和JSP)和子類,如JstlView和TilesView。可以使用setViewClass(..)為此解析器生成的所有檢視指定檢視類。 |
|
UrlBasedViewResolver的便捷子類,分別支援VelocityView(實際上是Velocity模板)或FreeMarkerView,以及它們的自定義子類。 |
|
ViewResolver介面的實現,該介面根據請求檔名或Accept頭解析檢視。 |
例如,使用JSP作為檢視技術,可以使用UrlBasedViewResolver。 此檢視解析程式將檢視名稱轉換為URL,並將請求移交給RequestDispatcher以呈現檢視。
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<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" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
<property name="defaultParentView" value="parentView"/>
</bean>
ResourceBundleViewResolver檢查由basename標識的ResourceBundle,並且對於它應該解析的每個檢視,它使用屬性[viewname].(class) 的值作為檢視類,並使用屬性[viewname].url的值作為檢視網址。示例可以在下一章中找到,其中包括檢視技術。可以標識父檢視,屬性檔案中的所有檢視都從該檢視中“擴充套件”。這樣,可以指定預設檢視類。
注:AbstractCachingViewResolver的子類快取它們解析的檢視例項。快取可提高某些檢視技術的效能。可以通過將cache屬性設定為false來關閉快取。此外,如果必須在執行時重新整理某個檢視(例如,修改Velocity模板時),則可以使用removeFromCache(String viewName,Locale loc)方法。
2. 連結ViewResolver
Spring支援多個檢視解析器。因此,可以連結解析器,例如,在某些情況下覆蓋特定檢視。可以通過嚮應用程式上下文新增多個解析程式來連結檢視解析器,並在必要時通過設定order屬性來指定排序。order屬性越高,檢視解析器在鏈中的位置越靠後。
在下面的示例中,檢視解析器鏈包含兩個解析器,一個InternalResourceViewResolver,它始終自動定位為鏈中的最後一個解析器,以及一個用於指定Excel檢視的XmlViewResolver。 InternalResourceViewResolver不支援Excel檢視。
<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會檢查其他檢視解析器的上下文。如果存在其他檢視解析器,Spring將繼續檢查它們,直到檢視得到解決。如果沒有檢視解析器返回檢視,Spring會丟擲ServletException。
檢視解析器約定指定檢視解析器可以返回null以指示無法找到檢視。但是,並非所有檢視解析器都這樣做,因為在某些情況下,解析器根本無法檢測檢視是否存在。例如,InternalResourceViewResolver在內部使用RequestDispatcher,並且排程是確定JSP是否存在的唯一方法,但此操作只能執行一次。 VelocityViewResolver和其他一些同樣適用。檢視特定檢視解析器的javadoc,以檢視它是否報告不存在的檢視。因此,將一個InternalResourceViewResolver放在鏈中的最後一個位置會導致鏈未被完全檢查,因為InternalResourceViewResolver將始終返回一個檢視!
3. 重定向到檢視
如前所述,控制器通常返回邏輯檢視名稱,檢視解析器將其解析為特定的檢視技術。對於通過Servlet或JSP引擎處理的JSP等檢視技術,此解決方案通常通過InternalResourceViewResolver和InternalResourceView的組合來處理,InternalResolver和InternalResourceView通過Servlet API的RequestDispatcher.forward(..)方法或RequestDispatcher.include()方法發出內部轉發或包含 。對於其他檢視技術,例如Velocity,XSLT等,檢視本身將內容直接寫入響應流。
有時需要在呈現檢視之前向客戶端發出HTTP重定向。例如,當使用POST資料呼叫一個控制器時,這是可取的,並且響應實際上是對另一個控制器的委託(例如,在成功的表單提交上)。在這種情況下,正常的內部轉發將意味著另一個控制器也將看到相同的POST資料,如果它可能將其與其他預期資料混淆,則可能存在問題。在顯示結果之前執行重定向的另一個原因是消除使用者多次提交表單資料的可能性。在這種情況下,瀏覽器將首先發送初始POST;然後它會收到重定向到不同URL的響應;最後,瀏覽器將對重定向響應中指定的URL執行後續GET。因此,從瀏覽器的角度來看,當前頁面不反映POST的結果,而是反映GET的結果。最終結果是使用者無法通過執行重新整理重新發布相同資料。重新整理強制結果頁面的GET,而不是重新發送初始POST資料。
3.1 RedirectView
作為控制器響應的結果,強制重定向的一種方法是控制器建立並返回Spring的RedirectView例項。在這種情況下,DispatcherServlet不使用普通的檢視解析機制。而是因為它已經被賦予(重定向)檢視,DispatcherServlet只是指示檢視執行其工作。 RedirectView依次呼叫HttpServletResponse.sendRedirect()將HTTP重定向傳送到客戶端瀏覽器。
如果使用RedirectView並且檢視是由控制器本身建立的,則建議將重定向URL配置注入控制器,以便它不會被固定在控制器中,而是在上下文中與檢視名稱一起配置。
3.2 將資料傳遞給重定向目標
預設情況下,所有模型屬性都被視為在重定向URL中公開為URI模板變數。在其餘屬性中,原始型別或基本型別的集合/陣列將自動附加為查詢引數。
如果專門為重定向準備了模型例項,則將原始型別屬性作為查詢引數附加可能是期望的結果。但是,在帶註解的控制器中,模型可能包含為渲染目的而新增的附加屬性(例如,下拉欄位值)。為了避免在URL中出現此類屬性的可能性,@RequestMapping方法可以宣告RedirectAttributes型別的引數,並使用它來指定可供RedirectView使用的確切屬性。如果方法重定向,則使用RedirectAttributes的內容。否則,使用模型的內容。
RequestMappingHandlerAdapter提供了一個名為“ignoreDefaultModelOnRedirect”的標誌,可用於指示如果控制器方法重定向,則永遠不應使用預設模型的內容。 相反,控制器方法應宣告RedirectAttributes型別的屬性,或者如果不這樣做,則不應將任何屬性傳遞給RedirectView。 MVC名稱空間和MVC Java配置都將此標誌設定為false以保持向後相容性。 但是,對於新應用程式,官方建議將其設定為true。
請注意,擴充套件重定向URL時,當前請求中的URI模板變數會自動變為可用,並且不需要通過Model或RedirectAttributes顯式新增。 例如:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
將資料傳遞到重定向目標的另一種方法是通過Flash屬性。與其他重定向屬性不同,Flash屬性儲存在HTTP會話中(因此不會出現在URL中)。
3.3 redirect:prefix
雖然RedirectView的使用工作正常,但如果控制器本身建立RedirectView,則無法避免控制器意識到重定向正在發生的事實。這實際上並不是最理想的,而且會把事情過於緊密。控制器不應該真正關心如何處理響應。通常,它應該僅根據已注入其中的檢視名稱進行操作。
特殊的redirect:字首允許完成此操作。如果返回的檢視名稱具有字首redirect:,則UrlBasedViewResolver(以及所有子類)會將此識別為需要重定向的特殊指示。檢視名稱的其餘部分將被視為重定向URL。
與控制器返回RedirectView的效果相同,但現在控制器本身可以簡單地按照邏輯檢視名稱進行操作。邏輯檢視名稱(例如redirect:/myapp/some/resource)將相對於當前Servlet上下文重定向,而諸如redirect:http://myhost.com/some/arbitrary/path之類的名稱將重定向到絕對URL。
請注意,控制器處理器使用@ResponseStatus進行註釋,註解值優先於RedirectView設定的響應狀態。
3.4 forward:prefix
對於最終由UrlBasedViewResolver和子類解析的檢視名稱,也可以使用特殊的forward:字首。這會對檢視名稱的其餘部分建立一個InternalResourceView(最終會執行RequestDispatcher.forward()),該檢視名稱被視為URL。因此,此字首對於InternalResourceViewResolver和InternalResourceView(例如,對於JSP)沒有用。但是,當主要使用其他檢視技術時,字首可能會有所幫助,但仍希望強制Servlet/JSP引擎處理資源的轉發。(也可以連結多個檢視解析器。)
與redirect:字首一樣,如果將帶有forward:字首的檢視名稱注入控制器,則控制器不會檢測到在處理響應方面發生了什麼特殊情況。
4. ContentNegotiatingViewResolver
ContentNegotiatingViewResolver本身不解析檢視,而是委託給其他檢視解析器,選擇類似於客戶端請求的表示的檢視。客戶端從伺服器請求表示存在兩種策略:
- 通常通過在URI中使用不同的副檔名為每個資源使用不同的URI。例如,URI http://www.example.com/users/fred.pdf請求使用者fred的PDF表示,並且http://www.example.com/users/fred.xml請求XML表示。
- 使用相同的URI為客戶端定位資源,但設定Accept HTTP請求頭以列出它理解的媒體型別。例如,http://www.example.com/users/fred的HTTP請求,其Accept頭設定為application/pdf,請求使用者fred的PDF表示,而http://www.example.com/users/fred與Accept頭設定為text/xml請求XML表示。此策略稱為內容協商。
注:Accept頭的一個問題是無法在HTML中的Web瀏覽器中設定它。例如,在Firefox中,它固定為:
Accept: text/html,application/xhtml+xmlapplication/xml;q=0.9,*/*;q=0.8
因此,在開發基於瀏覽器的Web應用程式時,通常會看到為每個表示使用不同的URI。
為了支援資源的多個表示,Spring提供了ContentNegotiatingViewResolver來根據HTTP請求的副檔名或Accept頭來解析檢視。 ContentNegotiatingViewResolver不執行檢視解析本身,而是委託給通過ViewResolvers bean屬性指定的檢視解析器列表。
ContentNegotiatingViewResolver通過將請求媒體型別與每個ViewResolvers關聯的View支援的媒體型別(也稱為Content-Type)進行比較,選擇適當的View來處理請求。列表中具有相容Content-Type的第一個View將表示返回給客戶端。如果ViewResolver鏈無法提供相容檢視,則將查詢通過DefaultViews屬性指定的檢視列表。後一個選項適用於單個檢視,它可以呈現當前資源的適當表示,而不管邏輯檢視名稱如何。 Accept頭可以包括萬用字元,例如text/*,在這種情況下,Content-Type為text/xml的View是相容的匹配。
以下是ContentNegotiatingViewResolver的示例配置:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</list>
</property>
</bean>
<bean id="content" class="com.foo.samples.rest.SampleContentAtomView"/>
InternalResourceViewResolver處理檢視名稱和JSP頁面的轉換,而BeanNameViewResolver根據bean的名稱返回檢視。 在此示例中,content bean是繼承自AbstractAtomFeedView的類,後者返回Atom RSS提要。
在上面的配置中,如果使用.html副檔名發出請求,則檢視解析程式將查詢與text/html媒體型別匹配的檢視。 InternalResourceViewResolver為text/html提供匹配檢視。如果請求是使用副檔名.atom進行的,則檢視解析程式將查詢與application/atom+xml媒體型別匹配的檢視。此檢視由BeanNameViewResolver提供,如果返回的檢視名稱是content,則對映到SampleContentAtomView。如果請求是使用副檔名.json進行的,則無論檢視名稱如何,都將選擇DefaultViews列表中的MappingJackson2JsonView例項。或者,可以在沒有副檔名的情況下進行客戶端請求,但將Accept頭設定了首選媒體型別,那麼會對檢視進行相同請求解析。
注:如果沒有顯式配置`ContentNegotiatingViewResolver的ViewResolvers列表,它會自動使用應用程式上下文中定義的ViewResolvers。
相應的控制器程式碼返回一個Atom RSS提要,該提要用於http://localhost/content.atom或http://localhost/content形式的URI以及Accept頭為application/atom+xml,如下所示。
@Controller
public class ContentController {
private List<SampleContent> contentList = new ArrayList<SampleContent>();
@GetMapping("/content")
public ModelAndView getContent() {
ModelAndView mav = new ModelAndView();
mav.setViewName("content");
mav.addObject("sampleContentList", contentList);
return mav;
}
}