SpingMVC 核心技術幫助文檔

SpringMVC 框架使用技巧

聲明:本篇文檔主要是用于參考幫助文檔,沒有實例,但幾乎包含了SpringMVC 4.2版本的所有核心技術

對于覺得篇幅長的文檔,建議大家使用快捷鍵crtl + F,搜索關鍵字查詢較為方便.

歡迎加群JAVA編程交流群 574337670

21.1 Spring Web MVC框架簡介

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

“對擴展開放”是Spring Web MVC框架一個重要的設計原則,而對于Spring的整個完整框架來說,其設計原則則是“對擴展開放,對修改閉合”。

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

關于該原則的詳細解釋,你可以參考Seth Ladd等人所著的“深入解析Spring Web MVC與Web Flow”一書。相關信息在第117頁,“設計初探(A Look At Design)”一節。

你無法增強Spring MVC中的 final 方法,比如 AbstractController.setSynchronizeOnSession() 方法等。

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

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)管理中最好的解決方案。

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

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

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)你的代碼提供了最大程度的靈活性。
  • 一個Spring 2.0開始引入的JSP表單標簽庫。它讓你在JSP頁面中編寫表單簡單許多。
  • 新增生命周期僅綁定到當前HTTP請求或HTTP會話的Bean類型。嚴格來說,這不是Spring MVC自身的特性,而是Spring MVC使用的上下文容器  WebApplicationContext  所提供的特性。

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> </code></pre>

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/*");
}

} </code></pre>

WebApplicationInitializer 是Spring MVC提供的一個接口,它會查找你所有基于代碼的配置,并應用它們來初始化Servlet 3版本以上的web容器。它有一個抽象的實現  AbstractDispatcherServletInitializer ,用以簡化  DispatcherServlet 的注冊工作:你只需要指定其servlet映射(mapping)即可。

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

在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 不同的地方在于,它支持主題的解析,并且知道它關聯到的是哪個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 ,那么框架就不會再使用bean ViewResolver 的默認實現。

在 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(分為多個部分連續上傳)的。若是,則將該請求包裝成一個 MultipartHttpServletRequest 對象,以便處理鏈中的其他組件對它做進一步的處理。
  • 為該請求查找一個合適的處理器。如果可以找到對應的處理器,則與該處理器關聯的整條執行鏈(前處理器、后處理器、控制器等)都會被執行,以完成相應模型的準備或視圖的渲染
  • 如果處理器返回的是一個模型(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";
}

} </code></pre>

你可以看到, @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.

<p>@Controller 注解表明了一個類是作為控制器的角色而存在的。Spring不要求你去繼承任何控制器基類,也不要求你去實現Servlet的那套API。當然,如果你需要的話也可以去使用任何與Servlet相關的特性和設施。</p>

[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).

<p>@Controller 注解可以認為是被標注類的原型(stereotype),表明了這個類所承擔的角色。分派器( DispatcherServlet )會掃描所有注解了 @Controller 的類,檢測其中通過 @RequestMapping 注解配置的方法(詳見下一小節)。</p>

[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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        ;

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

<!-- ... -->

</beans>

  </code></pre>

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

} </code></pre>

在上面的示例中,許多地方都使用到了 @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());
}

} </code></pre>

以上代碼沒有指定請求必須是GET方法還是 PUT/POST 或其他方法,  @RequestMapping 注解默認會映射所有的HTTP請求方法。如果僅想接收某種請求方法,請在注解中指定之  @RequestMapping(method=GET) 以縮小范圍。

<p>@Controller 和面向切面(AOP)代理</p>

有時,我們希望在運行時使用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) {
    // 方法實現體這里忽略
}

} </code></pre>

[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" .

<p>@PathVariable 可以被應用于所有 簡單類型 的參數上,比如int、long、Date等類型。Spring會自動地幫你把參數轉化成合適的類型,如果轉換失敗,就拋出一個  TypeMismatchException 。如果你需要處理其他數據類型的轉換,也可以注冊自己的類。</p>

帶正則表達式的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:

<p>@RequestMapping 注解支持你在URI模板變量中使用正則表達式。語法是  {varName:regex} ,其中第一部分定義了變量名,第二部分就是你所要應用的正則表達式。比如下面的代碼樣例:</p>
@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類是可以配置的

帶占位符的路徑模式(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.

<p>@RequestMapping 注解支持在路徑中使用占位符,以取得一些本地配置、系統配置、環境變量等。這個特性有時很有用,比如說控制器的映射路徑需要通過配置來定制的場景。如果想了解更多關于占位符的信息,可以參考 PropertyPlaceholderConfigurer 這個類的文檔。</p>

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

} </code></pre>

[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

} </code></pre>

[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

} </code></pre>

[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]

} </code></pre>

[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="

</beans> </code></pre>

可消費的媒體類型

[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) {
    // 實際實現省略
}

} </code></pre>

[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) {
    // 方法體實現省略
}

} </code></pre>

[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模板變量中的值。
  • 帶  @MatrixVariable  注解的方法參數,其存放了URI路徑段中的鍵值對。
  • 帶  @RequestParam  注解的方法參數,其存放了Servlet請求中所指定的參數。參數的值會被轉換成方法參數所聲明的類型。
  • 帶  @RequestHeader  注解的方法參數,其存放了Servlet請求中所指定的HTTP請求頭的值。參數的值會被轉換成方法參數所聲明的類型。
  • 帶  @RequestBody  注解的參數,提供了對HTTP請求體的存取。參數的值通過  HttpMessageConverter  被轉換成方法參數所聲明的類型。
  • 帶 @RequestPart 注解的參數,提供了對一個"multipart/form-data請求塊(request part)內容的存取。
  •   HttpEntity<?>  類型的參數,其提供了對HTTP請求頭和請求內容的存取。請求流是通過  HttpMessageConverter  被轉換成entity對象的。
  •  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響應數據進行了處理(比如在方法參數中定義一個 ServletResponse 或 HttpServletResponse 類型的參數并直接向其響應體中寫東西),那么方法可以返回void。handler方法也可以增加一個 Model 類型的方法參數來增強model
  • 如果處理器方法注解了 ResponseBody ,那么返回類型將被寫到HTTP的響應體中,而返回值會被 HttpMessageConverters 轉換成所方法聲明的參數類型。
  • HttpEntity<?> 或 ResponseEntity<?> 對象,用于提供對Servlet HTTP響應頭和響應內容的存取。對象體會被 HttpMessageConverters 轉換成響應流。
  • 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";
    }

// ,..

} </code></pre>

若參數使用了該注解,則該參數默認是必須提供的,但你也可以把該參數標注為非必須的:只需要將 @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  類的互相轉換

另外,如果使用的是MVC命名空間或Java編程的配置方式,會有更多默認注冊的消息轉換器。

若你更傾向于閱讀和編寫XML文件,那么你需要配置一個 MarshallingHttpMessageConverter 并為其提供  org.springframework.oxm 包下的一個  Marshaller 和  Unmarshaller 實現。下面的示例就為你展示如何直接在配置文件中配置它。但如果你的應用是使用MVC命令空間或MVC Java編程的方式進行配置的.

<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"/> </code></pre>

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

與 @ModelAttribute 注解的參數一樣, Errors 也可以被傳入為方法參數,用于檢查錯誤。如果沒有聲明這樣一個參數,那么程序會拋出一個  MethodArgumentNotValidException 異常。該異常默認由  DefaultHandlerExceptionResolver 處理,處理程序會返回一個 400 錯誤給客戶端。

關于如何通過MVC命令空間或MVC Java編程的方式配置消息轉換器和驗證器。

使用@ResponseBody注解映射響應體

<p>@ResponseBody 注解與  @RequestBody 注解類似。  @ResponseBody 注解可被應用于方法上,標志該方法的返回值(更正,原文是return type,看起來應該是返回值)應該被直接寫回到HTTP響應體中去(而不會被被放置到Model中或被解釋為一個視圖名)。舉個例子:</p>
@RequestMapping(path = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
    return "Hello World"
}

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

與 @RequestBody 注解類似,Spring使用了一個  HttpMessageConverter 來將返回對象轉換到響應體中。

使用@RestController注解創建REST控制器

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

<p>@RestController 是一個原生內置的注解,它結合了  @ResponseBody 與  @Controller 注解的功能。不僅如此,它也讓你的控制器更表義,而且在框架未來的發布版本中,它也可能承載更多的意義。</p>

與普通的 @Controller 無異,  @RestController 也可以與  @ControllerAdvicebean 配合使用。

使用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);

} </code></pre>

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

與 @RequestBody 與  @ResponseBody 注解一樣,Spring使用了  HttpMessageConverter 來對請求流和響應流進行轉換。

對方法使用@ModelAttribute注解

<p>@ModelAttribute 注解可被應用在方法或方法參數上。本節將介紹其被注解于方法上時的用法,下節會介紹其被用于注解方法參數的用法。</p>

注解在方法上的 @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 ... } </code></pre> <p>@ModelAttribute 方法通常被用來填充一些公共需要的屬性或數據,比如一個下拉列表所預設的幾種狀態,或者寵物的幾種類型,或者去取得一個HTML表單渲染所需要的命令對象,比如 Account 等。</p>

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

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

<p>@ModelAttribute 方法也可以定義在  @ControllerAdvice 注解的類中,并且這些  @ModelAttribute 可以同時對許多控制器生效。</p>

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

<p>@ModelAttribute 注解也可以被用在  @RequestMapping 方法上。這種情況下, @RequestMapping 方法的返回值將會被解釋為model的一個屬性,而非一個視圖名。此時視圖名將以視圖命名約定來方式來決議,與返回值為void的方法所采用的處理方法類似。</p>

在方法參數上使用@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中。
  • 它可能因為在同個控制器中使用了  @ModelAttribute  方法已經存在于model中——正如上一小節所敘述的
  • 它可能是由URI模板變量和類型轉換中取得的(下面會詳細講解)
  • 它可能是調用了自身的默認構造器被實例化出來的
<p>@ModelAttribute 方法常用于從數據庫中取一個屬性值,該值可能通過  @SessionAttributes 注解在請求中間傳遞。在一些情況下,使用URI模板變量和類型轉換的方式來取得一個屬性是更方便的方式。這里有個例子:</p>
@RequestMapping(path = "/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {

} </code></pre>

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

下一步就是數據的綁定。 WebDataBinder 類能將請求參數——包括字符串的查詢參數和表單字段等——通過名稱匹配到model的屬性上。成功匹配的字段在需要的時候會進行一次類型轉換(從String類型到目標字段的類型),然后被填充到model對應的屬性中。

進行了數據綁定后,則可能會出現一些錯誤,比如沒有提供必須的字段、類型轉換過程的錯誤等。若想檢查這些錯誤,可以在注解了 @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";
    }

// ...

} </code></pre>

拿到 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";
}

// ...

} </code></pre>

又或者,你可以通過添加一個 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";
}

// ...

} </code></pre>

在請求之間使用@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>

</code></pre>

上面的過濾器將會攔截內容類型(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注解映射cookie值

<p>@CookieValue 注解能將一個方法參數與一個HTTP cookie的值進行綁定。</p>

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

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

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

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

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

這個注解可以注解到處理器方法上,在Servlet環境和Portlet環境都能使用。

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

<p>@RequestHeader 注解能將一個方法參數與一個請求頭屬性進行綁定。</p>

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

  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 (,或者為  Formatters 配置一個  FormattingConversionService 來做到。

定制WebDataBinder的初始化

如果想通過Spring的 WebDataBinder 在屬性編輯器中做請求參數的綁定,你可以使用在控制器內使用  @InitBinder @InitBinder 注解的方法、在注解了  @ControllerAdvice 的類中使用  @InitBinder 注解的方法,或者提供一個定制的  WebBindingInitializer 。

數據綁定的定制:使用@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));
}

// ...

} </code></pre>

或者,你可以使用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"));
}

// ...

} </code></pre>

配置定制的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>
<p>@InitBinder 方法也可以定義在  @ControllerAdvice 注解的類上,這樣配置可以為許多控制器所共享。這提供了除使用 WebBindingInitializer 外的另外一種方法。</p>

使用@ControllerAdvice輔助控制器

<p>@ControllerAdvice 是一個組件注解,它使得其實現類能夠被classpath掃描自動發現。若應用是通過MVC命令空間或MVC Java編程方式配置,那么該特性默認是自動開啟的。</p>

注解 @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 {} </code></pre>

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

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

} </code></pre>

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

} </code></pre>

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

} </code></pre>

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

} </code></pre>

另一個選擇,是讓控制器方法返回一個 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); </code></pre>

如果對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(); </code></pre>

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="

...

</web-app> </code></pre>

異步請求必須在 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
                

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

</code></pre>

如果應用使用的是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 提供了最原生的支持。

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> </code></pre>

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

} </code></pre>

在上面的例子中,所有被此處理器處理的請求都會被 TimeBasedAccessInterceptor 攔截器攔截。如果當前時間在工作時間以外,那么用戶就會被重定向到一個HTML文件提示用戶,比如顯示“你只有在工作時間才可以訪問本網站”之類的信息。

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

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

上面的例子中,所有控制器方法處理的請求都會被配置的攔截器先攔截到。如果你想進一步縮小攔截的URL范圍,你可以通過MVC命名空間或MVC Java編程的方式來配置,或者,聲明一個 MappedInterceptor 類型的bean實例來處理。

需要注意的是, HandlerInterceptor 的后攔截 postHandle 方法不一定總是適用于注解了 @ResponseBody 或 ResponseEntity 的方法。這些場景中, HttpMessageConverter 會在攔截器的 postHandle 方法被調之前就把信息寫回響應中。這樣攔截器就無法再改變響應了,比如要增加一個響應頭之類的。如果有這種需求,請讓你的應用實現 ResponseBodyAdvice 接口,并將其定義為一個 @ControllerAdvice bean或直接在 RequestMappingHandlerMapping 中配置。

21.5 視圖解析

所 有web應用的MVC框架都提供了視圖相關的支持。Spring提供了一些視圖解析器,它們讓你能夠在瀏覽器中渲染模型,并支持你自由選用適合的視圖技術 而不必與框架綁定到一起。Spring原生支持JSP視圖技術、Velocity模板技術和XSLT視圖等。

有兩個接口在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 頭來解析一個視圖。

我們可以舉個例子,假設這里使用的是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> </code></pre>

如果一個視圖解析器不能返回一個視圖,那么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然后注入到控制器中來,而不是寫在控制器里面。這樣它就可以與視圖名一起在配置文件中配置。

向重定向目標傳遞數據

模 型中的所有屬性默認都會考慮作為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中)。

重定向前綴——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

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"/> </code></pre>

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

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

} </code></pre>

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(
        ");

URI uri = uriComponents.expand("42", "21").encode().toUri(); </code></pre>

請注意 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(); </code></pre>

或者,你也可以選擇只復用請求中一部分的信息:

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

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

</code></pre>

或者,如果你的 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()

</code></pre>

21.7.1 為控制器和方法指定URI

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

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

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

// ...

}

} </code></pre>

你可以通過引用方法名字的辦法來準備一個鏈接:

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

URI uri = uriComponents.encode().toUri(); </code></pre>

在上面的例子中,我們為方法參數準備了填充值:一個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(); </code></pre>

上面的代碼例子中使用了 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(); </code></pre>

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

21.8 地區信息(Locales)

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

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

此外,如果你需要自動解析地區信息,你可以在處理器映射前加一個攔截器,并用它來根據條件或環境不同,比如,根據請求中某個參數值,來更改地區信息。

21.8.1 獲取時區信息

除了獲取客戶端的地區信息外,有時他們所在的時區信息也非常有用。 LocaleContextResolver 接口為 LocaleResolver 提供了拓展點,允許解析器在 LocaleContext 中提供更多的信息,這里面就可以包含時區信息。

如果用戶的時區信息能被解析到,那么你總可以通過 RequestContext.getTimeZone() 方法獲得。時區信息會自動被Spring ConversionService 下注冊的日期/時間轉換器 Converter 及格式化對象 Formatter 所使用。

21.8.2 Accept請求頭解析器AcceptHeaderLocaleResolver

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

21.8.3 Cookie解析器CookieLocaleResolver

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> </code></pre>

表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.

你可以在處理器映射前添加一個 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> </code></pre>

21.9 主題 themes

1.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> </code></pre>

當然,要讓多路解析器正常工作,你需要在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";
}

} </code></pre>

請留意 @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";
}

} </code></pre>

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 ...

</code></pre>

對于名稱為 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) {

// ...

} </code></pre>

請注意 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;
}

} </code></pre>

此外, @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;
}

} </code></pre>

或者在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 。

另一個選擇是使用其他專注于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>

    </code></pre>

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

} </code></pre>

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 ;

<!-- 這個眾人皆知的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> </code></pre>

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

你無需顯式地定義一個 DefaultRequestToViewNameTranslator bean。如果默認的 DefaultRequestToViewNameTranslator 配置已能滿足你的需求,那么你無需配置,Spring Web MVC的 DispatcherServlet 會為你實例化這樣一個默認的對象。

當然,如果你需要更改默認的設置,那你就需要手動地配置自己的 DefaultRequestToViewNameTranslator bean。關于可配置屬性的一些詳細信息,你可以去咨詢 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"請求頭,支持在許多場景下使用它。

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(); </code></pre>

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

} </code></pre>

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' 響應頭,那么我們推薦在控制器內對這些請求頭進行處理。這涉及一些工作:計算最后更改時間 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);

} </code></pre>

這樣做不僅會在響應頭中設置 '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";

} </code></pre>

這里最重要的兩個地方是:調用 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> </code></pre>

如果是在Servlet 3.0以上的環境下,可以這么做:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

// ...

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

} </code></pre>

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("/");
}

} </code></pre>

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[] { "/" };
}

} </code></pre>

以上的例子適用于使用基于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[] { "/" };
}

} </code></pre>

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

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

// ...

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

} </code></pre>

每個過濾器被添加時,默認的名稱都基于其類類型決定,并且它們會被自動地映射到 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 {

} </code></pre>

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

// Override configuration methods...

} </code></pre>

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

} </code></pre>

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

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="

</beans> </code></pre>

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
}

} </code></pre>

XML中做法如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="

</beans> </code></pre>

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

@Controller
public class MyController {

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

} </code></pre>

做完這個最少的配置之后,任何時候只要方法中有參數注解了 @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/*");
}

} </code></pre>

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

} </code></pre>

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> </code></pre>

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

} </code></pre>

在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();
}

} </code></pre>

在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> </code></pre>

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

} </code></pre>

資源的服務

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

} </code></pre>

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

}

</code></pre>

XML命名空間的配置方式:

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

21.16.11 路徑匹配

這些配置允許你對許多與URL映射和路徑匹配有關的設置進行定制。關

下面是采用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() {
        //...
    }

}

</code></pre>

在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"/>

</code></pre>

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

}

</code></pre>

在上面的例子中, Jackson2ObjectMapperBuilder 用于為 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"/> </code></pre>

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的一些屬性
}

} </code></pre>

應用應該只有一個繼承 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) {
        // 修改適配器的屬性
    }
}

} </code></pre>

注意, MyPostProcessor 需要被包含在 <component scan/> 的路徑下,這樣它才能被自動檢測到;或者你也可以手動顯式地用一個XML的bean定義來聲明它。

歡迎加群JAVA編程交流群 574337670

 

來自:http://www.cnblogs.com/yxh1008/p/6058479.html

 

 本文由用戶 小財迷1989 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!