SpingMVC 核心技術幫助文檔

  •                      

聲明:本篇文檔主要是用於參考幫助文檔,沒有實例,但幾乎包含了SpringMVC 4.2版本的全部核心技術,當前最新版本是4.3,4.2的版本已經經是很新的了,因此很是值得你們一讀,對於讀完這篇文檔感受還不錯的麻煩給個推薦,畢竟花了我一個星期時間才整理出來的,因此請多多支持 。  對於以爲篇幅長的文檔,建議你們使用快捷鍵crtl + F,搜索關鍵字查詢較爲方便.css

 

21.1  Spring Web MVC框架簡介

Spring的模型-視圖-控制器(MVC)框架是圍繞一個 DispatcherServlet 來設計的,這個Servlet會把請求分發給各個處理器,並支持可配置的處理器映射、視圖渲染、本地化、時區與主題渲染等,甚至還能支持文件上傳。處理器是你的應用中註解了 @Controller 和 @RequestMapping 的類和方法,Spring爲處理器方法提供了極其多樣靈活的配置。Spring 3.0之後提供了 @Controller 註解機制、 @PathVariable 註解以及一些其餘的特性,你能夠使用它們來進行RESTful web站點和應用的開發。html

對擴展開放」是Spring Web MVC框架一個重要的設計原則,而對於Spring的整個完整框架來講,其設計原則則是「對擴展開放,對修改閉合」。前端

Spring Web MVC核心類庫中的一些方法被定義爲final方法。做爲開發人員,你不能覆寫這些方法以定製其行爲。固然,不是說絕對不行,但請記住這條原則,絕大多數狀況下不是好的實踐。java

關於該原則的詳細解釋,你能夠參考Seth Ladd等人所著的「深刻解析Spring Web MVC與Web Flow」一書。相關信息在第117頁,「設計初探(A Look At Design)」一節。或者,你能夠參考:git

你沒法加強Spring MVC中的final方法,好比 AbstractController  .setSynchronizeOnSession() 方法等。請參考10.6.1 理解AOP代理一節,其中解釋了AOP代理的相關知識,論述了爲何你不能對final方法進行加強。github

在Spring Web MVC中,你能夠使用任何對象來做爲命令對象或表單返回對象,而無須實現一個框架相關的接口或基類。Spring的數據綁定很是靈活:好比,它會把數據類 型不匹配當成可由應用自行處理的運行時驗證錯誤,而非系統錯誤。你可能會爲了不非法的類型轉換在表單對象中使用字符串來存儲數據,但無類型的字符串沒法 描述業務數據的真正含義,而且你還須要把它們轉換成對應的業務對象類型。有了Spring的驗證機制,意味着你不再需這麼作了,而且直接將業務對象綁定 到表單對象上一般是更好的選擇。web

Spring的視圖解析也是設計得異常靈活。控制器通常負責準備一個Map模型、填充數據、返回一個合適的視圖名等,同時它也能夠直接將數據寫到響應流中。視圖名的解析高度靈活,支持多種配置,包括經過文件擴展名、Accept內容頭、bean、配置文件等的配置,甚至你還能夠本身實現一個視圖解析器 ViewResolver 。模型(MVC中的M,model)實際上是一個Map類型的接口,完全地把數據從視圖技術中抽象分離了出來。你能夠與基於模板的渲染技術直接整合,如JSP、Velocity和Freemarker等,或者你還能夠直接生成XML、JSON、Atom以及其餘多種類型的內容。Map模型會簡單地被轉換成合適的格式,好比JSP的請求屬性(attribute),一個Velocity模板的模型等。正則表達式

21.1.1 Spring Web MVC的新特性

Spring Web Flow算法

Spring Web Flow (SWF) 意在成爲web應用中的頁面流(page flow)管理中最好的解決方案。spring

SWF在Servlet環境和Portlet環境下集成了現有的框架,如Spring MVC和JSF等。若是你的業務流程有一個貫穿始終的模型,而非單純分立的請求,那麼SWF多是適合你的解決方案。

SWF 容許你將邏輯上的頁面流抽取成獨立可複用的模塊,這對於構建一個web應用的多個模塊是有益的。that guide the user through controlled navigations that drive business processes.

關於SWF的更多信息,請訪問Spring Web Flow的官網

Spring的web模塊支持許多web相關的特性:

  • 清晰的職責分離。每一個角色——控制器,驗證器,命令對象,表單對象,模型對象, DispatcherServlet ,處理器映射,視圖解析器,等等許多——的工做,均可以由相應的對象來完成。
  • 強大、直觀的框架和應用bean的配置。這種配置能力包括可以從不一樣的上下文中進行簡單的引用,好比在web控制器中引用業務對象、驗證器等。
  • 強大的適配能力、非侵入性和靈活性。Spring MVC支持你定義任意的控制器方法簽名,在特定的場景下你還能夠添加適合的註解(好比 @RequestParam、@RequestHeader、@PathVariable 等)
  • 可複用的業務代碼,使你遠離重複代碼。你能夠使用已有的業務對象做爲命令對象或表單對象,而不需讓它們去繼承一個框架提供的什麼基類。
  • 可定製的數據綁定和驗證。類型不匹配僅被認爲是應用級別的驗證錯誤,錯誤值、本地化日期、數字綁定等會被保存。你不須要再在表單對象使用全String字段,而後再手動將它們轉換成業務對象。
  • 可定製的處理器映射和視圖解析。處理器映射和視圖解析策略從簡單的基於URL配置,到精細專用的解析策略,Spring全都支持。在這一點上,Spring比一些依賴於特定技術的web框架要更加靈活。
  • 靈活的模型傳遞。Spring使用一個名稱/值對的Map來作模型,這使得模型很容易集成、傳遞給任何類型的視圖技術。
  • 可定製的本地化信息、時區和主題解析。支持用/不用Spring標籤庫的JSP技術,支持JSTL,支持無需額外配置的Velocity模板,等等。;
  • 一個簡單但功能強大的JSP標籤庫,一般稱爲Spring標籤庫,它提供了諸如數據綁定、主題支持等一些特性的支持。這些定製的標籤爲標記(markup)你的代碼提供了最大程度的靈活性。關於標籤庫描述符(descriptor)的更多信息,請參考附錄第42章 Spring JSP標籤庫
  • 一個Spring 2.0開始引入的JSP表單標籤庫。它讓你在JSP頁面中編寫表單簡單許多。關於標籤庫描述符(descriptor)的更多信息,請參考附錄 第43章 Spring表單的JSP標籤庫
  • 新增生命週期僅綁定到當前HTTP請求或HTTP會話的Bean類型。嚴格來講,這不是Spring MVC自身的特性,而是Spring MVC使用的上下文容器 WebApplicationContext 所提供的特性。這些bean的scope在6.5.4 請求、會話及全局會話scope一節有詳細描述。

21.1.2 容許其餘MVC實現

有些項目可能更傾向於使用非Spring的MVC框架。 許多團隊但願仍然使用現有的技術棧,好比JSF等,這樣他們掌握的技能和工具依然能發揮做用。

若是你確實不想使用Spring的Web MVC,但又但願能從Spring提供的一些解決方案中受益,那麼將你所使用的框架和Spring進行集成也很容易。只須要在 ContextLoaderListener 中啓動一個Spring的根應用上下文(root application context),而後你就能夠在任何action對象中經過其 ServletContext 屬性(或經過Spring對應的helper方法)取得。不須要任何侵入性的插件,所以不須要複雜的集成。從應用層的視角來看,你只是將Spring當成依賴庫使用,而且將它的根應用上下文實例做爲應用進入點。

即 使不用Spring的Web MVC框架,你配置的其餘Spring的 bean 和服務也都能很方便地取得。在這種場景下,Spring與其餘web框架的使用不衝突。Spring只是 在許多問題上提出了其餘純web MVC框架不曾提出過的解決方案,好比 bean 的配置、數據存取、事務處理等,僅此而已。所以,若是你只是想使用Spring的一部分特性來加強你的應 用,好比Spring提供的JDBC/Hibernate事務抽象等,那麼你能夠將Spring做爲一箇中間層和/或數據存取層來使用。

21.2 DispatcherServlet

Spring MVC框架,與其餘不少web的MVC框架同樣:請求驅動;全部設計都圍繞着一箇中央Servlet來展開,它負責把全部請求分發到控制器;同時提供其餘web應用開發所須要的功能。不過Spring的中央處理器, DispatcherServlet ,能作的比這更多。它與Spring IoC容器作到了無縫集成,這意味着,Spring提供的任何特性,在Spring MVC中你均可以使用。

下圖展現了Spring Web MVC的 DispatcherServlet 處理請求的工做流。熟悉設計模式的朋友會發現, DispatcherServlet 應用的其實就是一個「前端控制器」的設計模式(其餘不少優秀的web框架也都使用了這個設計模式)。

 DispatcherServlet 其實就是個 Servlet (它繼承自 HttpServlet 基類),一樣也須要在你web應用的web.xml配置文件下聲明。你須要在 web.xml 文件中把你但願 DispatcherServlet 處理的請求映射到對應的URL上去。這就是標準的Java EE Servlet配置;下面的代碼就展現了對 DispatcherServlet 和路徑映射的聲明:

<web-app>
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>/example/*</url-pattern>
    </servlet-mapping>
</web-app>

 

In the preceding example, all requests starting with /example will be handled by the DispatcherServlet instance named example. In a Servlet 3.0+ environment, you also have the option of configuring the Servlet container programmatically. Below is the code based equivalent of the above web.xml example:

在上面的例子中,全部路徑以 /example 開頭的請求都會被名字爲example的 DispatcherServlet 處理。在Servlet 3.0+的環境下,你還能夠用編程的方式配置Servlet容器。下面是一段這種基於代碼配置的例子,它與上面定義的 web.xml 配置文件是等效的。

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/example/*");
    }

}

 

 WebApplicationInitializer 是Spring MVC提供的一個接口,它會查找你全部基於代碼的配置,並應用它們來初始化Servlet 3版本以上的web容器。它有一個抽象的實現 AbstractDispatcherServletInitializer ,用以簡化 DispatcherServlet 的註冊工做:你只須要指定其servlet映射(mapping)便可。若想了解更多細節,能夠參考基於代碼的Servlet容器初始化一節。

上面只是配置Spring Web MVC的第一步,接下來你須要配置其餘的一些bean(除了 DispatcherServlet 之外的其餘bean),它們也會被Spring Web MVC框架使用到。

6.15 應用上下文ApplicationContext的其餘做用)一節中咱們聊到,Spring中的 ApplicationContext 實例是能夠有範圍(scope)的。在Spring MVC中,每一個 DispatcherServlet 都持有一個本身的上下文對象 WebApplicationContext ,它又繼承了根(root) WebApplicationContext 對象中已經定義的全部bean。這些繼承的bean能夠在具體的Servlet實例中被重載,在每一個Servlet實例中你也能夠定義其scope下的新bean。

 DispatcherServlet 的初始化過程當中,Spring MVC會在你web應用的WEB-INF目錄下查找一個名爲 [servlet-name]-servlet.xml 的配置文件,並建立其中所定義的 bean 。若是在全局上下文中存在相同名字的 bean ,則它們將被新定義的同名bean覆蓋。

看看下面這個 DispatcherServlet 的 Servlet 配置(定義於 web.xml 文件中):

<web-app>
    <servlet>
        <servlet-name>golfing</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>golfing</servlet-name>
        <url-pattern>/golfing/*</url-pattern>
    </servlet-mapping>
</web-app>

 

有了以上的Servlet配置文件,你還須要在應用中的/WEB-INF/路徑下建立一個 golfing-servlet.xml 文件,在該文件中定義全部Spring MVC相關的組件(好比bean等)。你能夠經過servlet初始化參數爲這個配置文件指定其餘的路徑(見下面的例子):

當你的應用中只須要一個 DispatcherServlet 時,只配置一個根 contex t對象也是可行的。

要配置一個惟一的根 context 對象,能夠經過在 servlet 初始化參數中配置一個空的 contextConfigLocation 來作到,以下所示:

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
</web-app>

 

 WebApplicationContext 繼承自 ApplicationContext ,它提供了一些web應用常常須要用到的特性。它與普通的 ApplicationContext 不一樣的地方在於,它支持主題的解析(詳見21.9 主題Themes一小節),而且知道它關聯到的是哪一個servlet(它持有一個該 ServletContext 的引用)。 WebApplicationContext 被綁定在 ServletContext 中。若是須要獲取它,你能夠經過 RequestContextUtils 工具類中的靜態方法來拿到這個web應用的上下文 WebApplicationContext 。

21.2.1 WebApplicationContext中特殊的bean類型

Spring的 DispatcherServlet 使用了特殊的 bean 來處理請求、渲染視圖等,這些特定的bean是Spring MVC框架的一部分。若是你想指定使用哪一個特定的 bean ,你能夠在web應用上下文 WebApplicationContext 中簡單地配置它們。固然這只是可選的,Spring MVC維護了一個默認的 bean 列表,若是你沒有進行特別的配置,框架將會使用默認的bean。下一小節會介紹更多的細節,這裏,咱們將先快速地看一下, DispatcherServlet 都依賴於哪些特殊的bean來進行它的初始化。

bean的類型 做用
HandlerMapping 處理器映射。它會根據某些規則將進入容器的請求映射到具體的處理器以及一系列前處理器和後處理器(即處理器攔截器)上。具體的規則視 HandlerMapping 類的實現不一樣而有所不一樣。其最經常使用的一個實現支持你在控制器上添加註解,配置請求路徑。固然,也存在其餘的實現。
HandlerAdapter 處理器適配器。拿到請求所對應的處理器後,適配器將負責去調用該處理器,這使得 DispatcherServlet 無需關心具體的調用細節。比方說,要調用的是一個基於註解配置的控制器,那麼調用前還須要從許多註解中解析出一些相應的信息。所以, HandlerAdapter 的主要任務就是對 DispatcherServlet 屏蔽這些具體的細節。
HandlerExceptionResolver 處理器異常解析器。它負責將捕獲的異常映射到不一樣的視圖上去,此外還支持更復雜的異常處理代碼。
ViewResolver 視圖解析器。它負責將一個表明邏輯視圖名的字符串(String)映射到實際的視圖類型View上。
LocaleResolver & LocaleContextResolver 地區解析器 和 地區上下文解析器。它們負責解析客戶端所在的地區信息甚至時區信息,爲國際化的視圖定製提供了支持。
ThemeResolver 主題解析器。它負責解析你web應用中可用的主題,好比,提供一些個性化定製的佈局等。
MultipartResolver 解析multi-part的傳輸請求,好比支持經過HTML表單進行的文件上傳等。
FlashMapManager FlashMap管理器。它可以存儲並取回兩次請求之間的 FlashMap 對象。後者可用於在請求之間傳遞數據,一般是在請求重定向的情境下使用。

21.2.2 默認的DispatcherServlet配置

上一小節講到, DispatcherServlet 維護了一個列表,其中保存了其所依賴的全部 bean 的默認實現。這個列表保存在包 org.springframework.web.servlet 下的 DispatcherServlet.properties 文件中。

這些特殊的bean都有一些基本的默認行爲。或早或晚,你可能須要對它們提供的一些默認配置進行定製。好比說,一般你須要配置 InternalResourceViewResolver 類提供的 prefix 屬性,使其指向視圖文件所在的目錄。  這裏須要理解的一個事情是,一旦你在web應用上下文 WebApplicationContext 中配置了某個特殊 bean 之後(好比 InternalResourceViewResolver ),實際上你也覆寫了該 bean 的默認實現。比方說,若是你配置了 InternalResourceViewResolver ,那麼框架就不會再使用 beanViewResolver 的默認實現。

21.16節 Spring MVC的配置中, 咱們介紹了其餘配置Spring MVC的方式,好比經過Java編程配置或者經過MVC XML命名空間進行配置。它們爲配置一個Spring MVC應用提供了簡易的開始方式,也不須要你對框架實現細節有太多瞭解。固然,不管你選用何種方式開始配置,本節所介紹的一些概念都是基礎且普適的,它們 對你後續的學習都應有所助益。

21.2.3 DispatcherServlet的處理流程

配置好 DispatcherServlet 之後,開始有請求會通過這個 DispatcherServlet 。此時, DispatcherServlet 會依照如下的次序對請求進行處理:

  • 首先,搜索應用的上下文對象 WebApplicationContext 並把它做爲一個屬性( attribute )綁定到該請求上,以便控制器和其餘組件可以使用它。屬性的鍵名默認爲 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 
  • 將地區(locale)解析器綁定到請求上,以便其餘組件在處理請求(渲染視圖、準備數據等)時能夠獲取區域相關的信息。若是你的應用不須要解析區域相關的信息,忽略它便可
  • 將主題(theme)解析器綁定到請求上,以便其餘組件(好比視圖等)可以瞭解要渲染哪一個主題文件。一樣,若是你不須要使用主題相關的特性,忽略它便可
  • 若是你配置了multipart文件處理器,那麼框架將查找該文件是否是multipart(分爲多個部分連續上傳)的。如果,則將該請求包裝成一個 MultipartHttpServletReques t對象,以便處理鏈中的其餘組件對它作進一步的處理。關於Spring對multipart文件傳輸處理的支持,讀者能夠參考21.10 Spring的multipart(文件上傳)支持一小節
  • 爲該請求查找一個合適的處理器。若是能夠找到對應的處理器,則與該處理器關聯的整條執行鏈(前處理器、後處理器、控制器等)都會被執行,以完成相應模型的準備或視圖的渲染
  • 若是處理器返回的是一個模型(model),那麼框架將渲染相應的視圖。若沒有返回任何模型(多是由於先後的處理器出於某些緣由攔截了請求等,好比,安全問題),則框架不會渲染任何視圖,此時認爲對請求的處理可能已經由處理鏈完成了

若是在處理請求的過程當中拋出了異常,那麼上下文 WebApplicationContext 對象中所定義的異常處理器將會負責捕獲這些異常。經過配置你本身的異常處理器,你能夠定製本身處理異常的方式。

Spring的 DispatcherServlet 也容許處理器返回一個Servlet API規範中定義的 最後修改時間戳(last-modification-date) 值。決定請求最後修改時間的方式很直接: DispatcherServlet 會先查找合適的處理器映射來找到請求對應的處理器,而後檢測它是否實現了 LastModified 接口。如果,則調用接口的 long getLastModified(request) 方法,並將該返回值返回給客戶端。

你能夠定製 DispatcherServlet 的配置,具體的作法,是在 web.xml 文件中,Servlet的聲明元素上添加一些Servlet的初始化參數(經過 init-param 元素)。該元素可選的參數列表以下:

可選參數 解釋
 contextClass  任意實現了 WebApplicationContext 接口的類。這個類會初始化該 servlet 所須要用到的上下文對象。默認狀況下,框架會使用一個 XmlWebApplicationContext 對象。
 contextConfigLocation  一個指定了上下文配置文件路徑的字符串,該值會被傳入給 contextClass 所指定的上下文實例對象。該字符串內能夠包含多個字符串,字符串之間以逗號分隔,以此支持你進行多個上下文的配置。在多個上下文中重複定義的 bean ,以最後加載的bean定義爲準
 namespace   WebApplicationContext 的命名空間。默認是 [servlet-name]-servlet 

21.3 控制器(Controller)的實現

...Spring implements a controller in a very abstract way, which enables you to create a wide variety of controllers.

控制器做爲應用程序邏輯的處理入口,它會負責去調用你已經實現的一些服務。一般,一個控制器會接收並解析用戶的請求,而後把它轉換成一個模型交給視圖,由視圖渲染出頁面最終呈現給用戶。Spring對控制器的定義很是寬鬆,這意味着你在實現控制器時很是自由。

Spring 2.5之後引入了基於註解的編程模型,你能夠在你的控制器實現上添加 @RequestMapping、@RequestParam、@ModelAttribute 等 註解。註解特性既支持基於Servlet的MVC,也可支持基於Portlet的MVC。經過此種方式實現的控制器既無需繼承某個特定的基類,也無需實現 某些特定的接口。並且,它一般也不會直接依賴於Servlet或Portlet的API來進行編程,不過你仍然能夠很容易地獲取Servlet或 Portlet相關的變量、特性和設施等。

Spring項目的官方Github上你能夠找到許多項目,它們對本節所述之後的註解支持提供了進一步加強,好比說MvcShowcase,MvcAjax,MvcBasic,PetClinic,PetCare等。

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

 

你能夠看到, @Controller 註解和 @RequestMapping 註解支持多樣的方法名和方法簽名。在上面這個例子中,方法接受一個Model類型的參數並返回一個字符串String類型的視圖名。但事實上,方法所支持的參數和返回值有很是多的選擇,這個咱們在本小節的後面部分會說起。 @Controller 和 @RequestMapping 及其餘的一些註解,共同構成了Spring MVC框架的基本實現。本節將詳細地介紹這些註解,以及它們在一個Servlet環境下最常被使用到的一些場景。

21.3.1 使用@Controller註解定義一個控制器

[Original] The @Controller annotation indicates that a particular class serves the role of a controller. Spring does not require you to extend any controller base class or reference the Servlet API. However, you can still reference Servlet-specific features if you need to.

 @Controller 註解代表了一個類是做爲控制器的角色而存在的。Spring不要求你去繼承任何控制器基類,也不要求你去實現Servlet的那套API。固然,若是你須要的話也能夠去使用任何與Servlet相關的特性和設施。

[Original] The @Controller annotation acts as a stereotype for the annotated class, indicating its role. The dispatcher scans such annotated classes for mapped methods and detects @RequestMapping annotations (see the next section).

 @Controller 註解能夠認爲是被標註類的原型(stereotype),代表了這個類所承擔的角色。分派器( DispatcherServlet )會掃描全部註解了 @Controller 的類,檢測其中經過 @RequestMapping 註解配置的方法(詳見下一小節)。

[Original] You can define annotated controller beans explicitly, using a standard Spring bean definition in the dispatcher’s context. However, the @Controller stereotype also allows for autodetection, aligned with Spring general support for detecting component classes in the classpath and auto-registering bean definitions for them.

固然,你也能夠不使用 @Controller 註解而顯式地去定義被註解的bean,這點經過標準的Spring bean的定義方式,在dispather的上下文屬性下配置便可作到。可是 @Controller 原型是能夠被框架自動檢測的,Spring支持classpath路徑下組件類的自動檢測,以及對已定義bean的自動註冊。

[Original] To enable autodetection of such annotated controllers, you add component scanning to your configuration. Use the spring-context schema as shown in the following XML snippet:

你須要在配置中加入組件掃描的配置代碼來開啓框架對註解控制器的自動檢測。請使用下面XML代碼所示的spring-context schema:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    <!-- ... -->

</beans>

 

 

21.3.2 使用@RequestMapping註解映射請求路徑

你能夠使用 @RequestMapping 註解來將請求URL,如 /appointments 等, 映射到整個類上或某個特定的處理器方法上。通常來講,類級別的註解負責將一個特定(或符合某種模式)的請求路徑映射到一個控制器上,同時經過方法級別的注 解來細化映射,即根據特定的HTTP請求方法(「GET」「POST」方法等)、HTTP請求中是否攜帶特定參數等條件,將請求映射到匹配的方法上。

下面這段代碼示例來自Petcare,它展現了在Spring MVC中如何在控制器上使用 @RequestMapping 註解:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(path = "/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(path = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

 

在上面的示例中,許多地方都使用到了 @RequestMapping 註解。第一次使用點是做用於類級別的,它指示了全部 /appointments 開頭的路徑都會被映射到控制器下。 get() 方法上的 @RequestMapping 註解對請求路徑進行了進一步細化:它僅接受GET方法的請求。這樣,一個請求路徑爲 /appointments 、HTTP方法爲GET的請求,將會最終進入到這個方法被處理。 add() 方法也作了相似的細化,而 getNewForm() 方法則同時註解了可以接受的請求的HTTP方法和路徑。這種狀況下,一個路徑爲 appointments/new 、HTTP方法爲GET的請求將會被這個方法所處理。

 getForDay() 方法則展現了使用 @RequestMapping 註解的另外一個技巧:URI模板。(關於URI模板,請見下小節

類級別的 @RequestMapping 註解並非必須的。不配置的話則全部的路徑都是絕對路徑,而非相對路徑。如下的代碼示例來自PetClinic,它展現了一個具備多個處理器方法的控制器:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }
}

 

以上代碼沒有指定請求必須是GET方法仍是 PUT/POST 或其餘方法, @RequestMapping 註解默認會映射全部的HTTP請求方法。若是僅想接收某種請求方法,請在註解中指定之 @RequestMapping(method=GET) 以縮小範圍。

 @Controller 和麪向切面(AOP)代理

有時,咱們但願在運行時使用AOP代理來裝飾控制器,好比當你直接在控制器上使用 @Transactional 註解時。這種狀況下,咱們推薦使用類級別(在控制器上使用)的代理方式。這通常是代理控制器的默認作法。若是控制器必須實現一些接口,而該接口又不支持Spring Context的回調(好比 InitializingBean, *Aware 等接口),那要配置類級別的代理就必須手動配置了。好比,原來的配置文件 <tx:annotation-driven/> 須要顯式配置爲 <tx:annotation-driven proxy-target-class="true"/> 。

Spring MVC 3.1中新增支持 @RequestMapping 的一些類

They are recommended for use and even required to take advantage of new features in Spring MVC 3.1 and going forward.

Spring 3.1中新增了一組類用以加強 @RequestMapping ,分別是 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 。 咱們推薦你用一用。有部分Spring MVC 3.1以後新增的特性,這兩個註解甚至是必須的。在MVC命名空間和MVC Java編程配置方式下,這組類及其新特性默認是開啓的。但若你使用其餘配置方式,則該特性必須手動配置才能使用。本小節將簡要介紹一下,新類相比以前的 一些重要變化。

在Spring 3.1以前,框架會在兩個不一樣的階段分別檢查類級別和方法級別的請求映射——首先, DefaultAnnotationHanlderMapping 會先在類級別上選中一個控制器,而後再經過 AnnotationMethodHandlerAdapter 定位到具體要調用的方法。

[Original] With the new support classes in Spring 3.1, the RequestMappingHandlerMapping is the only place where a decision is made about which method should process the request. Think of controller methods as a collection of unique endpoints with mappings for each method derived from type and method-level @RequestMapping information.

如今有了Spring 3.1後引入的這組新類, RequestMappingHandlerMapping 成爲了這兩個決策實際發生的惟一一個地方。你能夠把控制器中的一系列處理方法當成是一系列獨立的服務節點,每一個從類級別和方法級別的 @RequestMapping 註解中獲取到足夠請求1路徑映射信息。

[Original] This enables some new possibilities. For once a HandlerInterceptor or a HandlerExceptionResolver can now expect the Object-based handler to be a HandlerMethod, which allows them to examine the exact method, its parameters and associated annotations. The processing for a URL no longer needs to be split across different controllers.

這種新的處理方式帶來了新的可能性。以前的 HandlerInterceptor 或 HandlerExceptionResolver 如今能夠肯定拿到的這個處理器確定是一個 HandlerMethod 類型,所以它可以精確地瞭解這個方法的全部信息,包括它的參數、應用於其上的註解等。這樣,內部對於一個URL的處理流程不再須要分隔到不一樣的控制器裏面去執行了。

[Original] There are also several things no longer possible: [Original] Select a controller first with a SimpleUrlHandlerMapping or BeanNameUrlHandlerMapping and then narrow the method based on @RequestMapping annotations. [Original] Rely on method names as a fall-back mechanism to disambiguate between two @RequestMapping methods that don’t have an explicit path mapping URL path but otherwise match equally, e.g. by HTTP method. In the new support classes @RequestMapping methods have to be mapped uniquely. [Original] * Have a single default method (without an explicit path mapping) with which requests are processed if no other controller method matches more concretely. In the new support classes if a matching method is not found a 404 error is raised.

同時,也有其餘的一些變化,好比有些事情就無法這麼玩兒了:

  • 先經過 SimpleUrlHandlerMapping 或 BeanNameUrlHandlerMapping 來拿到負責處理請求的控制器,而後經過 @RequestMapping 註解配置的信息來定位到具體的處理方法;
  • 依靠方法名稱來做爲選擇處理方法的標準。好比說,兩個註解了 @RequestMapping 的方法除了方法名稱擁有徹底相同的URL映射和HTTP請求方法。在新版本下, @RequestMapping 註解的方法必須具備惟一的請求映射;
  • 定義一個默認方法(即沒有聲明路徑映射),在請求路徑沒法被映射到控制器下更精確的方法上去時,爲該請求提供默認處理。在新版本中,若是沒法爲一個請求找到合適的處理方法,那麼一個404錯誤將被拋出;

[Original] The above features are still supported with the existing support classes. However to take advantage of new Spring MVC 3.1 features you’ll need to use the new support classes.

若是使用原來的類,以上的功能仍是能夠作到。可是,若是要享受Spring MVC 3.1版本帶來的方便特性,你就須要去使用新的類。

[Original] ## URI Template Patterns

URI模板

[Original] URI templates can be used for convenient access to selected parts of a URL in a @RequestMapping method.

URI模板能夠爲快速訪問@RequestMapping中指定的URL的一個特定的部分提供很大的便利。

[Original] A URI Template is a URI-like string, containing one or more variable names. When you substitute values for these variables, the template becomes a URI. The proposed RFC for URI Templates defines how a URI is parameterized. For example, the URI Template http://www.example.com/users/{userId} contains the variable userId. Assigning the value fred to the variable yields http://www.example.com/users/fred.

URI模板是一個相似於URI的字符串,只不過其中包含了一個或多個的變量名。當你使用實際的值去填充這些變量名的時候,模板就退化成了一個URI。在URI模板的RFC提議中定義了一個URI是如何進行參數化的。好比說,一個這個URI模板 http://www.example.com/users/{userId} 就包含了一個變量名userId。將值fred賦給這個變量名後,它就變成了一個URI: http://www.example.com/users/fred 。

[Original] In Spring MVC you can use the @PathVariable annotation on a method argument to bind it to the value of a URI template variable:

在Spring MVC中你能夠在方法參數上使用 @PathVariable 註解,將其與URI模板中的參數綁定起來:

@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

 

[Original] The URI Template " /owners/{ownerId} " specifies the variable name ownerId. When the controller handles this request, the value of ownerId is set to the value found in the appropriate part of the URI. For example, when a request comes in for /owners/fred, the value of ownerId is fred.

URI模板" /owners/{ownerId} "指定了一個變量,名爲ownerId。當控制器處理這個請求的時候,ownerId的值就會被URI模板中對應部分的值所填充。好比說,若是請求的URI是

 /owners/fred ,此時變量ownerId的值就是fred. `

爲了處理 @PathVariables 註解,Spring MVC必須經過變量名來找到URI模板中相對應的變量。你能夠在註解中直接聲明:

@RequestMapping(path="/owners/{ownerId}}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // 具體的方法代碼…
}

 

或者,若是URI模板中的變量名與方法的參數名是相同的,則你能夠沒必要再指定一次。只要你在編譯的時候留下debug信息,Spring MVC就能夠自動匹配URL模板中與方法參數名相同的變量名。

@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    // 具體的方法代碼…
}

 

[Original] A method can have any number of @PathVariable annotations:

一個方法能夠擁有任意數量的 @PathVariable 註解:

@RequestMapping(path="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

 

[Original] When a  @PathVariable  annotation is used on a  Map<String, String>  argument, the map is populated with all URI template variables.

當 @PathVariable 註解被應用於 Map<String, String> 類型的參數上時,框架會使用全部URI模板變量來填充這個map。

[Original] A URI template can be assembled from type and path level @RequestMapping annotations. As a result the findPet() method can be invoked with a URL such as /owners/42/pets/21.

URI模板能夠從類級別和方法級別的  @RequestMapping  註解獲取數據。所以,像這樣的 findPet() 方法能夠被相似於 /owners/42/pets/21 這樣的URL路由並調用到:

_@Controller_
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping("/pets/{petId}")
    public void findPet(_@PathVariable_ String ownerId, _@PathVariable_ String petId, Model model) {
        // 方法實現體這裏忽略
    }

}

 

[Original] A  @PathVariable  argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a  TypeMismatchException  if it fails to do so. You can also register support for parsing additional data types. See the section called "Method Parameters And Type Conversion" and the section called "Customizing WebDataBinder initialization".

 @PathVariable 能夠被應用於全部 簡單類型 的參數上,好比int、long、Date等類型。Spring會自動地幫你把參數轉化成合適的類型,若是轉換失敗,就拋出一個 TypeMismatchException 。若是你須要處理其餘數據類型的轉換,也能夠註冊本身的類。若須要更詳細的信息能夠參考「方法參數與類型轉換」一節「定製WebDataBinder初始化過程」一節

帶正則表達式的URI模板

[Original] Sometimes you need more precision in defining URI template variables. Consider the URL /spring-web/spring-web-3.0.5.jar ". How do you break it down into multiple parts?

有時候你可能須要更準確地描述一個URI模板的變量,好比說這個URL:/spring-web/spring-web-3.0.5.jar 。你要怎麼把它分解成幾個有意義的部分呢?

[Original] The  @RequestMapping  annotation supports the use of regular expressions in URI template variables. The syntax is  {varName:regex}  where the first part defines the variable name and the second - the regular expression.For example:

@RequestMapping註解支持你在URI模板變量中使用正則表達式。語法是 {varName:regex} ,其中第一部分定義了變量名,第二部分就是你所要應用的正則表達式。好比下面的代碼樣例:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
    public void handle(@PathVariable String version, @PathVariable String extension) {
        // 代碼部分省略...
    }
}

 

Path Patterns(很差翻,容易掉韻味)

[Original] In addition to URI templates, the  @RequestMapping  annotation also supports Ant-style path patterns (for example,  /myPath/*.do ). A combination of URI template variables and Ant-style globs is also supported (e.g.  /owners/*/pets/{petId} ).

除了URI模板外, @RequestMapping 註解還支持Ant風格的路徑模式(如 /myPath/*.do 等)。不只如此,還能夠把URI模板變量和Ant風格的glob組合起來使用(好比 /owners/*/pets/{petId} 這樣的用法等)。

路徑樣式的匹配(Path Pattern Comparison)

[Original] When a URL matches multiple patterns, a sort is used to find the most specific match.

當一個URL同時匹配多個模板(pattern)時,咱們將須要一個算法來決定其中最匹配的一個。

[Original] A pattern with a lower count of URI variables and wild cards is considered more specific. For example  /hotels/{hotel}/*  has 1 URI variable and 1 wild card and is considered more specific than  /hotels/{hotel}/**  which as 1 URI variable and 2 wild cards.

URI模板變量的數目和通配符數量的總和最少的那個路徑模板更準確。舉個例子, /hotels/{hotel}/* 這個路徑擁有一個URI變量和一個通配符,而 /hotels/{hotel}/** 這個路徑則擁有一個URI變量和兩個通配符,所以,咱們認爲前者是更準確的路徑模板。

[Original] If two patterns have the same count, the one that is longer is considered more specific. For example  /foo/bar*  is longer and considered more specific than  /foo/* .

若是兩個模板的URI模板數量和通配符數量總和一致,則路徑更長的那個模板更準確。舉個例子,/foo/bar*就被認爲比/foo/*更準確,由於前者的路徑更長。

[Original] When two patterns have the same count and length, the pattern with fewer wild cards is considered more specific. For example /hotels/{hotel} is more specific than /hotels/*.

若是兩個模板的數量和長度均一致,則那個具備更少通配符的模板是更加準確的。好比,/hotels/{hotel}就比/hotels/*更精確。

[Original] There are also some additional special rules:

除此以外,還有一些其餘的規則:

[Original] The default mapping pattern `/*is less specific than any other pattern. For example/api/{a}/{b}/{c}` is more specific.

[Original] A prefix pattern such as `/public/*is less specific than any other pattern that doesn't contain double wildcards. For example/public/path3/{a}/{b}/{c}` is more specific.

  • 默認的通配模式/**比其餘全部的模式都更「不許確」。比方說,/api/{a}/{b}/{c}就比默認的通配模式/**要更準確
  • 前綴通配(好比/public/**)被認爲比其餘任何不包括雙通配符的模式更不許確。好比說,/public/path3/{a}/{b}/{c}就比/public/**更準確

[Original] For the full details see AntPatternComparator in AntPathMatcher. Note that the PathMatcher can be customized (see Section 21.16.11, "Path Matching" in the section on configuring Spring MVC).

更多的細節請參考這兩個類: AntPatternComparator 和 AntPathMatcher 。值得一提的是, PathMatcher 類是能夠配置的(見「配置Spring MVC」一節中的21.16.11 路徑的匹配一節)。

帶佔位符的路徑模式(path patterns)

[Original] Patterns in @RequestMapping annotations support ${…} placeholders against local properties and/or system properties and environment variables. This may be useful in cases where the path a controller is mapped to may need to be customized through configuration. For more information on placeholders, see the javadocs of the PropertyPlaceholderConfigurer class.

 @RequestMapping 註解支持在路徑中使用佔位符,以取得一些本地配置、系統配置、環境變量等。這個特性有時頗有用,好比說控制器的映射路徑須要經過配置來定製的場景。若是想了解更多關於佔位符的信息,能夠參考 PropertyPlaceholderConfigurer 這個類的文檔。

Suffix Pattern Matching

後綴模式匹配

[Original] By default Spring MVC performs .* " suffix pattern matching so that a controller mapped to /person is also implicitly mapped to /person.*. This makes it easy to request different representations of a resource through the URL path (e.g.  /person.pdf, /person.xml ).

Spring MVC默認採用".*"的後綴模式匹配來進行路徑匹配,所以,一個映射到/person路徑的控制器也會隱式地被映射到 /person.* 。這使得經過URL來請求同一資源文件的不一樣格式變得更簡單(好比 /person.pdf,/person.xml )。

[Original] Suffix pattern matching can be turned off or restricted to a set of path extensions explicitly registered for content negotiation purposes. This is generally recommended to minimize ambiguity with common request mappings such as  /person/{id}  where a dot might not represent a file extension, e.g.  /person/joe@email.com  vs  /person/joe@email.com.json ). Furthermore as explained in the note below suffix pattern matching as well as content negotiation may be used in some circumstances to attempt malicious attacks and there are good reasons to restrict them meaningfully.

你能夠關閉默認的後綴模式匹配,或者顯式地將路徑後綴限定到一些特定格式上for content negotiation purpose。咱們推薦這樣作,這樣能夠減小映射請求時能夠帶來的一些二義性,好比請求如下路徑 /person/{id} 時,路徑中的點號後面帶的可能不是描述內容格式,好比/person/joe@email.com vs /person/joe@email.com.json。並且正以下面立刻要提到的,後綴模式通配以及內容協商有時可能會被黑客用來進行攻擊,所以,對後綴通配進行有意義的限定是有好處的。

[Original] See Section 21.16.11, "Path Matching" for suffix pattern matching configuration and also Section 21.16.6, "Content Negotiation" for content negotiation configuration.

關於後綴模式匹配的配置問題,能夠參考第21.16.11小節 "路徑匹配";關於內容協商的配置問題,能夠參考第21.16.6小節 "內容協商"的內容。

後綴模式匹配與RFD

[Original] Reflected file download (RFD) attack was first described in a paper by Trustwave in 2014. The attack is similar to XSS in that it relies on input (e.g. query parameter, URI variable) being reflected in the response. However instead of inserting JavaScript into HTML, an RFD attack relies on the browser switching to perform a download and treating the response as an executable script if double-clicked based on the file extension (e.g. .bat, .cmd).

RFD(Reflected file download)攻擊最早是2014年在Trustwave的一篇論文中 被提出的。它與XSS攻擊有些類似,由於這種攻擊方式也依賴於某些特徵,即須要你的輸入(好比查詢參數,URI變量等)等也在輸出(response)中 以某種形式出現。不一樣的是,RFD攻擊並非經過在HTML中寫入JavaScript代碼進行,而是依賴於瀏覽器來跳轉到下載頁面,並把特定格式(比 如.bat,.cmd等)的response當成是可執行腳本,雙擊它就會執行。

[Original] In Spring MVC  @ResponseBody  and  ResponseEntity  methods are at risk because they can render different content types which clients can request including via URL path extensions. Note however that neither disabling suffix pattern matching nor disabling the use of path extensions for content negotiation purposes alone are effective at preventing RFD attacks.

Spring MVC的 @ResponseBody 和 ResponseEntity 方法是有風險的,由於它們會根據客戶的請求——包括URL的路徑後綴,來渲染不一樣的內容類型。所以,禁用後綴模式匹配或者禁用僅爲內容協商開啓的路徑文件後綴名攜帶,都是防範RFD攻擊的有效方式。

[Original] For comprehensive protection against RFD, prior to rendering the response body Spring MVC adds a  Content-Disposition:inline;filename=f.txt  header to suggest a fixed and safe download file filename. This is done only if the URL path contains a file extension that is neither whitelisted nor explicitly registered for content negotiation purposes. However it may potentially have side effects when URLs are typed directly into a browser.

若要開啓對RFD更高級的保護模式,能夠在Spring MVC渲染開始請求正文以前,在請求頭中增長一行配置 Content-Disposition:inline;filename=f.txt ,指定固定的下載文件的文件名。這僅在URL路徑中包含了一個文件符合如下特徵的拓展名時適用:該擴展名既不在信任列表(白名單)中,也沒有被顯式地被註冊於內容協商時使用。而且這種作法還能夠有一些反作用,好比,當URL是經過瀏覽器手動輸入的時候。

[Original] Many common path extensions are whitelisted by default. Furthermore REST API calls are typically not meant to be used as URLs directly in browsers. Nevertheless applications that use custom  HttpMessageConverter  implementations can explicitly register file extensions for content negotiation and the Content-Disposition header will not be added for such extensions. See Section 21.16.6, "Content Negotiation".

不少經常使用的路徑文件後綴默認是被信任的。另外,REST的API通常是不該該直接用作URL的。不過,你能夠本身定製 HttpMessageConverter 的實現,而後顯式地註冊用於內容協商的文件類型,這種情形下Content-Disposition頭將不會被加入到請求頭中。詳見第21.16.6節中「內容協商」的內容

[Original] This was originally introduced as part of work for CVE-2015-5211. Below are additional recommendations from the report:

  • Encode rather than escape JSON responses. This is also an OWASP XSS recommendation. For an example of how to do that with Spring see spring-jackson-owasp.
  • Configure suffix pattern matching to be turned off or restricted to explicitly registered suffixes only.
  • Configure content negotiation with the properties "useJaf" and "ignoreUnknownPathExtensions" set to false which would result in a 406 response for URLs with unknown extensions. Note however that this may not be an option if URLs are naturally expected to have a dot towards the end.
  • Add X-Content-Type-Options: nosniff header to responses. Spring Security 4 does this by default.

感受這節的翻譯質量還有限,須要繼續瞭解XSS攻擊和RFD攻擊的細節再翻。

矩陣變量

[Original] The URI specification RFC 3986 defines the possibility of including name-value pairs within path segments. There is no specific term used in the spec. The general "URI path parameters" could be applied although the more unique "Matrix URIs", originating from an old post by Tim Berners-Lee, is also frequently used and fairly well known. Within Spring MVC these are referred to as matrix variables.

原來的URI規範RFC 3986中容許在路徑段落中攜帶鍵值對,但規範沒有明確給這樣的鍵值對定義術語。有人叫「URI路徑參數」,也有叫「矩陣URI」的。後者是Tim Berners-Lee首先在其博客中提到的術語,被使用得要更加頻繁一些,知名度也更高些。而在Spring MVC中,咱們稱這樣的鍵值對爲矩陣變量。

[Original] Matrix variables can appear in any path segment, each matrix variable separated with a ";" (semicolon). For example: "/cars;color=red;year=2012". Multiple values may be either "," (comma) separated "color=red,green,blue" or the variable name may be repeated "color=red;color=green;color=blue".

矩陣變量能夠在任何路徑段落中出現,每對矩陣變量之間使用一個分號「;」隔開。好比這樣的URI:/cars;color=red;year=2012 "。多個值能夠用逗號隔開color=red,green,blue ",或者重複變量名屢次color=red;color=green;color=blue "

[Original] If a URL is expected to contain matrix variables, the request mapping pattern must represent them with a URI template. This ensures the request can be matched correctly regardless of whether matrix variables are present or not and in what order they are provided.

若是一個URL有可能須要包含矩陣變量,那麼在請求路徑的映射配置上就須要使用URI模板來體現這一點。這樣才能確保請求能夠被正確地映射,而無論矩陣變量在URI中是否出現、出現的次序是怎樣等。

[Original] Below is an example of extracting the matrix variable "q":

下面是一個例子,展現了咱們如何從矩陣變量中獲取到變量「q」的值:

// GET /pets/42;q=11;r=22

@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11

}

 

[Original] Since all path segments may contain matrix variables, in some cases you need to be more specific to identify where the variable is expected to be:

因爲任意路徑段落中均可以含有矩陣變量,在某些場景下,你須要用更精確的信息來指定一個矩陣變量的位置:

// GET /owners/42;q=11/pets/21;q=22

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
public void findPet(
    @MatrixVariable(name="q", pathVar="ownerId") int q1,
    @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22

}

 

[Original] A matrix variable may be defined as optional and a default value specified:

你也能夠聲明一個矩陣變量不是必須出現的,並給它賦一個默認值:

// GET /pets/42

@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET)
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1

}

 

[Original] All matrix variables may be obtained in a Map:

也能夠經過一個Map來存儲全部的矩陣變量:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
public void findPet(
    @MatrixVariable Map<String, String> matrixVars,
    @MatrixVariable(pathVar="petId") Map<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]

}

 

[Original] Note that to enable the use of matrix variables, you must set the  removeSemicolonContent  property of  RequestMappingHandlerMapping  to  false . By default it is set to  true .

若是要容許矩陣變量的使用,你必須把 RequestMappingHandlerMapping 類的 removeSemicolonContent 屬性設置爲 false 。該值默認是 true 的。

[Original] The MVC Java config and the MVC namespace both provide options for enabling the use of matrix variables.

MVC的Java編程配置和命名空間配置都提供了啓用矩陣變量的方式。

[Original] If you are using Java config, The Advanced Customizations with MVC Java Config section describes how the RequestMappingHandlerMapping can be customized.

若是你是使用Java編程的方式,「MVC Java高級定製化配置」一節描述瞭如何對 RequestMappingHandlerMapping 進行定製。

[Original] In the MVC namespace, the  <mvc:annotation-driven>  element has an  enable-matrix-variables  attribute that should be set to true. By default it is set to false.

而使用MVC的命名空間配置時,你能夠把 <mvc:annotation-driven> 元素下的  enable-matrix-variables  屬性設置爲 true 。該值默認狀況下是配置爲 false 的。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven enable-matrix-variables="true"/>

</beans>

 

可消費的媒體類型

[Original] You can narrow the primary mapping by specifying a list of consumable media types. The request will be matched only if the Content-Type request header matches the specified media type. For example:

你能夠指定一組可消費的媒體類型,縮小映射的範圍。這樣只有當請求頭中 Content-Type 的值與指定可消費的媒體類型中有相同的時候,請求才會被匹配。好比下面這個例子:

@Controller
@RequestMapping(path = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // 方法實現省略
}

 

[Original] Consumable media type expressions can also be negated as in !text/plain to match to all requests other than those with Content-Type of text/plain. Also consider using constants provided in MediaType such as APPLICATION_JSON_VALUE and APPLICATION_JSON_UTF8_VALUE.

指定可消費媒體類型的表達式中還能夠使用否認,好比,能夠使用 !text/plain 來匹配全部請求頭 Content-Type 中不含 text/plain 的請求。同時,在MediaType類中還定義了一些常量,好比 APPLICATION_JSON_VALUE、APPLICATION_JSON_UTF8_VALUE 等,推薦更多地使用它們。

[Original] The consumes condition is supported on the type and on the method level. Unlike most other conditions, when used at the type level, method-level consumable types override rather than extend type-level consumable types.

consumes 屬性提供的是方法級的類型支持。與其餘屬性不一樣,當在類型級使用時,方法級的消費類型將覆蓋類型級的配置,而非繼承關係。

可生產的媒體類型

[Original] You can narrow the primary mapping by specifying a list of producible media types. The request will be matched only if the Accept request header matches one of these values. Furthermore, use of the produces condition ensures the actual content type used to generate the response respects the media types specified in the produces condition. For example:

你能夠指定一組可生產的媒體類型,縮小映射的範圍。這樣只有當請求頭中 Accept 的值與指定可生產的媒體類型中有相同的時候,請求才會被匹配。並且,使用 produces 條件能夠確保用於生成響應(response)的內容與指定的可生產的媒體類型是相同的。舉個例子:

@Controller
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // 方法實現省略
}

 

[Original] Be aware that the media type specified in the produces condition can also optionally specify a character set. For example, in the code snippet above we specify the same media type than the default one configured in MappingJackson2HttpMessageConverter, including the UTF-8 charset.

要注意的是,經過 condition 條件指定的媒體類型也能夠指定字符集。好比在上面的小段代碼中,咱們仍是覆寫了 MappingJackson2HttpMessageConverter 類中默認配置的媒體類型,同時,還指定了使用UTF-8的字符集。

[Original] Just like with consumes, producible media type expressions can be negated as in !text/plain to match to all requests other than those with an Accept header value of text/plain. Also consider using constants provided in  MediaType  such as APPLICATION_JSON_VALUE and APPLICATION_JSON_UTF8_VALUE.

consumes 條件相似,可生產的媒體類型表達式也能夠使用否認。好比,能夠使用 !text/plain 來匹配全部請求頭 Accept 中不含 text/plain 的請求。同時,在 MediaType 類中還定義了一些常量,好比 APPLICATION_JSON_VALUE、APPLICATION_JSON_UTF8_VALUE 等,推薦更多地使用它們。

[Original] The produces condition is supported on the type and on the method level. Unlike most other conditions, when used at the type level, method-level producible types override rather than extend type-level producible types.

produces 屬性提供的是方法級的類型支持。與其餘屬性不一樣,當在類型級使用時,方法級的消費類型將覆蓋類型級的配置,而非繼承關係。

請求參數與請求頭的值

[Original] You can narrow request matching through request parameter conditions such as "myParam", "!myParam", or "myParam=myValue". The first two test for request parameter presence/absence and the third for a specific parameter value. Here is an example with a request parameter value condition:

你能夠篩選請求參數的條件來縮小請求匹配範圍,好比"myParam""!myParam""myParam=myValue"等。前兩個條件用於篩選存在/不存在某些請求參數的請求,第三個條件篩選具備特定參數值的請求。下面有個例子,展現瞭如何使用請求參數值的篩選條件:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // 實際實現省略
    }

}

 

[Original] The same can be done to test for request header presence/absence or to match based on a specific request header value:

一樣,你能夠用相同的條件來篩選請求頭的出現與否,或者篩選出一個具備特定值的請求頭:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping(path = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // 方法體實現省略
    }

}

 

[Original] Although you can match to Content-Type and Accept header values using media type wild cards (for example "content-type=text/*" will match to "text/plain" and "text/html"), it is recommended to use the consumes and produces conditions respectively instead. They are intended specifically for that purpose.

儘管,你能夠使用媒體類型的通配符(好比 "content-type=text/*")來匹配請求頭 Content-TypeAccept的值,但咱們更推薦獨立使用 consumesproduces條件來篩選各自的請求。由於它們就是專門爲區分這兩種不一樣的場景而生的。

21.3.3 定義 @RequestMapping 註解的處理方法(handler method)

使用 @RequestMapping 註解的處理方法能夠擁有很是靈活的方法簽名,它支持的方法參數及返回值類型將在接下來的小節講述。大多數參數均可以任意的次序出現,除了惟一的一個例外: BindingResult 參數。這在下節也會詳細描述。

Spring 3.1中新增了一些類,用以加強註解了 @RequestMapping 的處理方法,分別是 RequestMappingHandlerMapping 類和 RequestMappingHandlerAdapter 類。咱們鼓勵使用這組新的類,若是要使用Spring 3.1及之後版本的新特性,這組類甚至是必須使用的。這些加強類在MVC的命名空間配置和MVC的Java編程方式配置中都是默認開啓的,若是不是使用這兩種方法,那麼就須要顯式地配置。

支持的方法參數類型

下面列出全部支持的方法參數類型:

  • 請求或響應對象(Servlet API)。能夠是任何具體的請求或響應類型的對象,好比, ServletRequest 或 HttpServletRequest 對象等。
  •  HttpSession 類型的會話對象(Servlet API)。使用該類型的參數將要求這樣一個 session 的存在,所以這樣的參數永不爲 null 。

存 取session可能不是線程安全的,特別是在一個Servlet的運行環境中。若是應用可能有多個請求同時併發存取一個session場景,請考慮將  RequestMappingHandlerAdapter 類中的"synchronizeOnSession"標誌設置爲"true"。

  •  org.springframework.web.context.request.WebRequest 或 org.springframework.web.context.request.NativeWebRequest 。容許存取通常的請求參數和請求/會話範圍的屬性(attribute),同時無需綁定使用Servlet/Portlet的API
  • 當前請求的地區信息 java.util.Locale ,由已配置的最相關的地區解析器解析獲得。在MVC的環境下,就是應用中配置的 LocaleResolver 或 LocaleContextResolver 
  • 與當前請求綁定的時區信息 java.util.TimeZone (java 6以上的版本)/ java.time.ZoneId (java 8),由 LocaleContextResolver 解析獲得
  • 用於存取請求正文的 java.io.InputStream 或 java.io.Reader 。該對象與經過Servlet API拿到的輸入流/Reader是同樣的
  • 用於生成響應正文的 java.io.OutputStream 或 java.io.Writer 。該對象與經過Servlet API拿到的輸出流/Writer是同樣的
  •  org.springframework.http.HttpMethod 。能夠拿到HTTP請求方法
  • 包裝了當前被認證用戶信息的 java.security.Principal 
  • 帶 @PathVariable 註解的方法參數,其存放了URI模板變量中的值。詳見「URI模板變量」一節
  • 帶 @MatrixVariable 註解的方法參數,其存放了URI路徑段中的鍵值對。詳見「矩陣變量」一節
  • 帶 @RequestParam 註解的方法參數,其存放了Servlet請求中所指定的參數。參數的值會被轉換成方法參數所聲明的類型。詳見「使用@RequestParam註解綁定請求參數至方法參數」一節
  • 帶 @RequestHeader 註解的方法參數,其存放了Servlet請求中所指定的HTTP請求頭的值。參數的值會被轉換成方法參數所聲明的類型。詳見「使用@RequestHeader註解映射請求頭屬性」一節.
  • 帶 @RequestBody 註解的參數,提供了對HTTP請求體的存取。參數的值經過 HttpMessageConverter 被轉換成方法參數所聲明的類型。詳見「使用@RequestBody註解映射請求體」一節"
  • @RequestPart註解的參數,提供了對一個"multipart/form-data請求塊(request part)內容的存取。更多的信息請參考21.10.5 「處理客戶端文件上傳的請求」一節21.10 「Spring對多部分文件上傳的支持」一節
  •  HttpEntity<?> 類型的參數,其提供了對HTTP請求頭和請求內容的存取。請求流是經過 HttpMessageConverter 被轉換成entity對象的。詳見「HttpEntity」一節
  •  java.util.Map/org.springframework.io.Model/org.springframework.ui.ModelMap  類型的參數,用以加強默認暴露給視圖層的模型(model)的功能
  •  org.springframework.web.servlet.mvc.support.RedirectAttributes 類型的參數,用以指定重定向下要使用到的屬性集以及添加flash屬性(暫存在服務端的屬性,它們會在下次重定向請求的範圍中有效)。詳見「向重定向請求傳遞參數」一節
  • 命令或表單對象,它們用於將請求參數直接綁定到bean字段(多是經過setter方法)。你能夠經過 @InitBinder 註解和/或 HanderAdapter 的配置來定製這個過程的類型轉換。具體請參考 RequestMappingHandlerAdapter類webBindingInitializer 屬性的文檔。這樣的命令對象,以及其上的驗證結果,默認會被添加到模型model中,鍵名默認是該命令對象類的類名——好比, some.package.OrderAddress 類型的命令對象就使用屬性名 orderAddress 類獲取。 ModelAttribute 註解能夠應用在方法參數上,用以指定該模型所用的屬性名
  •  org.springframework.validation.Errors / org.springframework.validation.BindingResult 驗證結果對象,用於存儲前面的命令或表單對象的驗證結果(緊接其前的第一個方法參數)。
  •  org.springframework.web.bind.support.SessionStatus 對象,用以標記當前的表單處理已結束。這將觸發一些清理操做: @SessionAttributes 在類級別註解的屬性將被移除
  •  org.springframework.web.util.UriComponentsBuilder 構造器對象,用於構造當前請求URL相關的信息,好比主機名、端口號、資源類型(scheme)、上下文路徑、servlet映射中的相對部分(literal part)等

在參數列表中, Errors 或 BindingResult 參數必須緊跟在其所綁定的驗證對象後面。這是由於,在參數列表中容許有多於一個的模型對象,Spring會爲它們建立不一樣的 BindingResult 實例。所以,下面這樣的代碼是不能工做的:

BindingResult與@ModelAttribute錯誤的參數次序

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

 

上例中,由於在模型對象Pet和驗證結果對象BindingResult中間還插了一個Model參數,這是不行的。要達到預期的效果,必須調整一下參數的次序:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

 

對於一些帶有required屬性的註解(好比 @RequestParam、@RequestHeader 等),JDK 1.8的 java.util.Optional 能夠做爲被它們註解的方法參數。在這種狀況下,使用 java.util.Optional 與 required=false 的做用是相同的。

支持的方法返回類型

如下是handler方法容許的全部返回類型:

  • ModelAndView對象,其中model隱含填充了命令對象,以及註解了@ModelAttribute字段的存取器被調用所返回的值。
  • Model對象,其中視圖名稱默認由RequestToViewNameTranslator決定,model隱含填充了命令對象以及註解了@ModelAttribute字段的存取器被調用所返回的值
  • Map對象,用於暴露model,其中視圖名稱默認由RequestToViewNameTranslator決定,model隱含填充了命令對象以及註解了@ModelAttribute字段的存取器被調用所返回的值
  • View對象。其中model隱含填充了命令對象,以及註解了@ModelAttribute字段的存取器被調用所返回的值。handler方法也能夠增長一個Model類型的方法參數來加強model
  • String對象,其值會被解析成一個邏輯視圖名。其中,model將默認填充了命令對象以及註解了@ModelAttribute字段的存取器被調用所返回的值。handler方法也能夠增長一個Model類型的方法參數來加強model
  • void。若是處理器方法中已經對response響應數據進行了處理(好比在方法參數中定義一個ServletResponseHttpServletResponse類型的參數並直接向其響應體中寫東西),那麼方法能夠返回void。handler方法也能夠增長一個Model類型的方法參數來加強model
  • 若是處理器方法註解了ResponseBody,那麼返回類型將被寫到HTTP的響應體中,而返回值會被HttpMessageConverters轉換成所方法聲明的參數類型。詳見使用"@ResponseBody註解映射響應體"一節
  • HttpEntity<?>ResponseEntity<?>對象,用於提供對Servlet HTTP響應頭和響應內容的存取。對象體會被HttpMessageConverters轉換成響應流。詳見使用HttpEntity一節
  • HttpHeaders對象,返回一個不含響應體的response
  • Callable<?>對象。當應用但願異步地返回方法值時使用,這個過程由Spring MVC自身的線程來管理
  • DeferredResult<?>對象。當應用但願方法的返回值交由線程自身決定時使用
  • ListenableFuture<?>對象。當應用但願方法的返回值交由線程自身決定時使用
  • ResponseBodyEmitter對象,可用它異步地向響應體中同時寫多個對象,also supported as the body within a ResponseEntity
  • SseEmitter對象,可用它異步地向響應體中寫服務器端事件(Server-Sent Events),also supported as the body within a ResponseEntity
  • StreamingResponseBody對象,可用它異步地向響應對象的輸出流中寫東西。also supported as the body within a ResponseEntity
  • 其餘任何返回類型,都會被處理成model的一個屬性並返回給視圖,該屬性的名稱爲方法級的@ModelAttribute所註解的字段名(或者以返回類型的類名做爲默認的屬性名)。model隱含填充了命令對象以及註解了@ModelAttribute字段的存取器被調用所返回的值

使用@RequestParam將請求參數綁定至方法參數

你能夠使用 @RequestParam 註解將請求參數綁定到你控制器的方法參數上。

下面這段代碼展現了它的用法:

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
    @RequestMapping(method = RequestMapping.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ,..
}

 

若參數使用了該註解,則該參數默認是必須提供的,但你也能夠把該參數標註爲非必須的:只須要將 @RequestParam 註解的 required 屬性設置爲 false 便可(好比, @RequestParam(path="id", required=false) )。

若所註解的方法參數類型不是String,則類型轉換會自動地發生。詳見"方法參數與類型轉換"一節

若 @RequestParam 註解的參數類型是 Map<String, String> 或者 MultiValueMap<String, String> ,則該Map中會自動填充全部的請求參數。

使用@RequestBody註解映射請求體

方法參數中的 @RequestBody 註解暗示了方法參數應該被綁定了HTTP請求體的值。舉個例子:

@RequestMapping(path = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

 

請求體到方法參數的轉換是由 HttpMessageConverter 完成的。 HttpMessageConverter 負責將HTTP請求信息轉換成對象,以及將對象轉換回一個HTTP響應體。對於 @RequestBody 註解, RequestMappingHandlerAdapter 提供瞭如下幾種默認的 HttpMessageConverter 支持:

  •  ByteArrayHttpMessageConverter 用以轉換字節數組
  •  StringHttpMessageConverter 用以轉換字符串
  •  FormHttpMessageConverter 用以將表格數據轉換成 MultiValueMap<String, String> 或從 MultiValueMap<String, String> 中轉換出表格數據
  •  SourceHttpMessageConverter 用於 javax.xml.transform.Source 類的互相轉換

關於這些轉換器的更多信息,請參考"HTTP信息轉換器"一節。另外,若是使用的是MVC命名空間或Java編程的配置方式,會有更多默認註冊的消息轉換器。更多信息,請參考"啓用MVC Java編程配置或MVC XML命令空間配置"一節

若你更傾向於閱讀和編寫XML文件,那麼你須要配置一個 MarshallingHttpMessageConverter 併爲其提供 org.springframework.oxm 包下的一個 Marshaller 和 Unmarshaller 實現。下面的示例就爲你展現如何直接在配置文件中配置它。但若是你的應用是使用MVC命令空間或MVC Java編程的方式進行配置的,則請參考"啓用MVC Java編程配置或MVC XML命令空間配置"這一節

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <util:list id="beanList">
            <ref bean="stringHttpMessageConverter"/>
            <ref bean="marshallingHttpMessageConverter"/>
        </util:list>
    </property
</bean>

<bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"/>

<bean id="marshallingHttpMessageConverter"
        class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
    <property name="marshaller" ref="castorMarshaller"/>
    <property name="unmarshaller" ref="castorMarshaller"/>
</bean>

<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

 

註解了 @RequestBody 的方法參數還能夠被 @Valid 註解,這樣框架會使用已配置的 Validator 實例來對該參數進行驗證。若你的應用是使用MVC命令空間或MVC Java編程的方式配置的,框架會假設在classpath路徑下存在一個符合JSR-303規範的驗證器,並自動將其做爲默認配置。

與 @ModelAttribute 註解的參數同樣,Errors也能夠被傳入爲方法參數,用於檢查錯誤。若是沒有聲明這樣一個參數,那麼程序會拋出一個 MethodArgumentNotValidException 異常。該異常默認由 DefaultHandlerExceptionResolver 處理,處理程序會返回一個400錯誤給客戶端。

關於如何經過MVC命令空間或MVC Java編程的方式配置消息轉換器和驗證器,也請參考"啓用MVC Java編程配置或MVC XML命令空間配置"一節

使用@ResponseBody註解映射響應體

 @ResponseBody 註解與 @RequestBody 註解相似。 @ResponseBody 註解可被應用於方法上,標誌該方法的返回值(更正,原文是return type,看起來應該是返回值)應該被直接寫回到HTTP響應體中去(而不會被被放置到Model中或被解釋爲一個視圖名)。舉個例子:

@RequestMapping(path = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
    return "Hello World"
}

 

上面的代碼結果是文本Hello World將被寫入HTTP的響應流中。

與 @RequestBody 註解相似,Spring使用了一個 HttpMessageConverter 來將返回對象轉換到響應體中。關於這些轉換器的更多信息,請參考"HTTP信息轉換器"一節

使用@RestController註解建立REST控制器

當今讓控制器實現一個REST API是很是常見的,這種場景下控制器只須要提供JSON、XML或其餘自定義的媒體類型內容便可。你不須要在每一個 @RequestMapping 方法上都增長一個 @ResponseBody 註解,更簡明的作法是,給你的控制器加上一個 @RestController 的註解。

@RestController是一個原生內置的註解,它結合了 @ResponseBody 與 @Controller 註解的功能。不只如此,它也讓你的控制器更表義,並且在框架將來的發佈版本中,它也可能承載更多的意義。

與普通的 @Controller 無異, @RestController 也能夠與 @ControllerAdvicebean 配合使用。更多細節,請見使用@ControllerAdvice輔助控制器

使用HTTP實體HttpEntity

 HttpEntity 與 @RequestBody 和 @ResponseBody 很類似。除了能得到請求體和響應體中的內容以外, HttpEntity (以及專門負責處理響應的 ResponseEntity 子類)還能夠存取請求頭和響應頭,像下面這樣:

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
    String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
    byte[] requestBody = requestEntity.getBody();

    // do something with request header and body

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

 

上面這段示例代碼先是獲取了 MyRequestHeader 請求頭的值,而後讀取請求體的主體內容。讀完之後往影響頭中添加了一個本身的響應頭 MyResponseHeader ,而後向響應流中寫了字符串Hello World,最後把響應狀態碼設置爲201(建立成功)。

與 @RequestBody 與 @ResponseBody 註解同樣,Spring使用了 HttpMessageConverter 來對請求流和響應流進行轉換。關於這些轉換器的更多信息,請閱讀上一小節以及"HTTP信息轉換器"這一節

對方法使用@ModelAttribute註解

 @ModelAttribute 註解可被應用在方法或方法參數上。本節將介紹其被註解於方法上時的用法,下節會介紹其被用於註解方法參數的用法。

註解在方法上的 @ModelAttribute 說明了方法的做用是用於添加一個或多個屬性到model上。這樣的方法能接受與 @RequestMapping 註解相同的參數類型,只不過不能直接被映射到具體的請求上。在同一個控制器中,註解了 @ModelAttribute 的方法實際上會在 @RequestMappin g方法以前被調用。如下是幾個例子:

// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}

// Add multiple attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // add more ...
}

 

 @ModelAttribute 方法一般被用來填充一些公共須要的屬性或數據,好比一個下拉列表所預設的幾種狀態,或者寵物的幾種類型,或者去取得一個HTML表單渲染所須要的命令對象,好比Account等。

留意 @ModelAttribute 方法的兩種風格。在第一種寫法中,方法經過返回值的方式默認地將添加一個屬性;在第二種寫法中,方法接收一個Model對象,而後能夠向其中添加任意數量的屬性。你能夠在根據須要,在兩種風格中選擇合適的一種。

一個控制器能夠擁有數量不限的 @ModelAttribute 方法。同個控制器內的全部這些方法,都會在 @RequestMapping 方法以前被調用。

@ModelAttribute方法也能夠定義在 @ControllerAdvice 註解的類中,而且這些 @ModelAttribute 能夠同時對許多控制器生效。具體的信息能夠參考使用@ControllerAdvice輔助控制器

屬性名沒有被顯式指定的時候又當如何呢?在這種狀況下,框架將根據屬性的類型給予一個默認名稱。舉個例子,若方法返回一個Account類型的對象,則默認的屬性名爲"account"。你能夠經過設置 @ModelAttribute 註解的值來改變默認值。當向Model中直接添加屬性時,請使用合適的重載方法 addAttribute(..) -即,帶或不帶屬性名的方法。

 @ModelAttribute 註解也能夠被用在 @RequestMapping 方法上。這種狀況下,@RequestMapping方法的返回值將會被解釋爲model的一個屬性,而非一個視圖名。此時視圖名將以視圖命名約定來方式來決議,與返回值爲void的方法所採用的處理方法相似——請見視圖:請求與視圖名的對應

在方法參數上使用@ModelAttribute註解

如上一小節所解釋, @ModelAttribute 註解既能夠被用在方法上,也能夠被用在方法參數上。這一小節將介紹它註解在方法參數上時的用法。

註解在方法參數上的 @ModelAttribute 說 明瞭該方法參數的值將由model中取得。若是model中找不到,那麼該參數會先被實例化,而後被添加到model中。在model中存在之後,請求中 全部名稱匹配的參數都會填充到該參數中。這在Spring MVC中被稱爲數據綁定,一個很是有用的特性,節約了你每次都須要手動從表格數據中轉換這些字段數據的時間。

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) { }

 

以上面的代碼爲例,這個Pet類型的實例可能來自哪裏呢?有幾種可能:

  • 它可能由於 @SessionAttributes 註解的使用已經存在於model中——詳見"在請求之間使用@SessionAttributes註解,使用HTTP會話保存模型數據"一節
  • 它可能由於在同個控制器中使用了 @ModelAttribute 方法已經存在於model中——正如上一小節所敘述的
  • 它多是由URI模板變量和類型轉換中取得的(下面會詳細講解)
  • 它多是調用了自身的默認構造器被實例化出來的

 @ModelAttribute 方法經常使用於從數據庫中取一個屬性值,該值可能經過 @SessionAttributes 註解在請求中間傳遞。在一些狀況下,使用URI模板變量和類型轉換的方式來取得一個屬性是更方便的方式。這裏有個例子:

@RequestMapping(path = "/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {

}

 

上面這個例子中,model屬性的名稱("account")與URI模板變量的名稱相匹配。若是你配置了一個能夠將String類型的帳戶值轉換成Account類型實例的轉換器 Converter<String, Account> ,那麼上面這段代碼就能夠工做的很好,而不須要再額外寫一個 @ModelAttribute 方法。

下一步就是數據的綁定。 WebDataBinder 類能將請求參數——包括字符串的查詢參數和表單字段等——經過名稱匹配到model的屬性上。成功匹配的字段在須要的時候會進行一次類型轉換(從String類型到目標字段的類型),而後被填充到model對應的屬性中。數據綁定和數據驗證的問題在第8章 驗證,數據綁定和類型轉換中提到。如何在控制器層來定製數據綁定的過程,在這一節 "定製WebDataBinder的初始化"中說起。

進行了數據綁定後,則可能會出現一些錯誤,好比沒有提供必須的字段、類型轉換過程的錯誤等。若想檢查這些錯誤,能夠在註解了 @ModelAttribute 的參數緊跟着聲明一個 BindingResult 參數:

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

 

拿到 BindingResult 參數後,你能夠檢查是否有錯誤。有時你能夠經過Spring的 <errors> 表單標籤來在同一個表單上顯示錯誤信息。

 BindingResult 被用於記錄數據綁定過程的錯誤,所以除了數據綁定外,你還能夠把該對象傳給本身定製的驗證器來調用驗證。這使得數據綁定過程和驗證過程出現的錯誤能夠被蒐集到一處,而後一併返回給用戶:

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

 

又或者,你能夠經過添加一個JSR-303規範的 @Valid 註解,這樣驗證器會自動被調用。

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

 

關於如何配置並使用驗證,能夠參考第8.8小節 "Spring驗證"第8章 驗證,數據綁定和類型轉換

在請求之間使用@SessionAttributes註解,使用HTTP會話保存模型數據

類型級別的 @SessionAttributes 註解聲明瞭某個特定處理器所使用的會話屬性。一般它會列出該類型但願存儲到 session 或 converstaion 中的model屬性名或model的類型名,通常是用於在請求之間保存一些表單數據的 bean 。

如下的代碼段演示了該註解的用法,它指定了模型屬性的名稱

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

 

使用"application/x-www-form-urlencoded"數據

上一小節講述瞭如何使用 @ModelAttribute 支 持客戶端瀏覽器的屢次表單提交請求。對於不是使用的瀏覽器的客戶端,咱們也推薦使用這個註解來處理請求。但當請求是一個HTTP PUT方法的請求時,有一個事情須要注意。瀏覽器能夠經過HTTP的GET方法或POST方法來提交表單數據,非瀏覽器的客戶端還能夠經過HTTP的 PUT方法來提交表單。這就設計是個挑戰,由於在Servlet規範中明確規定, ServletRequest.getParameter*() 系列的方法只能支持經過HTTP POST方法的方式提交表單,而不支持HTTP PUT的方式。

爲了支持HTTP的PUT類型和PATCH類型的請求,Spring的spring-web模塊提供了一個過濾器 HttpPutFormContentFilter 。你能夠在web.xml文件中配置它:

 <filter>
        <filter-name>httpPutFormFilter</filter-name>
        <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>httpPutFormFilter</filter-name>
        <servlet-name>dispatcherServlet</servlet-name>
    </filter-mapping>

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

 

上面的過濾器將會攔截內容類型(content type)爲 application/x-www-form-urlencoded 、HTTP方法爲PUT或PATCH類型的請求,而後從請求體中讀取表單數據,把它們包裝在 ServletRequest 中。這是爲了使表單數據可以經過 ServletRequest.getParameter*() 系列的方法來拿到。

由於 HttpPutFormContentFilter 會消費請求體的內容,所以,它不該該用於處理那些依賴於其餘 application/x-www-form-urlencoded 轉換器的PUT和PATCH請求,這包括了 @RequestBodyMultiValueMap<String, String> 和 HttpEntity<MultiValueMap<String, String>> 。

 @CookieValue 註解能將一個方法參數與一個HTTP cookie的值進行綁定。

看一個這樣的場景:如下的這個cookie存儲在一個HTTP請求中:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代碼演示了拿到JSESSIONID這個cookie值的方法:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
    //...
}

 

若註解的目標方法參數不是String類型,則類型轉換會自動進行。詳見"方法參數與類型轉換"一節。

這個註解能夠註解處處理器方法上,在Servlet環境和Portlet環境都能使用。

使用@RequestHeader註解映射請求頭屬性

 @RequestHeader 註解能將一個方法參數與一個請求頭屬性進行綁定。

如下是一個請求頭的例子:

  Host                    localhost:8080
    Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
    Accept-Language         fr,en-gb;q=0.7,en;q=0.3
    Accept-Encoding         gzip,deflate
    Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
    Keep-Alive              300

 

如下的代碼片斷展現瞭如何取得Accept-Encoding請求頭和Keep-Alive請求頭的值:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

 

若註解的目標方法參數不是String類型,則類型轉換會自動進行。"方法參數與類型轉換"一節。

若是 @RequestHeader 註解應用在 Map<String, String>、MultiValueMap<String, String> 或 HttpHeaders 類型的參數上,那麼全部的請求頭屬性值都會被填充到map中。

Spring內置支持將一個逗號分隔的字符串(或其餘類型轉換系統所能識別的類型)轉換成一個String類型的列表/集合。舉個例子,一個註解了 @RequestHeader("Accept") 的方法參數能夠是一個String類型,但也能夠是String[]List<String>類型的。

這個註解能夠註解處處理器方法上,在Servlet環境和Portlet環境都能使用。

方法參數與類型轉換

從請求參數、路徑變量、請求頭屬性或者cookie中抽取出來的String類型的值,可能須要被轉換成其所綁定的目標方法參數或字段的類型(好比,經過 @ModelAttribute 將請求參數綁定到方法參數上)。若是目標類型不是String,Spring會自動進行類型轉換。全部的簡單類型諸如int、long、Date都有內置的支持。若是想進一步定製這個轉換過程,你能夠經過 WebDataBinder (詳見"定製WebDataBinder的初始化"一節),或者爲 Formatters 配置一個 FormattingConversionService (詳見8.6節 "Spring字段格式化"一節)來作到。

定製WebDataBinder的初始化

若是想經過Spring的 WebDataBinder 在屬性編輯器中作請求參數的綁定,你能夠使用在控制器內使用 @InitBinder  @InitBinder 註解的方法、在註解了 @ControllerAdvice 的類中使用 @InitBinder 註解的方法,或者提供一個定製的 WebBindingInitializer 。更多的細節,請參考使用@ControllerAdvice輔助控制器一節。

數據綁定的定製:使用@InitBinder

使用 @InitBinder 註解控制器的方法,你能夠直接在你的控制器類中定製應用的數據綁定。 @InitBinder 用來標記一些方法,這些方法會初始化一個WebDataBinder並用覺得處理器方法填充命令對象和表單對象的參數。

除了命令/表單對象以及相應的驗證結果對象,這樣的「綁定器初始化」方法可以接收 @RequestMapping 所支持的全部參數類型。「綁定器初始化」方法不能有返回值,所以,通常將它們聲明爲void返回類型。特別地,當 WebDataBinder 與 WebRequest 或 java.util.Locale 一塊兒做爲方法參數時,你能夠在代碼中註冊上下文相關的編輯器。

下面的代碼示例演示瞭如何使用 @InitBinder 來配置一個 CustomerDateEditor ,後者會對全部 java.util.Date 類型的表單字段進行操做:

@Controller
public class MyFormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

 

或者,你能夠使用Spring 4.2提供的 addCustomFormatter 來指定 Formatter 的實現,而非經過 PropertyEditor 實例。這在你擁有一個須要Formatter的setup方法,而且該方法位於一個共享的 FormattingConversionService中 時很是有用。這樣對於控制器級別的綁定規則的定製,代碼更容易被複用。

@Controller
public class MyFormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

 

配置定製的WebBindingInitializer

爲了externalize數據綁定的初始化過程,你能夠爲 WebBindingInitializer 接口提供一個本身的實現,在其中你能夠爲 AnnotationMethodHandlerAdapter 提供一個默認的配置 bean ,以此來覆寫默認的配置。

如下的代碼來自PetClinic的應用,它展現了爲 WebBindingInitializer 接口提供一個自定義實現: org.springframework.samples.petclinic.web.ClinicBindingInitializer  org.springframework.samples.petclinic.web.ClinicBindingInitializer 完整的配置過程。後者中配置了PetClinic應用中許多控制器所須要的屬性編輯器PropertyEditors。

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0"/>
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/>
    </property>
</bean>

 

@InitBinder方法也能夠定義在 @ControllerAdvice 註解的類上,這樣配置能夠爲許多控制器所共享。這提供了除使用 WebBindingInitializer 外的另一種方法。更多細節請參考使用@ControllerAdvice輔助控制器一節。

使用@ControllerAdvice輔助控制器

 @ControllerAdvice 是一個組件註解,它使得其實現類可以被classpath掃描自動發現。若應用是經過MVC命令空間或MVC Java編程方式配置,那麼該特性默認是自動開啓的。

註解 @ControllerAdvice 的類能夠擁有 @ExceptionHandler、@InitBinder 或 @ModelAttribute 註解的方法,而且這些方法會被應用至控制器類層次??的全部 @RequestMapping 方法上。

你也能夠經過@ControllerAdvice的屬性來指定其只對一個子集的控制器生效:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

 

更多的細節,請查閱@ControllerAdvice的文檔

下面兩節,還看不太懂,待譯。

Jackson Serialization View Support

It can sometimes be useful to filter contextually the object that will be serialized to the HTTP response body. In order to provide such capability, Spring MVC has built-in support for rendering with Jackson's Serialization Views.

To use it with an @ResponseBody controller method or controller methods that return  ResponseEntity , simply add the @JsonView annotation with a class argument specifying the view class or interface to be used:

_@RestController_
public class UserController {

    _@RequestMapping(path = "/user", method = RequestMethod.GET)_
    _@JsonView(User.WithoutPasswordView.class)_
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    _@JsonView(WithoutPasswordView.class)_
    public String getUsername() {
        return this.username;
    }

    _@JsonView(WithPasswordView.class)_
    public String getPassword() {
        return this.password;
    }
}

 

\[Note\] Note
 

Note that despite  @JsonView  allowing for more than one class to be specified, the use on a controller method is only supported with exactly one class argument. Consider the use of a composite interface if you need to enable multiple views.

For controllers relying on view resolution, simply add the serialization view class to the model:

_@Controller_
public class UserController extends AbstractController {

    _@RequestMapping(path = "/user", method = RequestMethod.GET)_
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

 

Jackson JSONP Support

In order to enable JSONP support for  @ResponseBody  and  ResponseEntity  methods, declare an  @ControllerAdvice  bean that extends  AbstractJsonpResponseBodyAdvice  as shown below where the constructor argument indicates the JSONP query parameter name(s):

_@ControllerAdvice_
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

 

For controllers relying on view resolution, JSONP is automatically enabled when the request has a query parameter named  jsonp  or  callback . Those names can be customized through  jsonpParameterNames  property.

21.3.4 異步請求的處理

Spring MVC 3.2開始引入了基於Servlet 3的異步請求處理。相比之前,控制器方法已經不必定須要返回一個值,而是能夠返回一個 java.util.concurrent.Callable 的對象,並經過Spring MVC所管理的線程來產生返回值。與此同時,Servlet容器的主線程則能夠退出並釋放其資源了,同時也容許容器去處理其餘的請求。經過一個 TaskExecutor ,Spring MVC能夠在另外的線程中調用 Callable 。當 Callable 返回時,請求再攜帶 Callable 返回的值,再次被分配到Servlet容器中恢復處理流程。如下代碼給出了一個這樣的控制器方法做爲例子:

@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}

 

另外一個選擇,是讓控制器方法返回一個 DeferredResult 的實例。這種場景下,返回值能夠由任何一個線程產生,也包括那些不是由Spring MVC管理的線程。舉個例子,返回值多是爲了響應某些外部事件所產生的,好比一條JMS的消息,一個計劃任務,等等。如下代碼給出了一個這樣的控制器做爲例子:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);

 

若是對Servlet 3.0的異步請求處理特性沒有了解,理解這個特性可能會有點困難。所以,閱讀一下前者的文檔將會頗有幫助。如下給出了這個機制運做背後的一些原理:

  • 一個servlet請求 ServletRequest 能夠經過調用 request.startAsync() 方法而進入異步模式。這樣作的主要結果就是該servlet以及全部的過濾器均可以結束,但其響應(response)會留待異步處理結束後再返回
  • 調用 request.startAsync() 方法會返回一個 AsyncContext 對象,可用它對異步處理進行進一步的控制和操做。好比說它也提供了一個與轉向(forward)很類似的 dispatch 方法,只不過它容許應用恢復Servlet容器的請求處理進程
  •  ServletRequest 提供了獲取當前 DispatherType 的方式,後者能夠用來區別當前處理的是原始請求、異步分發請求、轉向,或是其餘類型的請求分發類型。

有了上面的知識,下面能夠來看一下 Callable  的異步請求被處理時所依次發生的事件:

  • 控制器先返回一個 Callable 對象
  • Spring MVC開始進行異步處理,並把該 Callable 對象提交給另外一個獨立線程的執行器 TaskExecutor 處理
  •  DispatcherServlet 和全部過濾器都退出Servlet容器線程,但此時方法的響應對象仍未返回
  •  Callable 對象最終產生一個返回結果,此時Spring MVC會從新把請求分派回Servlet容器,恢復處理
  •  DispatcherServlet 再次被調用,恢復對 Callable 異步處理所返回結果的處理

對 DeferredResult 異步請求的處理順序也很是相似,區別僅在於應用能夠經過任何線程來計算返回一個結果:

  • 控制器先返回一個 DeferredResult 對象,並把它存取在內存(隊列或列表等)中以便存取
  • Spring MVC開始進行異步處理
  •  DispatcherServlet 和全部過濾器都退出Servlet容器線程,但此時方法的響應對象仍未返回
  • 由處理該請求的線程對  DeferredResult 進行設值,而後Spring MVC會從新把請求分派回Servlet容器,恢復處理
  •  DispatcherServlet 再次被調用,恢復對該異步返回結果的處理

關於引入異步請求處理的背景和緣由,以及何時使用它、爲何使用異步請求處理等問題,你能夠從這個系列的博客中瞭解更多信息。

異步請求的異常處理

若控制器返回的 Callable 在執行過程當中拋出了異常,又會發生什麼事情?簡單來講,這與通常的控制器方法拋出異常是同樣的。它會被正常的異常處理流程捕獲處理。更具體地說呢,當 Callable 拋出異常時,Spring MVC會把一個 Exception 對象分派給Servlet容器進行處理,而不是正常返回方法的返回值,而後容器恢復對此異步請求異常的處理。若方法返回的是一個 DeferredResult 對象,你能夠選擇調 Exception 實例的 setResult 方法仍是 setErrorResult 方法。

攔截異步請求

處理器攔截器 HandlerInterceptor 能夠實現 AsyncHandlerInterceptor 接口攔截異步請求,由於在異步請求開始時,被調用的回調方法是該接口的 afterConcurrentHandlingStarted 方法,而非通常的 postHandle 和 afterCompletion 方法。

若是須要與異步請求處理的生命流程有更深刻的集成,好比須要處理timeout的事件等,則 HandlerInterceptor 須要註冊一個 CallableProcessingInterceptor 或 DeferredResultProcessingInterceptor 攔截器。具體的細節能夠參考 AsyncHandlerInterceptor 類的Java文檔。

 DeferredResult 類還提供了 onTimeout(Runnable) 和 onCompletion(Runnable) 等方法,具體的細節能夠參考 DeferredResult 類的Java文檔。

 Callable 須要請求過時(timeout)和完成後的攔截時,能夠把它包裝在一個 WebAsyncTask 實例中,後者提供了相關的支持。

HTTP streaming(不知道怎麼翻)

如前所述,控制器能夠使用 DeferredResult 或 Callable 對象來異步地計算其返回值,這能夠用於實現一些有用的技術,好比 long polling技術,讓服務器能夠儘量快地向客戶端推送事件。

若是你想在一個HTTP響應中同時推送多個事件,怎麼辦?這樣的技術已經存在,與"Long Polling"相關,叫"HTTP Streaming"。Spring MVC支持這項技術,你能夠經過讓方法返回一個 ResponseBodyEmitte r類型對象來實現,該對象可被用於發送多個對象。一般咱們所使用的 @ResponseBody 只能返回一個對象,它是經過 HttpMessageConverter 寫到響應體中的。

下面是一個實現該技術的例子:

@RequestMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

 

 ResponseBodyEmitter 也能夠被放到 ResponseEntity 體裏面使用,這能夠對響應狀態和響應頭作一些定製。

Note that ResponseBodyEmitter can also be used as the body in a ResponseEntity in order to customize the status and headers of the response.

使用「服務器端事件推送」的HTTP Streaming

 SseEmitter 是 ResponseBodyEmitter 的一個子類,提供了對服務器端事件推送的技術的支持。服務器端事件推送其實只是一種HTTP Streaming的相似實現,只不過它服務器端所推送的事件遵循了W3C Server-Sent Events規範中定義的事件格式。

「服務器端事件推送」技術正如其名,是用於由服務器端向客戶端進行的事件推送。這在Spring MVC中很容易作到,只須要方法返回一個 SseEmitter 類型的對象便可。

需 要注意的是,Internet Explorer並不支持這項服務器端事件推送的技術。另外,對於更大型的web應用及更精緻的消息傳輸場景——好比在線遊戲、在線協做、金融應用等—— 來講,使用Spring的WebSocket(包含SockJS風格的實時WebSocket)更成熟一些,由於它支持的瀏覽器範圍很是廣(包括IE), 而且,對於一個以消息爲中心的架構中,它爲服務器端-客戶端間的事件發佈-訂閱模型的交互提供了更高層級的消息模式(messaging patterns)的支持。

直接寫回輸出流OutputStream的HTTP Streaming

 ResponseBodyEmitter  ResponseBodyEmitter 也容許經過 HttpMessageConverter 向響應體中支持寫事件對象。這多是最多見的情形,好比寫返回的JSON數據的時候。但有時,跳過消息轉換的階段,直接把數據寫回響應的輸出流 OutputStream 可能更有效,好比文件下載這樣的場景。這能夠經過返回一個 StreamingResponseBody 類型的對象來實現。

如下是一個實現的例子:

@RequestMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

 

 ResponseBodyEmitter 也能夠被放到 ResponseEntity 體裏面使用,這能夠對響應狀態和響應頭作一些定製。

異步請求處理的相關配置

Servlet容器配置

對於那些使用web.xml配置文件的應用,請確保 web.xml 的版本更新到3.0:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance http://java.sun.com/xml/ns/javaee
                    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    ...

</web-app>

 

異步請求必須在 web.xml 將 DispatcherServlet 下的子元素 <async-supported>true</async-supported> 設置爲true。此外,全部可能參與異步請求處理的過濾器 Filter 都必須配置爲支持ASYNC類型的請求分派。在Spring框架中爲過濾器啓用支持ASYNC類型的請求分派應是安全的,由於這些過濾器通常都繼承了基類 OncePerRequestFilter ,後者在運行時會檢查該過濾器是否須要參與到異步分派的請求處理中。

如下是一個例子,展現了web.xml的配置:

  <web-app xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
                http://java.sun.com/xml/ns/javaee
                http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        version="3.0">

        <filter>
            <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
            <filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
            <async-supported>true</async-supported>
        </filter>

        <filter-mapping>
            <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>ASYNC</dispatcher>
        </filter-mapping>

    </web-app>

 

若是應用使用的是Servlet 3規範基於Java編程的配置方式,好比經過 WebApplicationInitializer ,那麼你也須要設置"asyncSupported"標誌和ASYNC分派類型的支持,就像你在 web.xml 中所配置的同樣。你能夠考慮直接繼承 AbstractDispatcherServletInitializer 或 AbstractAnnotationConfigDispatcherServletInitializer 來簡化配置,它們都自動地爲你設置了這些配置項,並使得註冊 Filter 過濾器實例變得很是簡單。

Spring MVC配置

MVC Java編程配置和MVC命名空間配置方式都提供了配置異步請求處理支持的選擇。 WebMvcConfigurer 提供了 configureAsyncSupport 方法,而 <mvc:annotation-driven> 有一個子元素 <async-support> ,它們都用覺得此提供支持。

這些配置容許你覆寫異步請求默認的超時時間,在未顯式設置時,它們的值與所依賴的Servlet容器是相關的(好比,Tomcat設置的超時時間是10秒)。你也能夠配置用於執行控制器返回值 Callable 的執行器 AsyncTaskExecutor 。Spring強烈推薦你配置這個選項,由於Spring MVC默認使用的是普通的執行器 SimpleAsyncTaskExecutor 。MVC Java編程配置及MVC命名空間配置的方式都容許你註冊本身的 CallableProcessingInterceptor 和 DeferredResultProcessingInterceptor 攔截器實例。

若你須要爲特定的 DeferredResult 覆寫默認的超時時間,你能夠選用合適的構造方法來實現。相似,對於 Callable 返回,你能夠把它包裝在一個 WebAsyncTask 對象中,並使用合適的構造方法定義超時時間。 WebAsyncTask 類的構造方法同時也能接受一個任務執行器 AsyncTaskExecutor 類型的參數。

21.3.5 對控制器測試

spring-test模塊對測試控制器 @Controller 提供了最原生的支持。詳見14.6 "Spring MVC測試框架"一節。

21.4 處理器映射(Handler Mappings)

在Spring的上個版本中,用戶須要在web應用的上下文中定義一個或多個的 HandlerMappingbean ,用以將進入容器的web請求映射到合適的處理器方法上。容許在控制器上添加註解後,一般你就沒必要這麼作了,由於 RequestMappingHandlerMapping 類會自動查找全部註解了 @RequestMapping 的 @Controller 控制器bean。同時也請知道,全部繼承自 AbstractHandlerMapping 的處理器方法映射 HandlerMapping 類都擁有下列的屬性,你能夠對它們進行定製:

  • 一個 interceptors 列表,指示了應用其上的一個攔截器列表。處理器方法攔截器會在 21.4.1小節 使用HandlerInterceptor攔截請求中討論。
  •  defaultHandler ,生效的默認處理器,when this handler mapping does not result in a matching handler.
  • order,根據order(見 org.springframework.core.Ordered 接口)屬性的值,Spring會對上下文可用的全部處理器映射進行排序,並應用第一個匹配成功的處理器
  •  alwaysUseFullPath (老是使用完整路徑)。若設置爲 true ,Spring將在當前Servlet上下文中老是使用完整路徑來查找合適的處理器。若設置爲 false (默認就爲 false ),則使用當前Servlet的mapping路徑。舉個例子,若一個Servlet的mapping路徑是/testing/*,而且 alwaysUseFullPath 屬性被設置爲 true ,此時用於查找處理器的路徑將是/testing/viewPage.html;而若 alwaysUseFullPath 屬性的值爲 false ,則此時查找路徑是/viewPage.html
  •  urlDecode ,默認設置爲 true (也是Spring 2.5的默認設置)。若你須要比較加密過的路徑,則把此標誌設爲 false 。須要注意的是, HttpServletRequest 永遠以未加密的方式存儲Servlet路徑。此時,該路徑將沒法匹配到加密過的路徑

下面的代碼展現了配置一個攔截器的方法:

<beans>
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <bean class="example.MyInterceptor"/>
        </property>
    </bean>
<beans>

 

21.4.1 使用HandlerInterceptor攔截請求

Spring的處理器映射機制包含了處理器攔截器。攔截器在你須要爲特定類型的請求應用一些功能時可能頗有用,好比,檢查用戶身份等。

處理器映射處理過程配置的攔截器,必須實現  org.springframework.web.servlet 包下的  HandlerInterceptor 接口。這個接口定義了三個方法:  preHandle(..) ,它在處理器實際執行 以前 會被執行;  postHandle(..) ,它在處理器執行 完畢 之後被執行;  afterCompletion(..) ,它在 整個請求處理完成 以後被執行。這三個方法爲各類類型的前處理和後處理需求提供了足夠的靈活性。

 preHandle(..) 方法返回一個 boolean 值。你能夠經過這個方法來決定是否繼續執行處理鏈中的部件。當方法返回  true 時,處理器鏈會繼續執行;若方法返回  false ,  DispatcherServlet 即認爲攔截器自身已經完成了對請求的處理(好比說,已經渲染了一個合適的視圖),那麼其他的攔截器以及執行鏈中的其餘處理器就不會再被執行了。

攔截器能夠經過 interceptors 屬性來配置,該選項在全部繼承了 AbstractHandlerMapping 的處理器映射類 HandlerMapping 都提供了配置的接口。以下面代碼樣例所示:

<beans>
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
    </bean>

    <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime" value="9"/>
        <property name="closingTime" value="18"/>
    </bean>
<beans>

 

package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;

    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }

    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour && hour < closingTime) {
            return true;
        }
        response.sendRedirect("http://host.com/outsideOfficeHours.html");
        return false;
    }
}

 

在上面的例子中,全部被此處理器處理的請求都會被 TimeBasedAccessInterceptor 攔截器攔截。若是當前時間在工做時間之外,那麼用戶就會被重定向到一個HTML文件提示用戶,好比顯示「你只有在工做時間才能夠訪問本網站」之類的信息。

使用 RequestMappingHandlerMapping 時,實際的處理器是一個處理器方法 HandlerMethod 的實例,它標識了一個將被用於處理該請求的控制器方法。

如你所見,Spring的攔截器適配器 HandlerInterceptorAdapter 讓繼承 HandlerInterceptor 接口變得更簡單了。

上面的例子中,全部控制器方法處理的請求都會被配置的攔截器先攔截到。若是你想進一步縮小攔截的URL範圍,你能夠經過MVC命名空間或MVC Java編程的方式來配置,或者,聲明一個 MappedInterceptor 類型的bean實例來處理。具體請見 21.16.1 啓用MVC Java編程配置或MVC命名空間配置一小節。

須要注意的是, HandlerInterceptor 的後攔截 postHandle 方法不必定老是適用於註解了 @ResponseBody 或 ResponseEntity 的方法。這些場景中, HttpMessageConverter 會在攔截器的 postHandle 方法被調以前就把信息寫回響應中。這樣攔截器就沒法再改變響應了,好比要增長一個響應頭之類的。若是有這種需求,請讓你的應用實現 ResponseBodyAdvice 接口,並將其定義爲一個 @ControllerAdvicebean 或直接在 RequestMappingHandlerMapping 中配置。

21.5 視圖解析

所 有web應用的MVC框架都提供了視圖相關的支持。Spring提供了一些視圖解析器,它們讓你可以在瀏覽器中渲染模型,並支持你自由選用適合的視圖技術 而沒必要與框架綁定到一塊兒。Spring原生支持JSP視圖技術、Velocity模板技術和XSLT視圖等。你能夠閱讀文檔的第22章 視圖技術一章,裏面討論瞭如何集成並使用許多獨立的視圖技術。

有兩個接口在Spring處理視圖相關事宜時相當重要,分別是視圖解析器接口 ViewResolver 和視圖接口自己View。視圖解析器 ViewResolver 負責處理視圖名與實際視圖之間的映射關係。視圖接口View負責準備請求,並將請求的渲染交給某種具體的視圖技術實現。

21.5.1 使用ViewResolver接口解析視圖

正如在21.3 控制器的實現一節中所討論的,Spring MVC中全部控制器的處理器方法都必須返回一個邏輯視圖的名字,不管是顯式返回(好比返回一個 String、View 或者 ModelAndView )仍是隱式返回(好比基於約定的返回)。Spring中的視圖由一個視圖名標識,並由視圖解析器來渲染。Spring有很是多內置的視圖解析器。下表列出了大部分,表後也給出了一些例子。

表21.3 視圖解析器

視圖解析器 描述
 AbstractCachingViewResolver  一個抽象的視圖解析器類,提供了緩存視圖的功能。一般視圖在可以被使用以前須要通過準備。繼承這個基類的視圖解析器便可以得到緩存視圖的能力。
 XmlViewResolver  視圖解析器接口 ViewResolver 的一個實現,該類接受一個XML格式的配置文件。該XML文件必須與Spring XML的bean工廠有相同的DTD。默認的配置文件名是 /WEB-INF/views.xml 
 ResourceBundleViewResolver  視圖解析器接口 ViewResolver 的一個實現,採用bundle根路徑所指定的 ResourceBundle 中的 bean 定義做爲配置。通常bundle都定義在classpath路徑下的一個配置文件中。默認的配置文件名爲 views.properties 
 UrlBasedViewResolver   ViewResolver 接口的一個簡單實現。它直接使用URL來解析到邏輯視圖名,除此以外不須要其餘任何顯式的映射聲明。若是你的邏輯視圖名與你真正的視圖資源名是直接對應的,那麼這種直接解析的方式就很方便,不須要你再指定額外的映射。
 InternalResourceViewResolver   UrlBasedViewResolver 的一個好用的子類。它支持內部資源視圖(具體來講,Servlet和JSP)、以及諸如 JstlView和TilesView 等類的子類。You can specify the view class for all views generated by this resolver by using setViewClass(..)。更多的細節,請見 UrlBasedViewResolver 類的java文檔。
 VelocityViewResolver / FreeMarkerViewResolver  UrlBasedViewResolver下的實用子類,支持 Velocity 視圖 VelocityView (Velocity模板)和 FreeMarker 視圖 FreeMarkerView 以及它們對應子類。
 ContentNegotiatingViewResolver  視圖解析器接口 ViewResolver 的一個實現,它會根據所請求的文件名或請求的Accept頭來解析一個視圖。更多細節請見21.5.4 內容協商視圖解析器"ContentNegotiatingViewResolver"一小節。

咱們能夠舉個例子,假設這裏使用的是JSP視圖技術,那麼咱們能夠使用一個基於URL的視圖解析器 UrlBasedViewResolver 。這個視圖解析器會將URL解析成一個視圖名,並將請求轉交給請求分發器來進行視圖渲染。

<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 ,後者會將請求交給 /WEB-INF/jsp/test.jsp 視圖去渲染。

若是須要在應用中使用多種不一樣的視圖技術,你能夠使用 ResourceBundleViewResolver :

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

 

 ResourceBundleViewResolver 會檢索由bundle根路徑下所配置的 ResourceBundle ,對於每一個視圖而言,其視圖類由[viewname].(class)屬性的值指定,其視圖url由[viewname].url屬性的值指定。下一節將詳細講解視圖技術,你能夠在那裏找到更多例子。你還能夠看到,視圖還容許有基視圖,即properties文件中全部視圖都「繼承」的一個文件。經過繼承技術,你能夠爲衆多視圖指定一個默認的視圖基類。

 AbstractCachingViewResolver 的子類可以緩存已經解析過的視圖實例。關閉緩存特性也是能夠的,只須要將cache屬性設置爲false便可。另外,若是實在須要在運行時刷新某個視圖(好比修改了Velocity模板時),你能夠使用 removeFromCache(String viewName, Locale loc) 方法。`

21.5.2 視圖鏈

Spring支持同時使用多個視圖解析器。所以,你能夠配置一個解析器鏈,並作更多的事好比,在特定條件下覆寫一個視圖等。你能夠經過把多個視圖解析器設置到應用上下文(application context)中的方式來串聯它們。若是須要指定它們的次序,那麼設置 order 屬性便可。請記住,order屬性的值越大,該視圖解析器在鏈中的位置就越靠後。

在下面的代碼例子中,視圖解析器鏈中包含了兩個解析器:一個是 InternalResourceViewResolver ,它老是自動被放置在解析器鏈的最後;另外一個是 XmlViewResolver ,它用來指定Excel視圖。 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 和部分其餘的視圖解析器也存在這樣的狀況。具體的請查閱某個特定的視圖解析器的Java文檔,看它是否會report不存在的視圖。所以,若是不把 InternalResourceViewResolver 放置在解析器鏈的最後,將可能致使解析器鏈沒法徹底執行,由於 InternalResourceViewResolver 永遠都會 返回一個視圖。

21.5.3 視圖重定向

如前所述,控制器一般都會返回一個邏輯視圖名,而後視圖解析器會把它解析到一個具體的視圖技術上去渲染。對於一些能夠由Servlet或JSP引擎來處理的視圖技術,好比JSP等,這個解析過程一般是由 InternalResourceViewResolver和InternalResourceView 協做來完成的,而這一般會調用Servlet的 APIRequestDispatcher.forward(..) 方法或 RequestDispatcher.include(..) 方法,併發生一次內部的轉發(forward)或引用(include)。而對於其餘的視圖技術,好比Velocity、XSLT等,視圖自己的內容是直接被寫回響應流中的。

有時,咱們想要在視圖渲染以前,先把一個HTTP重定向請求發送回客戶端。好比,當一個控制器成功地接受到了POST過來的數據,而響應僅僅是委託另外一個控制器來處理(好比一次成功的表單提交)時,咱們但願發生一次重定向。在這種場景下,若是隻是簡單地使用內部轉發,那麼意味着下一個控制器也能看到此次POST請求攜帶的數據,這可能致使一些潛在的問題,好比可能會與其餘指望的數據混淆,等。此外,另外一種在渲染視圖前對請求進行重定向的需求是,防止用戶屢次提交表單的數據。此時若使用重定向,則瀏覽器會先發送第一個POST請求;請求被處理後瀏覽器會收到一個重定向響應,而後瀏覽器直接被重定向到一個不一樣的URL,最後瀏覽器會使用重定向響應中攜帶的URL發起一次GET請求。所以,從瀏覽器的角度看,當前所見的頁面並非POST請求的結果,而是一次GET請求的結果。這就防止了用戶因刷新等緣由意外地提交了屢次一樣的數據。此時刷新會從新GET一次結果頁,而不是把一樣的POST數據再發送一遍。

重定向視圖 RedirectView

強制重定向的一種方法是,在控制器中建立並返回一個Spring重定向視圖 RedirectView 的實例。它會使得 DispatcherServlet 放棄使用通常的視圖解析機制,由於你已經返回一個(重定向)視圖給 DispatcherServlet 了,因此它會構造一個視圖來知足渲染的需求。緊接着RedirectView會調用 HttpServletResponse.sendRedirect() 方法,發送一個HTTP重定向響應給客戶端瀏覽器。

若是你決定返回 RedirectView ,而且這個視圖實例是由控制器內部建立出來的,那咱們更推薦在外部配置重定向URL而後注入到控制器中來,而不是寫在控制器裏面。這樣它就能夠與視圖名一塊兒在配置文件中配置。關於如何實現這個解耦,請參考 重定向前綴——redirect:一小節。

向重定向目標傳遞數據

模 型中的全部屬性默認都會考慮做爲URI模板變量被添加到重定向URL中。剩下的其餘屬性,若是是基本類型或者基本類型的集合或數組,那它們將被自動添加到 URL的查詢參數中去。若是model是專門爲該重定向所準備的,那麼把全部基本類型的屬性添加到查詢參數中多是咱們指望那個的結果。可是,在包含註解 的控制器中,model可能包含了專門做爲渲染用途的屬性(好比一個下拉列表的字段值等)。爲了不把這樣的屬性也暴露在URL中, @RequestMapping 方法能夠聲明一個 RedirectAttributes 類型的方法參數,用它來指定專門供重定向視圖 RedirectView 取用的屬性。若是重定向成功發生,那麼 RedirectAttributes 對象中的內容就會被使用;不然則使用模型model中的數據。

 RequestMappingHandlerAdapter 提供了一個ignoreDefaultModelOnRedirect "標誌。它被用來標記默認Model中的屬性永遠不該該被用於控制器方法的重定向中。控制器方法應該聲明一個 RedirectAttributes 類的參數。若是不聲明,那就沒有參數被傳遞到重定向的視圖 RedirectView 中。在MVC命名空間或MVC Java編程配置方式中,爲了維持向後的兼容性,這個標誌都仍被保持爲 false 。但若是你的應用是一個新的項目,那麼咱們推薦把它的值設置成 true 。

請注意,當前請求URI中的模板變量會在填充重定向URL的時候自動對應用可見,而不須要顯式地在Model或 RedirectAttributes 中再添加屬性。請看下面的例子:

@RequestMapping(path = "/files/{path}", method = RequestMethod.POST)
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

 

另一種向重定向目標傳遞數據的方法是經過 閃存屬性(Flash Attributes)。與其餘重定向屬性不一樣,flash屬性是存儲在HTTP session中的(所以不會出如今URL中)。更多內容,請參考 21.6 使用閃存屬性一節。

重定向前綴——redirect:

儘管使用 RedirectView 來作重定向能工做得很好,但若是控制器自身仍是須要建立一個 RedirectView , 那無疑控制器仍是瞭解重定向這麼一件事情的發生。這仍是有點不盡完美,不一樣範疇的耦合仍是太強。控制器其實不該該去關心響應會如何被渲染。In general it should operate only in terms of view names that have been injected into it.

一個特別的視圖名前綴能完成這個解耦: redirect: 。若是返回的視圖名中含有 redirect: 前綴,那麼 UrlBasedViewResolver (及它的全部子類)就會接受到這個信號,意識到這裏須要發生重定向。而後視圖名剩下的部分會被解析成重定向URL。

這種方式與經過控制器返回一個重定向視圖 RedirectView 所達到的效果是同樣的,不過這樣一來控制器就能夠只專一於處理並返回邏輯視圖名了。若是邏輯視圖名是這樣的形式: redirect:/myapp/some/resource ,他們重定向路徑將以Servlet上下文做爲相對路徑進行查找,而邏輯視圖名若是是這樣的形式: redirect:http://myhost.com/some/arbitrary/path ,那麼重定向URL使用的就是絕對路徑。

注意的是,若是控制器方法註解了 @ResponseStatus ,那麼註解設置的狀態碼值會覆蓋 RedirectView 設置的響應狀態碼值。

重定向前綴——forward:

對於最終會被 UrlBasedViewResolver 或其子類解析的視圖名,你能夠使用一個特殊的前綴: forward: 。這會致使一個 InternalResourceView 視圖對象的建立(它最終會調用 RequestDispatcher.forward() 方法),後者會認爲視圖名剩下的部分是一個URL。所以,這個前綴在使用 InternalResourceViewResolver 和 InternalResourceView 時並無特別的做用(好比對於JSP來講)。但當你主要使用的是其餘的視圖技術,而又想要強制把一個資源轉發給Servlet/JSP引擎進行處理時,這個前綴可能就頗有用(或者,你也可能同時串聯多個視圖解析器)。

與 redirect: 前綴同樣,若是控制器中的視圖名使用了 forward: 前綴,控制器自己並不會發覺任何異常,它關注的仍然只是如何處理響應的問題。

21.5.4 內容協商解析器ContentNegotiatingViewResolver

 ContentNegotiatingViewResolver 本身並不會解析視圖,而是委託給其餘的視圖解析器去處理。

The  ContentNegotiatingViewResolver  does not resolve views itself but rather delegates to other view resolvers, selecting the view that resembles the representation requested by the client. Two strategies exist for a client to request a representation from the server:

  • Use a distinct URI for each resource, typically by using a different file extension in the URI. For example, the URI  <http://www.example.com/users/fred.pdf>  requests a PDF representation of the user fred, and  <http://www.example.com/users/fred.xml>  requests an XML representation.
  • Use the same URI for the client to locate the resource, but set the Accept HTTP request header to list the media types that it understands. For example, an HTTP request for  <http://www.example.com/users/fred>  with an Accept header set to  application/pdf  requests a PDF representation of the user fred, while  <http://www.example.com/users/fred>  with an Accept header set to text/xml requests an XML representation. This strategy is known as content negotiation.
\[Note\] Note
 

One issue with the Accept header is that it is impossible to set it in a web browser within HTML. For example, in Firefox, it is fixed to:

 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 

For this reason it is common to see the use of a distinct URI for each representation when developing browser based web applications.

To support multiple representations of a resource, Spring provides the  ContentNegotiatingViewResolver  to resolve a view based on the file extension or Accept header of the HTTP request.  ContentNegotiatingViewResolver  does not perform the view resolution itself but instead delegates to a list of view resolvers that you specify through the bean property  ViewResolvers .

The  ContentNegotiatingViewResolver  selects an appropriate View to handle the request by comparing the request media type(s) with the media type (also known as Content-Type) supported by the  View  associated with each of its  ViewResolvers . The first  View  in the list that has a compatible Content- Type returns the representation to the client. If a compatible view cannot be supplied by the  ViewResolver  chain, then the list of views specified through the  DefaultViews  property will be consulted. This latter option is appropriate for singleton  Views  that can render an appropriate representation of the current resource regardless of the logical view name. The Accept header may include wild cards, for example text/*, in which case a  View  whose Content-Type was text/xml is a compatible match.

To support custom resolution of a view based on a file extension, use a ContentNegotiationManager: see Section 21.16.6, "Content Negotiation".

Here is an example configuration of a 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"/>

 

The  InternalResourceViewResolver  handles the translation of view names and JSP pages, while the  BeanNameViewResolver  returns a view based on the name of a bean. (See "[Resolving views with the ViewResolver interface](mvc.html

###mvc-viewresolver-resolver "21.5.1 Resolving views with the ViewResolver interface" )" for more details on how Spring looks up and instantiates a view.) In this example, the content bean is a class that inherits from  AbstractAtomFeedView , which returns an Atom RSS feed. For more information on creating an Atom Feed representation, see the section Atom Views.

In the above configuration, if a request is made with an .html extension, the view resolver looks for a view that matches the  text/html  media type. The  InternalResourceViewResolver  provides the matching view for  text/html . If the request is made with the file extension .atom, the view resolver looks for a view that matches the application/atom+xml media type. This view is provided by the  BeanNameViewResolver  that maps to the  SampleContentAtomView  if the view name returned is content. If the request is made with the file extension .json, the  MappingJackson2JsonView  instance from the DefaultViews list will be selected regardless of the view name. Alternatively, client requests can be made without a file extension but with the Accept header set to the preferred media-type, and the same resolution of request to views would occur.

\[Note\] Note
 

If `ContentNegotiatingViewResolver's list of ViewResolvers is not configured explicitly, it automatically uses any ViewResolvers defined in the application context.

The corresponding controller code that returns an Atom RSS feed for a URI of the form  <http://localhost/content.atom>  or  <http://localhost/content>  with an Accept header of application/atom+xml is shown below.

@Controller
public class ContentController {

    private List<SampleContent> contentList = new ArrayList<SampleContent>();

    @RequestMapping(path="/content", method=RequestMethod.GET)
    public ModelAndView getContent() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("content");
        mav.addObject("sampleContentList", contentList);
        return mav;
    }

}

 

21.6 使用閃存屬性FlashAttributes

Flash屬性( flash attributes )提供了一個請求爲另外一個請求存儲有用屬性的方法。這在重定向的時候最常使用,好比常見的 POST/REDIRECT/GET 模式。Flash屬性會在重定向前被暫時地保存起來(一般是保存在session中),重定向後會從新被下一個請求取用並當即從原保存地移除。

爲支持flash屬性,Spring MVC提供了兩個抽象。 FlashMap 被用來存儲 flash 屬性,而用 FlashMapManager 來存儲、取回、管理FlashMap的實例。

對flash屬性的支持默認是啓用的,並不須要顯式聲明,不過沒用到它時它毫不會主動地去建立HTTP會話(session)。對於每一個請求,框架都會「傳進」一個FlashMap,裏面存儲了從上個請求(若是有)保存下來的屬性;同時,每一個請求也會「輸出」一個FlashMap,裏面保存了要給下個請求使用的屬性。兩個FlashMap實例在Spring MVC應用中的任何地點均可以經過 RequestContextUtils 工具類的靜態方法取得。

控制器一般不須要直接接觸 FlashMap 。通常是經過 @RequestMapping 方法去接受一個 RedirectAttributes 類型的參數,而後直接地往其中添加flash屬性。經過 RedirectAttributes 對象添加進去的flash屬性會自動被填充到請求的「輸出」FlashMap對象中去。相似地,重定向後「傳進」的 FlashMap 屬性也會自動被添加到服務重定向URL的控制器參數Model中去。

匹配請求所使用的flash屬性

flash 屬性的概念在其餘許多的Web框架中也存在,而且實踐證實有時可能會致使併發上的問題。這是由於從定義上講,flash屬性保存的時間是到下個請求接收到 以前。問題在於,「下一個」請求不必定恰好就是你要重定向到的那個請求,它有多是其餘的異步請求(好比polling請求或者資源請求等)。這會致使 flash屬性在到達真正的目標請求前就被移除了。

爲了減小這個問題發生的可能性,重定向視圖 RedirectView 會自動爲一個FlashMap實例記錄其目標重定向URL的路徑和查詢參數。而後,默認的 FlashMapManager 會在爲請求查找其該「傳進」的FlashMap時,匹配這些信息。

這並不能徹底解決重定向的併發問題,但極大程度地減小了這種可能性,由於它能夠從重定向URL已有的信息中來作匹配。所以,通常只有在重定向的場景下,咱們才推薦使用flash屬性。

21.7 URI構造

在Spring MVC中,使用了 UriComponentsBuilder 和 UriComponents 兩個類來提供一種構造和加密URI的機制。

好比,你能夠經過一個URI模板字符串來填充並加密一個URI:

UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}").build();

URI uri = uriComponents.expand("42", "21").encode().toUri();

 

請注意 UriComponents 是不可變對象。所以 expand() 與 encode() 操做在必要的時候會返回一個新的實例。

你也能夠使用一個URI組件實例對象來實現URI的填充與加密:

UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

 

在Servlet環境下, ServletUriComponentsBuilder 類提供了一個靜態的工廠方法,能夠用於從Servlet請求中獲取URL信息:

HttpServletRequest request = ...

// 主機名、schema, 端口號、請求路徑和查詢字符串都重用請求裏已有的值
// 替換了其中的"accountId"查詢參數

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();

 

或者,你也能夠選擇只複用請求中一部分的信息:

  // 重用主機名、端口號和context path
    // 在路徑後添加"/accounts"

    ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
            .path("/accounts").build()

 

或者,若是你的 DispatcherServlet 是經過名字(好比,/main/*)映射請求的,you can also have the literal part of the servlet mapping included:

   // Re-use host, port, context path
    // Append the literal part of the servlet mapping to the path
    // Append "/accounts" to the path

    ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
            .path("/accounts").build()

 

21.7.1 爲控制器和方法指定URI

Spring MVC也提供了構造指定控制器方法連接的機制。如下面代碼爲例子,假設咱們有這樣一個控制器:

@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {

    @RequestMapping("/bookings/{booking}")
    public String getBooking(@PathVariable Long booking) {

    // ...

    }
}

 

你能夠經過引用方法名字的辦法來準備一個連接:

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

 

在上面的例子中,咱們爲方法參數準備了填充值:一個long型的變量值21,以用於填充路徑變量並插入到URL中。另外,咱們還提供了一個值42,以用於填充其餘剩餘的URI變量,好比從類層級的請求映射中繼承來的 hotel 變量。若是方法還有更多的參數,你能夠爲那些不須要參與URL構造的變量賦予null值。通常而言,只有 @PathVariable 和 @RequestParam 註解的參數才與URL的構造相關。

還有其餘使用 MvcUriComponentsBuilder 的方法。好比,你能夠經過相似mock掉測試對象的方法,用代理來避免直接經過名字引用一個控制器方法(如下方法假設 MvcUriComponentsBuilder.on 方法已經被靜態導入):

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

 

上面的代碼例子中使用了 MvcUriComponentsBuilder 類的靜態方法。內部實現中,它依賴於 ServletUriComponentsBuilder 來 從當前請求中抽取schema、主機名、端口號、context路徑和servlet路徑,並準備一個基本URL。大多數狀況下它能良好工做,但有時還不 行。好比,在準備連接時,你可能在當前請求的上下文(context)以外(好比,執行一個準備連接links的批處理),或你可能須要爲路徑插入一個前 綴(好比一個地區性前綴,它從請求中被移除,而後又從新被插入到連接中去)。

對於上面所提的場景,你能夠使用重載過的靜態方法 fromXxx ,它接收一個 UriComponentsBuilder 參數,而後從中獲取基本URL以便使用。或你也能夠使用一個基本URL建立一個 MvcUriComponentsBuilder 對象,而後使用實例對象的 fromXxx 方法。以下面的示例:

UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

 

在視圖中爲控制器和方法指定URI

21.8 地區信息(Locales)

Spring的架構中的不少層面都提供了對國際化的支持,一樣支持Spring MVC框架也能提供。 DispatcherServlet 爲你提供了自動使用用戶的地區信息來解析消息的能力。而這,是經過 LocaleResolver 對象來完成的。

一個請求進入處理時, DispatcherServlet 會查找一個地區解析器。若是找到,就嘗試使用它來設置地區相關的信息。經過調用 RequestContext.getLocale() 都能取到地區解析器所解析到的地區信息。

此外,若是你須要自動解析地區信息,你能夠在處理器映射前加一個攔截器(關於更多處理器映射攔截器的知識,請參見21.4.1 使用HandlerInterceptor攔截請求一小節),並用它來根據條件或環境不一樣,好比,根據請求中某個參數值,來更改地區信息。

21.8.1 獲取時區信息

除了獲取客戶端的地區信息外,有時他們所在的時區信息也很是有用。 LocaleContextResolver 接口爲 LocaleResolver 提供了拓展點,容許解析器在 LocaleContext 中提供更多的信息,這裏面就能夠包含時區信息。

若是用戶的時區信息能被解析到,那麼你總能夠經過 RequestContext.getTimeZone() 方法得到。時區信息會自動被 SpringConversionService 下注冊的日期/時間轉換器 Converter 及格式化對象 Formatter 所使用。

21.8.2 Accept請求頭解析器 AcceptHeaderLocaleResolver 

 AcceptHeaderLocaleResolver 解析器會檢查客戶端(好比,瀏覽器,等)所發送的請求中是否攜帶 accept-language 請求頭。一般,該請求頭字段中包含了客戶端操做系統的地區信息。不過請注意,該解析器不支持時區信息的解析。

 CookieLocaleResolver 解析會檢查客戶端是否有 Cookie ,裏面可能存放了地區 Locale 或時區 TimeZone 信息。若是檢查到相應的值,解析器就使用它們。經過該解析器的屬性,你能夠指定cookie的名稱和其最大的存活時間。請見下面的例子,它展現瞭如何定義一個 CookieLocaleResolver :

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

    <property name="cookieName" value="clientlanguage"/>

    <!-- 單位爲秒。若設置爲-1,則cookie不會被持久化(客戶端關閉瀏覽器後即被刪除) -->
    <property name="cookieMaxAge" value="100000">

</bean>

 

表21.4.  CookieLocaleResolver 支持的屬性

屬性 默認值 描述
cookieName classname + LOCALE cookie名
cookieMaxAge Integer.MAX_INT cookie被保存在客戶端的最長時間。若是該值爲-1,那麼cookie將不會被持久化,在客戶端瀏覽器關閉以後就失效了
cookiePath / 限制了cookie僅對站點下的某些特定路徑可見。若是指定了cookiePath,那麼cookie將僅對該路徑及其子路徑下的全部站點可見

21.8.4 Session解析器 SessionLocaleResolver 

 SessionLocaleResolver 容許你從 session 中取得可能與用戶請求相關聯的地區 Locale 和時區 TimeZone 信息。與 CookieLocaleResolver 不一樣,這種存取策略僅將Servlet容器的 HttpSession 中相關的地區信息存取到本地。所以,這些設置僅會爲該會話(session)臨時保存, session 結束後,這些設置就會失效。

不過請注意,該解析器與其餘外部 session 管理機制,好比 Spring的Session 項目等,並無直接聯繫。該 SessionLocaleResolver 僅會簡單地從與當前請求 HttpServletRequest 相關的 HttpSession 對象中,取出對應的屬性,並修改其值,僅此而已。

21.8.5 地區更改攔截器 LocaleChangeInterceptor 

You can enable changing of locales by adding the  LocaleChangeInterceptor  to one of the handler mappings (see [Section 21.4, "Handler mappings"](mvc.html

###mvc-handlermapping "21.4 Handler mappings" )). It will detect a parameter in the request and change the locale. It calls setLocale() on the  LocaleResolver  that also exists in the context. The following example shows that calls to all *.view resources containing a parameter named  siteLanguage  will now change the locale. So, for example, a request for the following URL,  <http://www.sf.net/home.view?siteLanguage=nl>  will change the site language to Dutch.

你能夠在處理器映射(詳見21.4 處理器映射(Handler mappings)小節)前添加一個LocaleChangeInterceptor攔截器來更改地區信息。它能檢測請求中的參數,並根據其值相應地更新地區信息。它經過調用 LocaleResolver 的 setLocale() 方法來更改地區。下面的代碼配置展現瞭如何爲全部請求 *.view 路徑而且攜帶了 siteLanguage 參數的資源請求更改地區。舉個例子,一個URL爲 <http://www.sf.net/home.view?siteLanguage=nl> 的請求將會將站點語言更改成荷蘭語。

<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

 

21.9 主題 themes

21.9.1 關於主題:概覽

You can apply Spring Web MVC framework themes to set the overall look-and-feel of your application, thereby enhancing user experience. A theme is a collection of static resources, typically style sheets and images, that affect the visual style of the application.

你能夠使用Spring Web MVC框架提供的主題來爲整站的應用設置皮膚/主題(look-and-feel),這能夠提升用戶體驗。主題 是指一系列靜態資源的集合,而且主要是樣式表和圖片,它們決定了你的應用的視覺風格。

21.9.2 定義主題

要在你的應用中使用主題,你必須實現一個 org.springframework.ui.context.ThemeSource 接口。 WebApplicationContext 接口繼承了 ThemeSource 接口,但主要的工做它仍是委託給接口具體的實現來完成。默認的實現是 org.springframework.ui.context.support.ResourceBundleThemeSource ,它會從classpath的根路徑下去加載配置文件。若是須要定製 ThemeSource 的實現,或要配置 ResourceBundleThemeSource 的基本前綴名(base name prefix),你能夠在應用上下文(application context)下注冊一個名字爲保留名 themeSource 的 bean ,web應用的上下文會自動檢測名字爲themeSource的bean並使用它。

使用的是 ResourceBundleThemeSource 時,一個主題能夠定義在一個簡單的配置文件中。該配置文件會列出全部組成了該主題的資源。下面是個例子:

 styleSheet=/themes/cool/style.css
    background=/themes/cool/img/coolBg.jpg

 

屬性的鍵(key)是主題元素在視圖代碼中被引用的名字。對於JSP視圖來講,通常經過 spring:theme 這個定製化的標籤(tag)來作,它與 spring:message 標籤很類似。如下的JSP代碼即便用了上段代碼片斷中定義的主題,用以定製總體的皮膚:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
    <head>
        <link rel="stylesheet" href="<spring:theme code=''styleSheet''/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code=''background''/>">
        ...
    </body>
</html>

 

默認狀況下 ResourceBundleThemeSource 使用的基本名前綴(base name prefix)是空值。也便是說,配置文件是從根classpath路徑下加載的。所以,你須要把主題的定義文件 cool.properties 放在classpath的根路徑目錄下,好比, /WEB-INF/classes。ResourceBundleThemeSource 採用了Java的標準資源 bundle 加載機制,徹底支持國際化主題。好比,你能夠建立一個 /WEB-INF/classes/cool_nl.properties 配置文件,並在其中引用一副有荷蘭文的背景圖片。

21.9.3 主題解析器

上一小節,咱們講了如何定義主題,定義以後,你要決定使用哪一個主題。 DispatcherServlet 會查找一個名稱爲 themeResolver 的 bean 以肯定使用哪一個 ThemeResolver 的實現。主題解析器的工做原理與地區解析器 LocaleResolver 的工做原理大同小異。它會檢測,對於一個請求來講,應該使用哪一個主題,同時它也能夠修改一個請求所應應用的主題。Spring提供了下列的這些主題解析器:

表21.5. ThemeResolver接口的實現

類名 描述
 FixedThemeResolver  選擇一個固定的主題,這是經過設置 defaultThemeName 這個屬性值實現的
 SessionThemeResolver  請求相關的主題保存在用戶的HTTP會話(session)中。對於每一個會話來講,它只須要被設置一次,但它不能在會話之間保存
 CookieThemeResolver  選中的主題被保存在客戶端的cookie中

Spring也提供了一個主題更改攔截器 ThemeChangeInterceptor ,以支持主題的更換。這很容易作到,只須要在請求中攜帶一個簡單的請求參數便可。

21.10 Spring的multipart(文件上傳)支持

21.10.1 概述

Spring內置對多路上傳的支持,專門用於處理web應用中的文件上傳。你能夠經過註冊一個可插拔的 MultipartResolver 對象來啓用對文件多路上傳的支持。該接口在定義於 org.springframework.web.multipart 包下。Spring爲通常的文件上傳提供了 MultipartResolver 接口的一個實現,爲Servlet 3.0多路請求的轉換提供了另外一個實現。

默 認狀況下,Spring的多路上傳支持是不開啓的,由於有些開發者但願由本身來處理多路請求。若是想啓用Spring的多路上傳支持,你須要在web應用 的上下文中添加一個多路傳輸解析器。每一個進來的請求,解析器都會檢查是否是一個多部分請求。若發現請求是完整的,則請求按正常流程被處理;若是發現請求是 一個多路請求,則你在上下文中註冊的 MultipartResolver 解析器會被用來處理該請求。以後,請求中的多路上傳屬性就與其餘屬性同樣被正常對待了。【最後一句翻的很差,multipart翻譯成多路仍是多部分還在斟酌中。望閱讀者注意此處。】

21.10.2 使用 MultipartResolver 與 Commons FileUpload 傳輸文件

下面的代碼展現瞭如何使用一個通用的多路上傳解析器 CommonsMultipartResolver :

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- 支持的其中一個屬性,支持的最大文件大小,以字節爲單位 -->
    <property name="maxUploadSize" value="100000"/>

</bean>

 

固然,要讓多路解析器正常工做,你須要在classpath路徑下準備必須的jar包。若是使用的是通用的多路上傳解析器 CommonsMultipartResolver ,你所須要的jar包是 commons-fileupload.jar 。

當Spring的 DispatcherServlet 檢測到一個多部分請求時,它會激活你在上下文中聲明的多路解析器並把請求交給它。解析器會把當前的 HttpServletRequest 請求對象包裝成一個支持多路文件上傳的請求對象 MultipartHttpServletRequest 。有了 MultipartHttpServletRequest 對象,你不只能夠獲取該多路請求中的信息,還能夠在你的控制器中得到該多路請求的內容自己。

21.10.3 Servlet 3.0下的MultipartResolver

要使用基於Servlet 3.0的多路傳輸轉換功能,你必須在web.xml中爲 DispatcherServlet 添加一個 multipart-config 元素,或者經過Servlet編程的方法使用 javax.servlet.MultipartConfigElement 進行註冊,或你本身定製了本身的Servlet類,那你必須使用 javax.servlet.annotation.MultipartConfig 對其進行註解。其餘諸如最大文件大小或存儲位置等配置選項都必須在這個Servlet級別進行註冊,由於Servlet 3.0不容許在解析器 MultipartResolver 的層級配置這些信息。

當你經過以上任一種方式啓用了Servlet 3.0多路傳輸轉換功能,你就能夠把一個 StandardServletMultipartResolver 解析器添加到你的Spring配置中去了:

<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

 

21.10.4 處理表單中的文件上傳

當解析器 MultipartResolver 完成處理時,請求便會像其餘請求同樣被正常流程處理。首先,建立一個接受文件上傳的表單將容許用於直接上傳整個表單。編碼屬性( enctype="multipart/form-data" )能讓瀏覽器知道如何對多路上傳請求的表單進行編碼(encode)。

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="/form" enctype="multipart/form-data">
            <input type="text" name="name"/>
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

 

下一步是建立一個能處理文件上傳的控制器。這裏須要的控制器與通常註解了@Controller的控制器基本同樣,除了它接受的方法參數類型是 MultipartHttpServletRequest ,或 MultipartFile 。

@Controller
public class FileUploadController {

    @RequestMapping(path = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

 

請留意 @RequestParam 註解是如何將方法參數對應到表單中的定義的輸入字段的。在上面的例子中,咱們拿到了byte[]文件數據,只是沒對它作任何事。在實際應用中,你可能會將它保存到數據庫、存儲在文件系統上,或作其餘的處理。

當使用Servlet 3.0的多路傳輸轉換時,你也能夠使用 javax.servlet.http.Part 做爲方法參數:

@Controller
public class FileUploadController {

    @RequestMapping(path = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") Part file) {

        InputStream inputStream = file.getInputStream();
        // store bytes from uploaded file somewhere

        return "redirect:uploadSuccess";
    }

}

 

21.10.5 處理客戶端發起的文件上傳請求

在 使用了RESTful服務的場景下,非瀏覽器的客戶端也能夠直接提交多路文件請求。上一節講述的全部例子與配置在這裏也都一樣適用。但與瀏覽器不一樣的是, 提交的文件和簡單的表單字段,客戶端發送的數據能夠更加複雜,數據能夠指定爲某種特定的內容類型(content type)——好比,一個多路上傳請求可能第一部分是個文件,而第二部分是個JSON格式的數據:

 POST /someUrl
    Content-Type: multipart/mixed

    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
    Content-Disposition: form-data; name="meta-data"
    Content-Type: application/json; charset=UTF-8
    Content-Transfer-Encoding: 8bit

    {
        "name": "value"
    }
    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
    Content-Disposition: form-data; name="file-data"; filename="file.properties"
    Content-Type: text/xml
    Content-Transfer-Encoding: 8bit
    ... File Data ...

 

對於名稱爲meta-data的部分,你能夠經過控制器方法上的 @RequestParam("meta-data") String metadata 參數來得到。但對於那部分請求體中爲JSON格式數據的請求,你可能更想經過接受一個對應的強類型對象,就像@RequestBody經過 HttpMessageConverter 將通常請求的請求體轉換成一個對象同樣。

這是可能的,你能夠使用 @RequestPart 註解來實現,而非 @RequestParam 。該註解將使得特定多路請求的請求體被傳給 HttpMessageConverter ,而且在轉換時考慮多路請求中不一樣的內容類型參數'Content-Type'

@RequestMapping(path = "/someUrl", method = RequestMethod.POST)
public String onSubmit(@RequestPart("meta-data") MetaData metadata, @RequestPart("file-data") MultipartFile file) {

    // ...

}

 

請注意 MultipartFile 方法參數是如何可以在 @RequestParam 或 @RequestPart 註解下互用的,兩種方法都能拿到數據。但,這裏的方法參數 @RequestPart("meta-data") MetaData 則會由於請求中的內容類型請求頭'Content-Type'被讀入成爲JSON數據,而後再經過 MappingJackson2HttpMessageConverter 被轉換成特定的對象。

21.11 異常處理

21.11.1 處理器異常解析器HandlerExceptionHandler

Spring的處理器異常解析器 HandlerExceptionResolver 接口的實現負責處理各種控制器執行過程當中出現的異常。某種程度上講, HandlerExceptionResolver 與你在web應用描述符 web.xml 文 件中能定義的異常映射(exception mapping)很相像,不過它比後者提供了更靈活的方式。好比它能提供異常被拋出時正在執行的是哪一個處理器這樣的信息。而且,一個更靈活 (programmatic)的異常處理方式能夠爲你提供更多選擇,使你在請求被直接轉向到另外一個URL以前(與你使用Servlet規範的異常映射是一 樣的)有更多的方式來處理異常。

實現 HandlerExceptionResolver 接口並不是實現異常處理的惟一方式,它只是提供了 resolveException(Exception, Hanlder) 方法的一個實現而已,方法會返回一個 ModelAndView 。除此以外,你還能夠框架提供的 SimpleMappingExceptionResolver 或在異常處理方法上註解 @ExceptionHandler 。 SimpleMappingExceptionResolver 容許你獲取可能拋出的異常類的名字,並把它映射到一個視圖名上去。這與Servlet API提供的異常映射特性是功能等價的,但你也能夠基於此實現粒度更精細的異常映射。而 @ExceptionHandler 註解的方法則會在異常拋出時被調用以處理該異常。這樣的方法能夠定義在 @Controller 註解的控制器類裏,也能夠定義在 @ControllerAdvice 類中,後者能夠使該異常處理方法被應用到更多的 @Controller 控制器中。下一小節將提供更爲詳細的信息。

21.11.2 @ExceptionHandler註解

 HandlerExceptionResolver 接口以及 SimpleMappingExceptionResolver 解析器類的實現使得你能聲明式地將異常映射到特定的視圖上,還能夠在異常被轉發(forward)到對應的視圖前使用Java代碼作些判斷和邏輯。不過在一些場景,特別是依靠 @ResponseBody 返回響應而非依賴視圖解析機制的場景下,直接設置響應的狀態碼並將客戶端須要的錯誤信息直接寫回響應體中,多是更方便的方法。

你也能夠使用 @ExceptionHandler 方法來作到這點。若是 @ExceptionHandler 方法是在控制器內部定義的,那麼它會接收並處理由控制器(或其任何子類)中的 @RequestMapping 方法拋出的異常。若是你將 @ExceptionHandler 方法定義在 @ControllerAdvice 類中,那麼它會處理相關控制器中拋出的異常。下面的代碼就展現了一個定義在控制器內部的 @ExceptionHandler 方法:

@Controller
public class SimpleController {

    // @RequestMapping methods omitted ...

    @ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleIOException(IOException ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

 

此外, @ExceptionHandler 註解還能夠接受一個異常類型的數組做爲參數值。若拋出了已在列表中聲明的異常,那麼相應的 @ExceptionHandler 方法將會被調用。若是沒有給註解任何參數值,那麼默認處理的異常類型將是方法參數所聲明的那些異常。

與標準的控制器@RequestMapping註解處理方法同樣, @ExceptionHandler 方法的方法參數和返回值也能夠很靈活。好比,在Servlet環境下方法能夠接收HttpServletRequest參數,而Portlet環境下方法能夠接收PortletRequest參數。返回值能夠是String類型——這種狀況下會被解析爲視圖名——能夠是 ModelAndView 類型的對象,也能夠是ResponseEntity。或者你還能夠在方法上添加@ResponseBody註解以使用消息轉換器會轉換信息爲特定類型的數據,而後把它們寫回到響應流中。

21.11.3 處理通常的Spring MVC異常

處理請求的過程當中,Spring MVC可能會拋出一些的異常。 SimpleMappingExceptionResolver 能夠根據須要很方便地將任何異常映射到一個默認的錯誤視圖。但,若是客戶端是經過自動檢測響應的方式來分發處理異常的,那麼後端就須要爲響應設置對應的狀態碼。根據拋出異常的類型不一樣,可能須要設置不一樣的狀態碼來標識是客戶端錯誤(4xx)仍是服務器端錯誤(5xx)。

默認處理器異常解析器 DefaultHandlerExceptionResolver 會將Spring MVC拋出的異常轉換成對應的錯誤狀態碼。該解析器在MVC命名空間配置或MVC Java配置的方式下默認已經被註冊了,另外,經過DispatcherServlet註冊也是可行的(即不使用MVC命名空間或Java編程方式進行配置的時候)。下表列出了該解析器能處理的一些異常,及他們對應的狀態碼。

異常 HTTP狀態碼
 BindException  400 (無效請求)
 ConversionNotSupportedException  500 (服務器內部錯誤)
 HttpMediaTypeNotAcceptableException  406 (不接受)
 HttpMediaTypeNotSupportedException  415 (不支持的媒體類型)
 HttpMessageNotReadableException  400 (無效請求)
 HttpMessageNotWritableException  500 (服務器內部錯誤)
 HttpRequestMethodNotSupportedException  405 (不支持的方法)
 MethodArgumentNotValidException  400 (無效請求)
 MissingServletRequestParameterException  400 (無效請求)
 MissingServletRequestPartException  400 (無效請求)
 NoHandlerFoundException  404 (請求未找到)
 NoSuchRequestHandlingMethodException  404 (請求未找到)
 TypeMismatchException  400 (無效請求)
 MissingPathVariableException  500 (服務器內部錯誤)
 NoHandlerFoundException  404 (請求未找到)

如下待翻譯。

The DefaultHandlerExceptionResolver works transparently by setting the status of the response. However, it stops short of writing any error content to the body of the response while your application may need to add developer- friendly content to every error response for example when providing a REST API. You can prepare a ModelAndView and render error content through view resolution -- i.e. by configuring a ContentNegotiatingViewResolver, MappingJackson2JsonView, and so on. However, you may prefer to use @ExceptionHandler methods instead.

If you prefer to write error content via @ExceptionHandler methods you can extend ResponseEntityExceptionHandler instead. This is a convenient base for @ControllerAdvice classes providing an @ExceptionHandler method to handle standard Spring MVC exceptions and return ResponseEntity. That allows you to customize the response and write error content with message converters. See the ResponseEntityExceptionHandler javadocs for more details.

21.11.4 使用@ResponseStatus註解業務異常

業務異常能夠使用 @ResponseStatus 來註解。當異常被拋出時, ResponseStatusExceptionResolver 會設置相應的響應狀態碼。 DispatcherServlet 會默認註冊一個 ResponseStatusExceptionResolver 以供使用。

21.11.5 Servlet默認容器錯誤頁面的定製化

當響應的狀態碼被設置爲錯誤狀態碼,而且響應體中沒有內容時,Servlet容器一般會渲染一個HTML錯誤頁。若須要定製容器默認提供的錯誤頁,你能夠在web.xml中定義一個錯誤頁面<error-page>元素。在Servlet 3規範出來以前,該錯誤頁元素必須被顯式指定映射到一個具體的錯誤碼或一個異常類型。從Servlet 3開始,錯誤頁再也不須要映射到其餘信息了,這意味着,你指定的位置就是對Servlet容器默認錯誤頁的自定製了。

<error-page>
    <location>/error</location>
</error-page>

 

這裏錯誤頁的位置所在能夠是一個JSP頁面,或者其餘的一些URL,只要它指定容器裏任意一個 @Controller 控制器下的處理器方法:

寫回 HttpServletResponse 的錯誤信息和錯誤狀態碼能夠在控制器中經過請求屬性來獲取:

@Controller
public class ErrorController {

    @RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Map<String, Object> handle(HttpServletRequest request) {

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));

        return map;
    }

}

 

或者在JSP中這麼使用:

<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
    status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
    reason:<%=request.getAttribute("javax.servlet.error.message") %>
}

 

21.12 Web安全

Spring Security項目爲保護web應用免受惡意攻擊提供了一些特性。你能夠去看看參考文檔的這幾小節:"CSRF保護""安全響應頭"以及"Spring MVC集成"。不過並不是應用的全部特性都須要引入Spring Security。好比,須要CSRF保護的話,你僅須要簡單地在配置中添加一個過濾器 CsrfFilter 和處理器 CsrfRequestDataValueProcessor 。你能夠參考Spring MVC Showcase一節,觀看一個完整的展現。

另外一個選擇是使用其餘專一於Web安全的框架。HDIV就是這樣的一個框架,而且它能與Spring MVC良好集成。

21.13 "約定優於配置"的支持

對於許多項目來講,不打破已有的約定,對於配置等有可預測的默認值是很是適合的。如今,Spring MVC對 約定優於配置 這個實踐已經有了顯式的支持。這意味着什麼呢?意味着若是你爲項目選擇了一組命名上的約定/規範,你將能減小 大量 的配置項,好比一些必要的處理器映射、視圖解析器、ModelAndView實例的配置等。這能幫你快速地創建起項目原型,此外在某種程度上(一般是好的方面)維護了整個代碼庫的一致性should you choose to move forward with it into production.

具體來講,支持「約定優於配置」涉及到MVC的三個核心方面:模型、視圖,和控制器。

21.13.1 控制器類名-處理器映射ControllerClassNameHandlerMapping

 ControllerClassNameHandlerMapping 類是 HandlerMapping 接口的一個實現,它是經過一個約定來解析請求URL及處理該請求的 @Controller 控制器實例之間的映射關係。

請看下面一個簡單的控制器實現。請注意留意該類的 名稱

 

public class **ViewShoppingCartController** implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // 這個例子中方法的具體實現並不重要,故忽略。
    }

}

對應的Spring Web MVC配置文件以下所示:

<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

<bean id="**viewShoppingCart**" class="x.y.z.ViewShoppingCartController">
    <!-- 注入須要的依賴 -->
</bean>
 
 

 

 

 

 ControllerClassNameHandlerMapping 會查找當前應用上下文中註冊的全部處理器(也即控制器) bean ,並去除類名的 Controller 後綴做爲決定處理器映射的依據。所以,類名 ViewShoppingCartController 會被映射到匹配 /viewshoppingcart* 的請求URL上。

讓咱們多看幾個例子,這樣你對於核心的思想會立刻熟悉起來(注意URL中路徑是全小寫,而Controller控制器類名符合駝峯命名法):

  •  WelcomeController 將映射到/welcome*請求URL
  •  HomeController  將映射到/home*請求URL
  •  IndexController  將映射到/index*請求URL
  •  RegisterController  將映射到/register*請求URL

對於 MultiActionController 處理器類,映射規則要稍微複雜一些。請看下面的代碼,假設這裏的控制器都是 MultiActionController 的實現:

  •  AdminController 將映射到/admin/*請求URL
  •  CatalogController 將映射到/catalog/*請求URL

只要全部控制器 Controller 實現都遵循 xxxController 這樣的命名規範,那麼 ControllerClassNameHandlerMapping 能把你從定義維護一個 長長長  SimpleUrlHandlerMapping 映射表的重複工做中拯救出來。

 ControllerClassNameHandlerMapping 類繼承自  AbstractHandlerMapping 基類。所以,你能夠視它與其餘 HandlerMapping 實現同樣,定義你所須要的攔截器HandlerInterceptor實例及其餘全部東西。

21.13.2 模型ModelMap(ModelAndView)

 ModelMap 類其實就是一個豪華版的 Map,它使得你爲視圖展現須要所添加的對象都遵循一個顯而易見的約定被命名。請看下面這個  Controller 實現,並請注意,添加到 ModelAndView 中去的對象都沒有顯式地指定鍵名。

public class DisplayShoppingCartController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {

        List cartItems = // 拿到一個CartItem對象的列表
        User user = // 拿到當前購物的用戶User

        ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- 邏輯視圖名

        mav.addObject(cartItems); <-- 啊哈,直接添加的對象,沒有指定名稱
        mav.addObject(user); <-- 啊哈再來一次

        return mav;
    }
}

 

  ModelAndView 內部使用了一個ModelMap類,它是Map的一個實現,會自動爲添加進來的對象生成一個鍵名。爲添加對象生成名稱的策略是,若添加對象是一個純Java bean(a scalar object),好比User,那麼使用對象類的短類名(short class name)來做爲該對象的名稱。下面是一些例子,展現了爲添加到ModelMap實例中的純Java對象所生成的名稱:

  • 添加一個x.y.User實例,爲其生成的名稱爲user
  • 添加一個x.y.Registration實例,爲其生成的名稱爲registration
  • 添加一個x.y.Foo實例,爲其生成的名稱爲foo
  • 添加一個java.util.HashMap實例,爲其生成的名稱爲hashMap。這種狀況下,顯式地聲明一個鍵名可能更好,由於hashMap的約定並不是那麼符合直覺
  • 添加一個null值將致使程序拋出一個 IllegalArgumentException 參數非法異常。若你所添加的(多個)對象有可能爲null值,那你也須要顯式地指定它(們)的名字

啥?鍵名不能自動變複數形式麼?

Spring Web MVC的約定優於配置支持尚不能支持自動複數轉換。這意思是,你不能指望往 ModelAndView 中添加一個Person對象的List列表時,框架會自動爲其生成一個名稱people

這個決定是通過許多爭論後的結果,最終「最小驚喜原則」勝出併爲你們所接受。

爲集合Set或列表List生成鍵名所採起的策略,是先檢查集合的元素類型、拿到第一個對象的短類名,而後在其後面添加List做爲名稱。添加數組對象也是同理,儘管對於數組咱們就不需再檢查數組內容了。下面給出的幾個例子能夠闡釋一些東西,讓集合的名稱生成語義變得更加清晰:

  • 添加一個帶零個或多個x.y.User元素類型的數組x.y.User[],爲其生成的鍵名是userList
  • 添加一個帶零個或多個x.y.User元素類型的數組x.y.Foo[],爲其生成的鍵名是fooList
  • 添加一個帶零個或多個x.y.User元素類型的數組java.util.ArrayList,爲其生成的鍵名是userList
  • 添加一個帶零個或多個x.y.Foo元素類型的數組java.util.HashSet,爲其生成的鍵名是fooList
  • 一個 空的 java.util.ArrayList則根本不會被添加

21.13.3 視圖-請求與視圖名的映射

 RequestToViewNameTranslator 接口能夠在邏輯視圖名未被顯式提供的狀況下,決定一個可用的邏輯視圖View名。

 DefaultRequestToViewNameTranslator 可以將請求URL映射到邏輯視圖名上去,以下面代碼例子所示:

 
 
public class RegistrationController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // 處理請求……
        ModelAndView mav = new ModelAndView();
        // 向Model中添加須要的數據
        return mav;
        // 請注意這裏,沒有設置任何View對象或邏輯視圖名
    }

}
 
 

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 這個衆人皆知的bean將爲咱們自動生成視圖名 -->
    <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/>

    <bean class="x.y.RegistrationController">
        <!-- 若是須要,注入依賴 -->
    </bean>

    <!-- 請請求URL映射到控制器名 -->
    <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>

 

 

請注意在ha ndleRequest(...) 方法實現中,返回的ModelAndView對象上自始至終未設置任何View對象或邏輯視圖名。這是由 DefaultRequestToViewNameTranslator 完成的,它的任務就是從請求的URL中生成一個邏輯視圖名。在上面的例子中, RegistrationController 與配置的 ControllerClassNameHandlerMapping 一塊兒使用的結果是,一個URL爲 <http://localhost/registration.html> 的請求,會經由 DefaultRequestToViewNameTranslator 生成並對應到一個邏輯視圖名 registration 上。該邏輯視圖名又會由 InternalResourceViewResolverbean 解析到 /WEB-INF/jsp/registration.jsp 視圖上。

你無需顯式地定義一個 DefaultRequestToViewNameTranslatorbean 。若是默認的 DefaultRequestToViewNameTranslator 配置已能知足你的需求,那麼你無需配置,Spring Web MVC的 DispatcherServlet 會爲你實例化這樣一個默認的對象。

固然,若是你須要更改默認的設置,那你就須要手動地配置本身的 DefaultRequestToViewNameTranslatorbean 。關於可配置屬性的一些詳細信息,你能夠去諮詢 DefaultRequestToViewNameTranslator 類詳細的java文檔。

21.14 HTTP緩存支持

一個好的HTTP緩存策略能夠極大地提升一個web應用的性能及客戶端的體驗。談到HTTP緩存,它主要是與HTTP的響應頭 'Cache-Control' 相關,其次另外的一些響應頭好比 'Last-Modified' 和 'ETag' 等也會起必定的做用。

HTTP的響應頭 'Cache-Control' 主要幫助私有緩存(好比瀏覽器端緩存)和公共緩存(好比代理端緩存)瞭解它們應該若是緩存HTTP響應,以便後用。

ETag(實體標籤)是一個HTTP響應頭,可由支持HTTP/1.1的web應用服務器設置返回,主要用於標識給定的URL下的內容有無變化。能夠認爲它是 Last-Modified 頭的一個更精細的後續版本。當服務器端返回了一個ETag頭的資源表示時,客戶端就能夠在後續的GET請求中使用這個表示,通常是將它放在If-None-Match請求頭中。此時若內容沒有變化,服務器端會直接返回 304:  內容未更改

這一節將講解其餘一些在Spring Web MVC應用中配置HTTP緩存的方法。

21.14.1 HTTP請求頭Cache-Control

Spring MVC提供了許多方式來配置 "Cache-Control" 請求頭,支持在許多場景下使用它。關於該請求頭完整詳盡的全部用法,你能夠參考RFC 7234的第5.2.2小節,這裏咱們只講解最經常使用的幾種用法。

Spring MVC的許多API中都使用了這樣的慣例配置: setCachePeriod(int seconds) ,若返回值爲:

  • -1,則框架不會生成一個'Cache-Control'緩存控制指令響應頭
  • 0,則指示禁止使用緩存,服務器端返回緩存控制指令'Cache-Control: no-store'
  • 任何n > 0的值,則響應會被緩存n秒,並返回緩存控制指令'Cache-Control: max-age=n'

CacheControl構造器類被簡單的用來描述"Cache-Control"緩存控制指令,使你能更容易地建立本身的HTTP緩存策略。建立完了之後,CacheControl類的實例就能夠在Spring MVC的許多API中被傳入爲方法參數了。

// 緩存一小時 - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// 禁止緩存 - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// 緩存十天,對全部公共緩存和私有緩存生效
// 響應不能被公共緩存改變
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
                                    .noTransform().cachePublic();

 

21.14.2 對靜態資源的HTTP緩存支持

爲優化站點性能,靜態資源應該帶有恰當的 'Cache-Control' 值與其餘必要的頭。配置一個ResourceHttpRequestHandler處理器服務靜態資源請求不只會讀取文件的元數據並填充

'Last-Modified'

 

頭的值,正確配置時 'Cache-Control' 頭也會被填充。【這段翻得還不是很清晰】

你能夠設置 ResourceHttpRequestHandler 上的 cachePeriod 屬性值,或使用一個 CacheControl 實例來支持更細緻的指令:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public-resources/")
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
    }

}

 

XML中寫法則以下:

<mvc:resources mapping="/resources/**" location="/public-resources/">
    <mvc:cache-control max-age="3600" cache-public="true"/>
</mvc:resources>

 

21.14.3 在控制器中設置Cache-Control、ETag和Last-Modified響應頭

控制器能處理帶有 'Cache-Control'、'ETag' 及/或 'If-Modified-Since' 頭的請求,若是服務端在響應中設置了 'Cache-Control'  'Cache-Control' 響應頭,那麼咱們推薦在控制器內對這些請求頭進行處理。這涉及一些工做:計算最後更改時間long和/或請求的ETag值、與請求頭的 'If-Modified-Since' 值作比較,而且在資源未更改的狀況下在響應中返回一個304(資源未更改)狀態碼。

正如在"使用HttpEntity"一節中所講,控制器能夠經過 HttpEntity 類與請求/響應交互。返回 ResponseEntity 的控制器能夠在響應中包含HTTP緩存的信息,以下代碼所示:

@RequestMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
                .ok()
                .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
                .eTag(version) // 這裏也能操做最後修改時間lastModified,只不過沒有一一展現
                .body(book);
}

 

這樣作不只會在響應頭中設置'ETag''Cache-Control'相關的信息,同時也會 嘗試將響應狀態碼設置爲HTTP 304 Not Modified(資源未修改)及將響應體置空——若是客戶端攜帶的請求頭信息與控制器設置的緩存信息可以匹配的話。

若是但願在 @RequestMapping 方法上也能完成一樣的事,那麼你能夠這樣作:

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {

    long lastModified = // 1. 應用相關的方式計算獲得(application-specific calculation)

    if (request.checkNotModified(lastModified)) {
        // 2. 快速退出 — 不須要更多處理了
        return null;
    }

    // 3. 若資源更改了,那麼再進行請求處理階段,通常而言是準備響應內容
    model.addAttribute(...);
    return "myViewName";
}

 

這裏最重要的兩個地方是:調用 request.checkNotModified(lastModified) 方法,以及返回 null 。前者(方法調用)在返回 true 以前會將響應狀態碼設爲304;然後者,在檢查是否更改的方法調用返回 true 的基礎上直接將方法返回,這會通知Spring MVC再也不對請求作任何處理。

另外要注意的是,檢查資源是否發生了更改有3種方式:

  •  request.checkNotModified(lastModified) 方法會將傳入的參數值(最後修改時間)與請求頭 'If-Modified-Since' 的值進行比較
  •  request.checkNotModified(eTag) 方法會將傳入的參數值與請求頭 'ETag' 的值進行比較
  •  request.checkNotModified(eTag, lastModified) 方法會同時進行以上兩種比較。也便是說,只有在兩個比較都被斷定爲未修改時,服務器纔會返回一個304響應狀態碼 HTTP 304 Not Modified (資源未修改)

21.14.4 弱ETag(Shallow ETag)

對ETag的支持是由Servlet的過濾器 ShallowEtagHeaderFilter 提供的。它是純Servlet技術實現的過濾器,所以,它能夠與任何web框架無縫集成。 ShallowEtagHeaderFilter 過 濾器會建立一個咱們稱爲弱ETag(與強ETag相對,後面會詳述)的對象。過濾器會將渲染的JSP頁面的內容(包括其餘類型的內容)緩存起來,而後以此 生成一個MD5哈希值,並把這個值做爲ETag頭的值寫回響應中。下一次客戶端再次請求這個一樣的資源時,它會將這個ETag的值寫到If-None-Match頭中。過濾器會檢測到這個請求頭,而後再次把視圖渲染出來並比較兩個哈希值。若是比較的結果是相同的,那麼服務器會返回一個304。正如你所見,視圖仍然會被渲染,所以本質上這個過濾器並不是節省任何計算資源。惟一節省的東西,是帶寬,由於被渲染的響應不會被整個發送回客戶端。

請注意,這個策略節省的是網絡帶寬,而非CPU。由於對於每一個請求,完整的響應仍然須要被整個計算出來。而其餘在控制器層級實現的策略(上幾節所述的)能夠同時節省網絡帶寬及避免多餘計算。

你能夠在 web.xml 中配置 ShallowEtagHeaderFilter :

<filter>
    <filter-name>etagFilter</filter-name>
    <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>etagFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

 

若是是在Servlet 3.0以上的環境下,能夠這麼作:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new ShallowEtagHeaderFilter() };
    }

}

 

更多配置細節,請參考第21.15 基於代碼的Servlet容器初始化一小節。

21.15 基於代碼的Servlet容器初始化

在Servlet 3.0以上的環境下,你能夠經過編程的方式來配置Servlet容器了。你能夠徹底放棄web.xml,也能夠兩種配置方式同時使用。如下是一個註冊 DispatcherServlet 的例子:

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }

}

 

Spring MVC提供了一個 WebApplicationInitializer 接口,實現這個接口能保證你的配置能自動被檢測到並應用於Servlet 3容器的初始化中。 WebApplicationInitializer 有一個實現,是一個抽象的基類,名字叫 AbstractDispatcherServletInitializer 。有了它,要配置 DispatcherServlet 將變得更簡單,你只須要覆寫相應的方法,在其中提供 servlet 映射、 DispatcherServlet 所需配置的位置便可:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

 

以上的例子適用於使用基於Java配置的Spring應用。若是你使用的是基於XML的Spring配置方式,那麼請直接繼承 AbstractDispatcherServletInitializer 這個類:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

 

 AbstractDispatcherServletInitializer 一樣也提供了便捷的方式來添加過濾器 Filter 實例並使他們自動被映射到 DispatcherServlet 下:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }

}

 

每一個過濾器被添加時,默認的名稱都基於其類類型決定,而且它們會被自動地映射到DispatcherServlet下。

關於異步支持, AbstractDispatcherServletInitializer 的保護方法 isAsyncSupported 提供了一個集中的地方來開關 DispatcherServlet 上的這個配置,它會對全部映射到這個分發器上的過濾器生效。默認狀況下,這個標誌被設爲 true 。

最後,若是你須要對 DispatcherServlet 作進一步的定製,你能夠覆寫 createDispatcherServlet 這個方法。

21.16 配置Spring MVC

21.2.1 WebApplicationContext中特殊的bean類型小節和21.2.2 默認的DispatcherServlet配置小節解釋了何謂Spring MVC的特殊bean,以及 DispatcherServlet 所使用的默認實現。在這小節中,你將瞭解配置Spring MVC的其餘兩種方式:MVC Java編程配置,以及MVC XML命名空間。

MVC Java編程配置和MVC命名空間都提供了類似的默認配置,以覆寫 DispatcherServlet 的默認值。目標在於爲大多數應用軟件免去建立相同配置的麻煩,同時也想爲配置Spring MVC提供一個更易理解的指南、一個簡單的開始點,它只須要不多或不需任何關於底層配置的知識。

你 能夠選用MVC Java編程配置或MVC命名空間的方式,這徹底取決於你的喜愛。若你能讀完後面的小節,你會發現使用MVC Java編程配置的方式能更容易看到底層具體的配置項,而且能對建立的Spring MVC bean有更細粒度的定製空間。不過,咱們仍是從頭來看起吧。

21.16.1 啓用MVC Java編程配置或MVC命名空間

要啓用MVC Java編程配置,你須要在其中一個註解了 @Configuration 的類上添加 @EnableWebMvc 註解:

@Configuration
@EnableWebMvc
public class WebConfig {

}

 

要啓用XML命名空間,請在你的 DispatcherServlet 上下文中(若是沒有定義任何 DispatcherServlet 上下文,那麼就在根上下文中)添加一個 mvc:annotation-driven 元素:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

 

上面的簡單的聲明代碼,就已經默認註冊了一個equestMappingHandlerMapping 、一個 RequestMappingHandlerAdapter ,以及一個 ExceptionHandlerExceptionResolver ,以支持對使用了 @RequestMapping、@ExceptionHandler 及其餘註解的控制器方法的請求處理。

同時,上面的代碼還啓用瞭如下的特性:

  1. Spring 3風格的類型轉換支持。這是使用一個配置的轉換服務ConversionService實例,以及the JavaBeans PropertyEditors used for Data Binding.
  2. 使用 @NumberFormat 對數字字段進行格式化,類型轉換由 ConversionService 實現
  3. 使用 @DateTimeFormat 註解對 Date、Calendar、Long 及 Joda Time 類型的字段進行格式化
  4. 使用 @Valid 註解對 @Controller 輸入進行驗證——前提是classpath路徑下好比提供符合JSR-303規範的驗證器
  5. HTTP消息轉換 HttpMessageConverter 的支持,對註解了 @RequestMapping 或 @ExceptionHandler 方法的 @RequestBody 方法參數或 @ResponseBody 返回值生效

下面給出了一份由 mvc:annotation-driven 註冊可用的HTTP消息轉換器的完整列表:

  1. 轉換字節數組的ByteArrayHttpMessageConverter
  2. 轉換字符串的StringHttpMessageConverter
  3. ResourceHttpMessageConverterorg.springframework.core.io.Resource與全部媒體類型之間的互相轉換
  4. SourceHttpMessageConverter:從(到)javax.xml.transform.Source的轉換
  5. FormHttpMessageConverter:數據與MultiValueMap<String, String>之間的互相轉換
  6. Jaxb2RootElementHttpMessageConverter:Java對象與XML之間的互相轉換——該轉換器在classpath路徑下有JAXB2依賴而且沒有Jackson 2 XML擴展時被註冊
  7. MappingJackson2HttpMessageConverter:從(到)JSON的轉換——該轉換器在classpath下有Jackson 2依賴時被註冊
  8. MappingJackson2XmlHttpMessageConverter:從(到)XML的轉換——該轉換器在classpath下有Jackson 2 XML擴展時被註冊
  9. AtomFeedHttpMessageConverter:Atom源的轉換——該轉換器在classpath路徑下有Rome時被註冊
  10. RssChannelHttpMessageConverter:RSS源的轉換——該轉換器在classpath路徑下有Rome時被註冊

你能夠參考21.16.12 消息轉換器一小節,瞭解如何進一步定製這些默認的轉換器。

Jackson JSON和XML轉換器是經過Jackson2ObjectMapperBuilder建立的ObjectMapper實例建立的,目的在於提供更好的默認配置

該builder會使用如下的默認屬性對Jackson進行配置:

  1. 禁用DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

  2. 禁用MapperFeature.DEFAULT_VIEW_INCLUSION

同時,若是檢測到在classpath路徑下存在這些模塊,該builder也會自動地註冊它們:

  1. jackson-datatype-jdk7: 支持Java 7的一些類型,例如java.nio.file.Path

  2. jackson-datatype-joda: 支持Joda-Time類型

  3. jackson-datatype-jsr310: 支持Java 8的Date & Time API類型

  4. jackson-datatype-jdk8: 支持Java 8其餘的一些類型,好比Optional

21.16.2 默認配置的定製化

在MVC Java編程配置方式下,若是你想對默認配置進行定製,你能夠本身實現WebMvcConfigurer接口,要麼繼承WebMvcConfigurerAdapter類並覆寫你須要定製的方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // Override configuration methods...

}

 

在MVC XML命名空間下,若是你想對默認配置進行定製,請查看 <mvc:annotation-driven/> 元素支持的屬性和子元素。你能夠查看Spring MVC XML schema,或使用IDE的自動補全功能來查看有哪些屬性和子元素是能夠配置的。

21.16.3 轉換與格式化

數字的Number類型和日期Date類型的格式化是默認安裝了的,包括 @NumberFormat 註解和DateTimeFormat 註解。若是classpath路徑下存在Joda Time依賴,那麼完美支持Joda Time的時間格式化庫也會被安裝好。若是要註冊定製的格式化器或轉換器,請覆寫addFormatters方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // Add formatters and/or converters
    }

}

 

使用MVC命名空間時, <mvc:annotation-driven> 也會進行一樣的默認配置。要註冊定製的格式化器和轉換器,只須要提供一個轉換服務 ConversionService :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven conversion-service="conversionService"/>

    <bean id="conversionService"
            class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="org.example.MyConverter"/>
            </set>
        </property>
        <property name="formatters">
            <set>
                <bean class="org.example.MyFormatter"/>
                <bean class="org.example.MyAnnotationFormatterFactory"/>
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.example.MyFormatterRegistrar"/>
            </set>
        </property>
    </bean>

</beans>

 

關於如何使用格式化管理器 FormatterRegistrar ,請參考 8.6.4 FormatterRegistrar SPI一節,以及 FormattingConversionServiceFactoryBean 的文檔。

21.16.4 驗證

Spring提供了一個驗證器Validator接口,應用的任何一層均可以使用它來作驗證。在Spring MVC中,你能夠配置一個全局的 Validator 實例,用以處理全部註解了 @Valid 的元素或註解了 @Validated 的控制器方法參數、以及/或在控制器內的 @InitBinder 方法中用做局部的 Validator 。全局驗證器與局部驗證器實例能夠結合起來使用,提供組合驗證。

Spring還支持JSR-303/JSR-349的Bean驗證。這是經過 LocalValidatorFactoryBean 類實現的,它爲Spring的驗證器接口 org.springframework.validation.Validator 到 Bean 驗證的 javax.validation.Validator 接口作了適配。這個類能夠插入到Spring MVC的上下文中,做爲一個全局的驗證器,以下所述。

若是在classpath下存在Bean驗證器,諸如Hibernate Validator等,那麼 @EnableWebMvc 或 <mvc:annotation-driven> 默認會自動使用 LocalValidatorFactoryBean 爲Spring MVC應用提供Bean驗證的支持。

有時,能將 LocalValidatorFactoryBean 直接注入到控制器或另一個類中會更方便。

Sometimes it's convenient to have a  LocalValidatorFactoryBean  injected into a controller or another class. The easiest way to do that is to declare your own  @Bean  and also mark it with  @Primary  in order to avoid a conflict with the one provided with the MVC Java config.

If you prefer to use the one from the MVC Java config, you'll need to override the mvcValidator method from  WebMvcConfigurationSupport  and declare the method to explicitly return  LocalValidatorFactory  rather than  Validator . See Section 21.16.13, "Advanced Customizations with MVC Java Config" for information on how to switch to extend the provided configuration.

此外,你也能夠配置你本身的全局 Validator 驗證器實例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public Validator getValidator(); {
        // return "global" validator
    }

}

 

XML中作法以下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>

 

若要同時使用全局驗證和局部驗證,只需添加一個(或多個)局部驗證器便可:

@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }

}

 

作完這個最少的配置以後,任什麼時候候只要方法中有參數註解了 @Valid 或 @Validated ,配置的驗證器就會自動對它們作驗證。任何沒法經過的驗證都會被自動報告爲錯誤並添加到 BindingResult 對象中去,你能夠在方法參數中聲明它並獲取這些錯誤,同時這些錯誤也能在Spring MVC的HTML視圖中被渲染。

21.16.5 攔截器

你能夠配置處理器攔截器 HandlerInterceptors 或web請求攔截器 WebRequestInterceptors 等攔截器,並配置它們攔截全部進入容器的請求,或限定到符合特定模式的URL路徑。

在MVC Java編程配置下注冊攔截器的方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleInterceptor());
        registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }

}

 

在MVC XML命名空間下,則使用 <mvc:interceptors> 元素:

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

 

21.16.6 內容協商

You can configure how Spring MVC determines the requested media types from the request. The available options are to check the URL path for a file extension, check the "Accept" header, a specific query parameter, or to fall back on a default content type when nothing is requested. By default the path extension in the request URI is checked first and the "Accept" header is checked second.

The MVC Java config and the MVC namespace register json, xml, rss, atom by default if corresponding dependencies are on the classpath. Additional path extension-to-media type mappings may also be registered explicitly and that also has the effect of whitelisting them as safe extensions for the purpose of RFD attack detection (see the section called "Suffix Pattern Matching and RFD" for more detail).

Below is an example of customizing content negotiation options through the MVC Java config:

_@Configuration_
_@EnableWebMvc_
public class WebConfig extends WebMvcConfigurerAdapter {

    _@Override_
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
    }
}

 

In the MVC namespace, the  <mvc:annotation-driven>  element has a  content- negotiation-manager  attribute, which expects a  ContentNegotiationManager  that in turn can be created with a  ContentNegotiationManagerFactoryBean :

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

 

If not using the MVC Java config or the MVC namespace, you'll need to create an instance of  ContentNegotiationManager  and use it to configure  RequestMappingHandlerMapping  for request mapping purposes, and  RequestMappingHandlerAdapter  and  ExceptionHandlerExceptionResolver  for content negotiation purposes.

Note that  ContentNegotiatingViewResolver  now can also be configured with a  ContentNegotiationManager , so you can use one shared instance throughout Spring MVC.

In more advanced cases, it may be useful to configure multiple  ContentNegotiationManager  instances that in turn may contain custom ContentNegotiationStrategy implementations. For example you could configure  ExceptionHandlerExceptionResolver  with a ContentNegotiationManager that always resolves the requested media type to  "application/json" . Or you may want to plug a custom strategy that has some logic to select a default content type (e.g. either XML or JSON) if no content types were requested.

21.16.7 視圖控制器

如下的一段代碼至關於定義一個 ParameterizableViewController 視圖控制器的快捷方式,該控制器會當即將一個請求轉發(forwards)給一個視圖。請確保僅在如下情景下才使用這個類:當控制器除了將視圖渲染到響應中外不須要執行任何邏輯時。

如下是一個例子,展現瞭如何在MVC Java編程配置方式下將全部"/"請求直接轉發給名字爲"home"的視圖:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
    }

}

 

在MVC XML命名空間下完成一樣的配置,則使用 <mvc:view-controller> 元素:

<mvc:view-controller path="/" view-name="home"/>

 

21.16.8 視圖解析器

MVC提供的配置簡化了視圖解析器的註冊工做。

如下的代碼展現了在MVC Java編程配置下,如何爲內容協商配置FreeMarker HTML模板和Jackson做爲JSON數據的默認視圖解析:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp();
    }

}

 

在MVC XML命名空間下實現相同配置:

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:jsp/>
</mvc:view-resolvers>

 

須要注意的是,使用FreeMarker, Velocity, Tiles, Groovy Markup及script模板做爲視圖技術時,仍須要配置一些其餘選項。

MVC命名空間爲每種視圖都提供了相應的元素。好比下面代碼是FreeMarker須要的配置:

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>

 

在MVC Java編程配置方式下,添加一個視圖對應的「配置器」 bean 便可:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.freeMarker().cache(false);
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/WEB-INF/");
        return configurer;
    }

}

 

資源的服務

21.16.10 回到默認的Servlet來進行資源服務

這些配置容許你將 DispatcherServlet 映射到"/"路徑(也即覆蓋了容器默認Servlet的映射),但依然保留容器默認的Servlet以處理靜態資源的請求。這能夠經過配置一個URL映射到"/**"的處理器 DefaultServletHttpRequestHandler 來實現,而且該處理器在其餘全部URL映射關係中優先級應該是最低的。

該處理器會將全部請求轉發(forward)到默認的Servlet,所以須要保證它在全部URL處理器映射 HandlerMappings 的最後。若是你是經過 <mvc:annotation-driven> 的方式進行配置,或本身定製了 HandlerMapping 實例,那麼你須要確保該處理器order屬性的值比 DefaultServletHttpRequestHandler 的次序值 Integer.MAXVALUE 小。

使用默認的配置啓用該特性,你能夠:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

}

 

XML命名空間只需一行:

 <mvc:default-servlet-handler/>

 

不過須要注意,覆寫了"/"的Servlet映射後,默認Servlet的 RequestDispatcher 就必須經過名字而非路徑來取得了。 DefaultServletHttpRequestHandler 會 嘗試在容器初始化的時候自動檢測默認Servlet,這裏它使用的是一份主流Servlet容器(包括Tomcat、Jetty、GlassFish、 JBoss、Resin、WebLogic,和WWebSphere)已知的名稱列表。若是默認Servlet被配置了一個其餘的名字,或者使用了一個列 表裏未提供默認Servlet名稱的容器,那麼默認Servlet的名稱必須被顯式指定。正以下面代碼所示:

  @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {

        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable("myCustomDefaultServlet");
        }

    }

 

XML命名空間的配置方式:

  <mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>

 

21.16.11 路徑匹配

這些配置容許你對許多與URL映射和路徑匹配有關的設置進行定製。關於全部可用的配置選項,請參考PathMatchConfigurer類的API文檔。

下面是採用MVC Java編程配置的一段代碼:

@Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {

        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer
                .setUseSuffixPatternMatch(true)
                .setUseTrailingSlashMatch(false)
                .setUseRegisteredSuffixPatternMatch(true)
                .setPathMatcher(antPathMatcher())
                .setUrlPathHelper(urlPathHelper());
        }

        @Bean
        public UrlPathHelper urlPathHelper() {
            //...
        }

        @Bean
        public PathMatcher antPathMatcher() {
            //...
        }

    }

 

在XML命名空間下實現一樣的功能,能夠使用 <mvc:path-matching> 元素:

  <mvc:annotation-driven>
        <mvc:path-matching
            suffix-pattern="true"
            trailing-slash="false"
            registered-suffixes-only="true"
            path-helper="pathHelper"
            path-matcher="pathMatcher"/>
    </mvc:annotation-driven>

    <bean id="pathHelper" class="org.example.app.MyPathHelper"/>
    <bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

 

21.16.12 消息轉換器

使用MVC Java編程配置方式時,若是你想替換Spring MVC提供的默認轉換器,徹底定製本身的 HttpMessageConverter ,這能夠經過覆寫configureMessageConverters()方法來實現。若是你只是想定製一下,或者想在默認轉換器以外再添加其餘的轉換器,那麼能夠經過覆寫extendMessageConverters()方法來實現。

下面是一段例子,它使用定製的 ObjectMapper 構造了新的Jackson的JSON和XML轉換器,並用它們替換了默認提供的轉換器:

  @Configuration
    @EnableWebMvc
    public class WebConfiguration extends WebMvcConfigurerAdapter {

        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                    .indentOutput(true)
                    .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                    .modulesToInstall(new ParameterNamesModule());
            converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
            converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build()));
        }

    }

 

在上面的例子中,ackson2ObjectMapperBuilder 用於爲 MappingJackson2HttpMessageConverter 和 MappingJackson2XmlHttpMessageConverter 轉換器建立公共的配置,好比啓用tab縮進、定製的日期格式,並註冊了一個模塊jackson-module-parameter-names用於獲取參數名(Java 8新增的特性)

除了jackson- dataformat-xml,要啓用Jackson XML的tab縮進支持,還須要一個woodstox-core-asl依賴。

還有其餘有用的Jackson模塊能夠使用:

  1. jackson-datatype-money:提供了對 javax.money 類型的支持(非官方模塊)
  2. jackson-datatype-hibernate:提供了Hibernate相關的類型和屬性支持(包含懶加載aspects)

在XML作一樣的事也是可能的:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
        <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
            <property name="objectMapper" ref="xmlMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true"
      p:simpleDateFormat="yyyy-MM-dd"
      p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

 

21.16.13 使用MVC Java編程進行高級定製

從 上面許多例子你能夠看到,MVC Java編程配置和MVC命名空間的方式都提供了更高抽象層級的應用配置,它不須要你對底下建立的bean有很是深刻的瞭解,相反,這使得你能僅專一於應 用須要的配置。不過,有時你可能但願對應用的更精細控制,或你就是單純但願理解底下的配置和機制。

要作到更精細的控制,你要作的第一步就是看看底層都爲你建立了哪些bean。若你使用MVC Java編程的方式進行配置,你能夠看看java文檔,以及 WebMvcConfigurationSupport 類的 @Bean 方法。這個類有的配置都會自動被 @EnableWebMvc 註解導入。事實上,若是你打開 @EnableWebMvc 的聲明,你就會看到應用於其上的 @Import 註解。

精細控制的下一步是選擇一個 WebMvcConfigurationSupport 建立的 bean ,定製它的屬性,或你能夠提供本身的一個實例。這確保作到如下兩步:移除 @EnableWebMvc 註解以免默認配置被自動導入,而後繼承 DelegatingWebMvcConfiguration 類,它是 WebMvcConfigurationSupport 的一個子類。如下是一個例子:

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        // ...
    }

    @Override
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        // 本身建立適配器,或者調用super讓基類處理
        // 而後在這裏定製bean的一些屬性
    }

}

 

應用應該只有一個繼承 DelegatingWebMvcConfiguration 的配置類,或只有一個 @EnableWebMvc 註解的類,由於它們背後註冊的 bean 都是相同的。

使用這個方式修改bean的屬性,與這節前面展現的任何高抽象層級的配置方式並不衝突。 WebMvcConfigurerAdapter 的子類和 WebMvcConfigurer 的實現都仍是會被使用。

21.16.14 使用MVC命名空間進行高級的定製化

若是使用MVC命名空間,要在默認配置的基礎上實現粒度更細的控制,則要比使用MVC Java編程配置的方式難一些。

若是你確實須要這麼作,那也儘可能不要複製默認提供的配置,請嘗試配置一個 BeanPostProcessor 後置處理器,用它來檢測你要定製的 bean 。能夠經過 bean 的類型來找,找到之後再修改須要定製的屬性值。好比這樣:

@Component
public class MyPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
        if (bean instanceof RequestMappingHandlerAdapter) {
            // 修改適配器的屬性
        }
    }

}

 

注意, MyPostProcessor 須要被包含在 <component scan/> 的路徑下,這樣它才能被自動檢測到;或者你也能夠手動顯式地用一個XML的 bean 定義來聲明它。

 

歡迎加羣JAVA編程交流羣 574337670,以爲有幫助的麻煩推薦一下Ok

相關文章
相關標籤/搜索