SpringMVC(9) - 解析視圖

參考:https://docs.spring.io/spring/docs/4.3.20.RELEASE/spring-framework-reference/htmlsingle/#mvc-viewresolver

 

所有MVC框架都爲Web應用程序提供了一種處理視圖的方法。 Spring提供了視圖解析器,可以在瀏覽器中呈現模型,而無需與特定的視圖技術聯繫起來。開箱即用,Spring允許使用JSP,Velocity模板和XSLT視圖。

對Spring處理視圖的方式很重要的兩個接口是ViewResolver和View。 ViewResolver提供視圖名稱和實際視圖之間的映射。 View接口解決了請求的準備問題,並將請求交給其中一種視圖技術。

 

1. 使用ViewResolver接口解析視圖
Spring Web MVC控制器中的所有處理器方法必須顯式地(例如,通過返回String、View或ModelAndView)或隱式地(基於約定)來解析爲邏輯視圖名稱。 Spring中的視圖由邏輯視圖名稱處理,並由視圖解析器解析。 Spring帶有相當多的視圖解析器。該表列出了其中大部分內容;下面是幾個例子。

ViewResolver Description

AbstractCachingViewResolver

緩存視圖的抽象視圖解析器。視圖通常需要準備才能使用;擴展此視圖解析器提供緩存。

XmlViewResolver

ViewResolver的實現,它接受XML中編寫的配置文件,該配置文件使用與Spring的XML bean工廠相同的DTD。默認配置文件是/WEB-INF/views.xml。

ResourceBundleViewResolver

ViewResolver的實現,它使用由bundle base name指定的ResourceBundle中定義的bean。通常,在屬性文件中定義捆綁包,該文件位於類路徑中。默認文件名是views.properties。

UrlBasedViewResolver

ViewResolver接口的簡單實現,它可以直接將邏輯視圖名稱解析爲URL,而無需顯式映射定義。如果邏輯名稱與視圖資源的名稱直接匹配,則這是合適的,而不需要任意映射。

InternalResourceViewResolver

UrlBasedViewResolver的便捷子類,支持InternalResourceView(實際上是Servlet和JSP)和子類,如JstlView和TilesView。可以使用setViewClass(..)爲此解析器生成的所有視圖指定視圖類。

VelocityViewResolver / FreeMarkerViewResolver

UrlBasedViewResolver的便捷子類,分別支持VelocityView(實際上是Velocity模板)或FreeMarkerView,以及它們的自定義子類。

ContentNegotiatingViewResolver

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

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章