《Spring 5 官方文件》18. Web MVC 框架
原文連結 譯者:dan
18.1 Spring Web MVC 框架的介紹
Spring Web模型檢視控制器(MVC)框架是圍繞一個DispatcherServlet設計的,它將請求分派給處理程式,具有可配置的處理程式對映,檢視解析,區域設定,本地化和主題解析,並且支援上傳檔案。預設的處理是基於註解@Controller
和@RequestMapping,提供一系列靈活的處理方法。隨著Spring 3.0的推出,通過@PathVariable或者其他註解,
@Controller
機制開始允許你去建立 Rest風格的web站點和應用。
在Spring Web MVC 和 Spring中一條關鍵的準則是“對擴充套件開放,對修改關閉”
在Spring Web MVC中一些核心類的方法被標註為final,由於開發者不能用自已的方法去覆蓋這些方法,這並不是任意的,而是特別考慮到這個原則。
對於這個準則的解釋,請參考Seth Ladd的Expert Spring Web MVC和Web Flow; 具體參見第一版第117頁的“A Look At Design”一節。 或者參見
當你使用Spring MVC時,你不能在final方法增加切面。例如,你不能在AbstractController.setSynchronizeOnSession()增加切面,有關AOP 代理的更多資訊以及為什麼不能再Final方法增加切面,檢視第7.6.1節
在Spring Web MVC中,您可以使用任何物件作為命令或表單支援物件;你不需要實現一個特別架構介面或者基類。Spring資料繫結非常靈活:例如,你可以使用程式將型別不匹配當作驗證錯誤而不是系統錯誤。 因此,您不需要將您的業務物件的屬性複製為簡單的無格式的字串,僅用於處理無效提交,或者正確轉換字串。 相反,通常最好直接繫結到您的業務物件。
Spring 的檢視處理也是相當靈活,控制器通常負責準備具有資料和選擇檢視名稱的模型對映,但它也可以直接寫入響應流並完成請求。檢視名稱解析可通過檔案擴充套件或Accept標頭內容型別協商進行高度配置,通過bean名稱,屬性檔案或甚至自定義的ViewResolver實現。模型(MVC中的M)是一個Map介面,可以完全提取檢視技術,你可以直接與基於模板的渲染技術(如JSP和FreeMarker)整合,或直接生成XML,JSON,Atom和許多其他型別的內容。 模型Map可以簡單地轉換成適當的格式,如JSP請求屬性或FreeMarker模板模型。
18.1.1 Spring Web MVC的特點
Spring Web 流程
Spring Web 流程 (SWF)的目的是成為最好的Web頁面應用流程管理方案,SWF與Servlet 和Portlet 環境中的Spring MVC和JSF等現有框架整合。如果你有一個這樣的業務流程,使用會話模型比純粹的請求要優,那麼SWF可能是一個選擇。
SWF允許您將邏輯頁面流作為在不同情況下可重用的自包含模組捕獲,因此非常適合構建引導使用者通過驅動業務流程的受控導航的Web應用程式模組。
Spring 的Web模組包含許多獨特的web支援特性:
- 明確並分離的角色.每個角色-控制器,驗證器,命令物件,構建物件,模型物件,分發器,對映處理器,檢視解析等等都是完全的一個特定物件
- 框架和應用程式類作為JavaBeans的強大而直接的配置。 此配置功能包括跨上下文的簡單引用,例如從Web控制器到業務物件和驗證器。
- 可適配,無入侵,靈活,定義您需要的任何控制器方法簽名,可能使用給定方案的引數註釋之一(例如@RequestParam,@RequestHeader,@PathVariable等)。
- 可重用的業務程式碼,不需要重複,使用現有的業務物件作為命令或表單物件,而不是仿照它們來擴充套件特定的框架基類。
- 自定義繫結和驗證,型別不匹配作為應用程式級驗證錯誤,保持違規值,本地化日期和數字繫結等,而不是隻使用僅包含字串的表單物件進行手動解析和轉換為業務物件。
- 自定義的處理程式對映和檢視解析,從簡單的URL配置策略到複雜的,特製的策略,Spring比Web MVC框架更靈活,這些框架需要特定的技術。
- 靈活的模型轉換,具有名稱/值的模型傳輸Map支援與任何檢視技術的輕鬆整合。
- 本地,時區,主題自定義,支援具有或不具有Spring標籤庫的JSP,支援JSTL,支援FreeMarker而不需要額外的網橋等等。
- 一個簡單而強大的JSP標籤庫,被稱為Spring標籤庫,為資料繫結和主題等功能提供支援。 自定義標籤允許在標記程式碼方面具有最大的靈活性。 有關標籤庫描述符的資訊,請參見附錄Chapter 40, spring JSP Tag Library
- 在Spring 2.0中引入的JSP表單標籤庫,使得在JSP頁面中的寫入表單更容易。 有關標籤庫描述符的資訊,請參見附錄Chapter 41, spring-form JSP Tag Library
- Bean的生命週期範圍限定在當前的HTTP請求或HTTP Session中。 這不是Spring MVC本身的一個特定功能,而是Spring MVC使用的WebApplicationContext容器。 這些bean範圍在Section 3.5.4, “Request, session, application, and WebSocket scopes”
18.1.2 其他MVC實現的可插拔性
對於某些專案,非Spring MVC實現更為可取。許多團隊希望利用他們現有的技能和工具投資,例如使用JSF。
如果您不想使用Spring的Web MVC,但打算利用Spring提供的其他解決方案,您可以輕鬆地將您選擇的Web MVC框架與Spring整合。通過其ContextLoaderListener簡單地啟動一個Spring根應用程式上下文,並通過任何動作物件中的ServletContext屬性(或Spring的各自的幫助方法)訪問它。沒有涉及“外掛”,因此不需要專門的整合。從Web層的角度來看,您只需使用Spring作為庫,將根應用程式上下文例項作為入口點。
即使沒有Spring的Web MVC,您的註冊bean和Spring的服務也可以在您的指尖。在這種情況下,Spring不會與其他Web框架競爭。它簡單地解決了純Web MVC框架從bean配置到資料訪問和事務處理的許多方面。所以您可以使用Spring中間層和/或資料訪問層來豐富您的應用程式,即使您只想使用JDBC或Hibernate的事務抽象。
18.2 分發
Spring的Web MVC框架與許多其他Web MVC框架一樣,以請求為驅動,圍繞一箇中央Servlet設計,將請求傳送給控制器,並提供了其他促進Web應用程式開發的功能。然而, Spring 的DispatcherServlet
做得更多.它和 Spring IoC 容器整合一起,它允許你使用Spring 每個特性.
Spring Web MVC DispatcherServlet的請求處理工作流程如下圖所示。 對設計模式熟悉的讀者將會認識到,DispatcherServlet是“前端控制器”設計模式的表達(這是Spring Web MVC與許多其他領先的Web框架共享的模式)。
下圖,在Spring Web MVC 中請求處理流程
DispatcherServlet是一個實際的Servlet(它繼承自HttpServlet基類),因此在Web應用程式中被宣告。 您需要使用URL對映來對映要DispatcherServlet處理的請求。 以下是Servlet 3.0+環境中的標準Java EE Servlet配置:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
ServletRegistration.Dynamic registration = container.addServlet("example", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/example/*");
}
}
在前面的示例中,以/ example開頭的所有請求都將由名為Example的DispatcherServlet例項處理。
WebApplicationInitializer是由Spring MVC提供的介面,可確保您的基於程式碼的配置被檢測並自動用於初始化任何Servlet 3容器。這個名為AbstractAnnotationConfigDispatcherServletInitializer的介面的抽象基類實現通過簡單地指定其servlet對映和列出配置類來更容易地註冊DispatcherServlet,甚至建議您設定Spring MVC應用程式。有關更多詳細資訊,請參閱基於程式碼的Servlet容器初始化。
DispatcherServlet是一個實際的Servlet(它繼承自HttpServlet基類),因此在Web應用程式的web.xml中宣告。您需要通過使用同一web.xml檔案中的URL對映來對映要DispatcherServlet處理的請求。這是標準的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>
如第3.15節“ApplicationContext的附加功能”中所述,Spring中的ApplicationContext例項可以被限定。 在Web MVC框架中,每個DispatcherServlet都有自己的WebApplicationContext,它繼承了已經在根WebApplicationContext中定義的所有bean。 根WebApplicationContext應該包含應該在其他上下文和Servlet例項之間共享的所有基礎架構bean。 這些繼承的bean可以在特定於servlet的範圍內被覆蓋,您可以在給定的Servlet例項本地定義新的範圍特定的bean。
18.2. Spring Web MVC中的典型上下文層次結構
在初始化DispatcherServlet時,Spring MVC將在Web應用程式的WEB-INF目錄中查詢名為[servlet-name] -servlet.xml的檔案,並建立在那裡定義的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 Web MVC特定元件(bean)。 您可以通過Servlet初始化引數更改此配置檔案的確切位置(有關詳細資訊,請參閱下文)。 單個DispatcherServlet方案也可能只有一個根上下文。
這可以通過設定一個空的ContextConfigLocation servlet init引數進行配置,如下所示:
<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不同之處在於它能夠解析主題(參見第18.9節“使用主題”),並且知道它與哪個Servlet相關聯(通過連線到ServletContext)。 WebApplicationContext繫結在ServletContext中,並且通過在RequestContextUtils類上使用靜態方法,您可以隨時查詢WebApplicationContext,如果您需要訪問它。 請注意,我們可以通過基於Java的配置實現相同的方式:
public class GolfingWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override protected Class<?>[] getRootConfigClasses() { // GolfingAppConfig defines beans that would be in root-context.xml return new Class[] { GolfingAppConfig.class }; }
@Override protected Class<?>[] getServletConfigClasses() { // GolfingWebConfig defines beans that would be in golfing-servlet.xml return new Class[] { GolfingWebConfig.class }; }
@Override protected String[] getServletMappings() { return new String[] { "/golfing/*" }; }
}
18.2.1 WebApplicationContext中的特殊Bean型別
Spring DispatcherServlet使用特殊的bean來處理請求並呈現適當的檢視。 這些bean是Spring MVC的一部分。 您可以通過在WebApplicationContext中簡單配置一個或多個選擇要使用的特殊bean。 但是,您最初不需要這樣做,因為Spring MVC維護一個預設bean列表,如果您沒有配置任何內容。 更多的在下一節。 首先看下錶列出DispatcherServlet依賴的特殊bean型別。
表18.1. 在 WebApplicationContext中的特殊bean型別
Bean type | Explanation |
---|---|
根據一些標準將傳入的請求對映到處理程式和前處理程式和後處理程式列表(處理程式攔截器),其細節由HandlerMapping實現而異。 最流行的實現支援註釋控制器,但其他實現也存在。 | |
HandlerAdapter | 幫助DispatcherServlet呼叫對映到請求的處理程式,而不管實際呼叫哪個處理程式。 例如,呼叫帶註釋的控制器需要解析各種註釋。 因此,HandlerAdapter的主要目的是遮蔽DispatcherServlet和這些細節 |
將基於邏輯字串的檢視名稱解析為實際的View型別。 | |
解決您的Web應用程式可以使用的主題,例如,提供個性化的佈局 | |
解析multi-part請求,以支援從HTML表單處理檔案上傳。 | |
儲存並檢索可以用於將屬性從一個請求傳遞到另一個請求的“輸入”和“輸出”FlashMap,通常是通過重定向。 |
18.2.2 預設DispatcherServlet 配置
如上一節中針對每個特殊bean所述,DispatcherServlet會維護預設使用的實現列表。此資訊儲存在包org.springframework.web.servlet中的檔案DispatcherServlet.properties中。
所有特殊豆都有一些合理的預設值。不久之後,您將需要自定義這些bean提供的一個或多個屬性。例如,將InternalResourceViewResolver設定的字首屬性配置為檢視檔案的父位置是很常見的。
無論細節如何,在這裡瞭解的重要概念是,一旦您在WebApplicationContext中配置了一個特殊的bean(如InternalResourceViewResolver),您可以有效地覆蓋該特殊bean型別所使用的預設實現列表。例如,如果配置了InternalResourceViewResolver,則會忽略ViewResolver實現的預設列表。
在第18.16節“配置Spring MVC”中,您將瞭解配置Spring MVC的其他選項,包括MVC Java配置和MVC XML名稱空間,這兩者都提供了一個簡單的起點,並且對Spring MVC的工作原理幾乎不瞭解。無論您如何選擇配置應用程式,本節中介紹的概念都是基礎的,應該對您有所幫助。
18.2.3 DispatcherServlet 處理序列
在您設定了DispatcherServlet並且針對該特定DispatcherServlet啟動了一個請求後,DispatcherServlet將按如下所示開始處理請求:
在請求中搜索並繫結WebApplicationContext作為控制元件和程序中的其他元素可以使用的屬性。預設情況下,它將在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE鍵下繫結。
語言環境解析器被繫結到請求,以使程序中的元素能夠解決在處理請求時使用的區域設定(渲染檢視,準備資料等)。如果您不需要語言環境解析,則不需要它。
主題解析器被繫結到使得諸如檢視之類的元素確定要使用哪個主題的請求。如果不使用主題,可以忽略它。
如果指定了多部分檔案解析器,則會檢查該請求的多部分;如果找到多部分,則請求被包裝在一個MultipartHttpServletRequest中,以便程序中的其他元素進一步處理。有關多部分處理的更多資訊,請參見第18.10節“Spring的多部分(檔案上傳)支援”。
搜尋適當的處理程式。如果找到處理程式,則執行與處理程式(預處理程式,後處理程式和控制器)關聯的執行鏈,以便準備模型或呈現。
如果返回模型,則呈現檢視。如果沒有返回模型(可能是由於預處理程式或後處理程式攔截請求,可能是出於安全原因),因為請求可能已經被滿足,所以不會呈現任何檢視。
在WebApplicationContext中宣告的處理程式異常解析程式在處理請求期間提取異常。使用這些異常解析器允許您定義自定義行為來解決異常。
Spring DispatcherServlet還支援返回由Servlet API指定的最後修改日期。確定特定請求的最後修改日期的過程很簡單:DispatcherServlet查詢適當的處理程式對映,並測試發現的處理程式是否實現了LastModified介面。如果是,則LastModified介面的long getLastModified(request)方法的值將返回給客戶端。
您可以通過將Servlet初始化引數(init-param元素)新增到web.xml檔案中的Servlet宣告來自定義單獨的DispatcherServlet例項。有關支援的引數列表,請參見下表。
表18.2. DispatcherServlet 初始化引數
引數 | 解釋 |
---|---|
contextClass |
實現WebApplicationContext的類,它例項化了這個Servlet使用的上下文。 預設情況下,使用XmlWebApplicationContext。 |
contextConfigLocation |
傳遞給上下文例項(由contextClass指定)以指示可以找到上下文的字串。 該字串可能包含多個字串(使用逗號作為分隔符)來支援多個上下文。 在具有兩次定義的bean的多個上下文位置的情況下,優先順序最高。 |
namespace |
WebApplicationContext的名稱空間。 預設為[servlet-name] -servlet。 |
18.3 實現Controllers
控制器提供對通常通過服務介面定義的應用程式行為的訪問。控制器解釋使用者輸入並將其轉換為由視圖表示給使用者的模型。 Spring以非常抽象的方式實現控制器,使您能夠建立各種各樣的控制器。
Spring 2.5引入了一種基於註釋的程式設計模型,用於使用諸如@RequestMapping,@RequestParam,@ModelAttribute等註釋的MVC控制器。以這種風格實現的控制器不必擴充套件特定的基類或實現特定的介面。此外,它們通常不直接依賴於Servlet API,但是如果需要,您可以輕鬆地配置對Servlet設施的訪問。
@Controller
public class HelloWorldController {
@RequestMapping( “/ HelloWorld” 的)
public String helloWorld(Model model){
model.addAttribute(“message”,“Hello World!”);
return “helloWorld”;
}
}
您可以看到,@Controller和@RequestMapping註釋允許靈活的方法名稱和簽名。在這個特殊的例子中,該方法接受一個Model並返回一個檢視名稱作為一個String,但是可以使用各種其他的方法引數和返回值,如本節稍後所述。 @Controller和@RequestMapping和許多其他註釋構成了Spring MVC實現的基礎。本節介紹這些註釋以及它們在Servlet環境中最常用的註釋。
18.3.1 使用@Controller定義控制器
@Controller註釋表示特定的類用於控制器的角色。 Spring不需要擴充套件任何控制器基類或引用Servlet API。 但是,如果需要,您仍然可以參考Servlet特定的功能。
@Controller註釋作為註釋類的構造型,表示其作用。 排程程式掃描這些註釋類的對映方法,並檢測@RequestMapping註釋(請參閱下一節)。
您可以使用排程程式上下文中的標準Spring bean定義來明確定義帶註釋的控制器bean。 但是,@Controller構造型還允許自動檢測,與Spring通用支援對齊,用於檢測類路徑中的元件類並自動註冊它們的bean定義。要啟用自動檢測這些帶註釋的控制器,您可以向組態新增元件掃描。 使用spring-context模式,如以下XML程式碼片段所示:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.springframework.samples.petclinic.web"/> <!-- ... --> </beans>
18.3.2 使用@RequestMapping對映請求
您可以使用@RequestMapping註釋將諸如/約會的URL對映到整個類或特定的處理程式方法。 通常,類級註釋將特定的請求路徑(或路徑模式)對映到表單控制器上,其他方法級註釋縮小了特定HTTP請求方法(“GET”,“POST”等)的主對映,或 HTTP請求引數條件。
Petcare示例中的以下示例顯示了使用此註釋的Spring MVC應用程式中的控制器:
@Controller @RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook; @Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook; } @RequestMapping(method = RequestMethod.GET) public Map<String, Appointment> get() { return appointmentBook.getAppointmentsForToday(); } @RequestMapping(path = "/{day}", method = RequestMethod.GET) public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @RequestMapping(path = "/new", method = RequestMethod.GET) public AppointmentForm getNewForm() { return new AppointmentForm(); } @RequestMapping(method = RequestMethod.POST) public String add(@Valid AppointmentForm appointment, BindingResult result) { if (result.hasErrors()) { return "appointments/new"; } appointmentBook.addAppointment(appointment); return "redirect:/appointments"; } }
在上面的例子中,@RequestMapping用在很多地方。 第一個用法是型別(類)級別,這表示此控制器中的所有處理程式方法都相對於/約會路徑。 get()方法還有一個@RequestMapping細化:它只接受GET請求,這意味著/appointments
的HTTP GET呼叫此方法。 add()有一個類似的細化,getNewForm()將HTTP方法和路徑的定義組合成一個,以便通過該方法處理appointments
/新的GET請求。 getForDay()方法顯示了@RequestMapping:URI模板的另一種用法。 (參見“URI模板模式”一節)。 類級別上的@RequestMapping不是必需的。 沒有它,所有的路徑都是絕對的,而不是相對的。 PetClinic示例應用程式的以下示例顯示了使用@RequestMapping的多操作控制器:
@Controller public class ClinicController { private final Clinic clinic; @Autowired public ClinicController(Clinic clinic) { this.clinic = clinic; } @RequestMapping("/") public void welcomeHandler() { } @RequestMapping("/vets") public ModelMap vetsHandler() { return new ModelMap(this.clinic.getVets()); } }
上述示例不指定GET與PUT,POST等,因為@RequestMapping預設對映所有HTTP方法。 使用@RequestMapping(method = GET)或@GetMapping來縮小對映。
組合@RequestMapping變體
Spring Framework 4.3引入了@RequestMapping註釋的以下方法級組合變體,有助於簡化常見HTTP方法的對映,並更好地表達註釋處理程式方法的語義。 例如,@GetMapping可以被讀取為GET @RequestMapping。
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
以下示例顯示了使用已組合的@RequestMapping註釋簡化的上一節中的AppointmentsController的修改版本。
@Controller @RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook; @Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook; } @GetMapping public Map<String, Appointment> get() { return appointmentBook.getAppointmentsForToday(); } @GetMapping("/{day}") public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @GetMapping("/new") public AppointmentForm getNewForm() { return new AppointmentForm(); } @PostMapping public String add(@Valid AppointmentForm appointment, BindingResult result) { if (result.hasErrors()) { return "appointments/new"; } appointmentBook.addAppointment(appointment); return "redirect:/appointments"; } }
@Controller 和AOP 代理
在某些情況下,控制器可能需要在執行時用AOP代理進行裝飾。 一個例子是如果您選擇在控制器上直接使用@Transactional註釋。 在這種情況下,對於控制器,我們建議使用基於類的代理。 這通常是控制器的預設選項。 但是,如果控制器必須實現不是Spring Context回撥的介面(例如InitializingBean,* Aware等),則可能需要顯式配置基於類的代理。 例如,使用<tx:annotation-driven />,更改為<tx:annotation-driven proxy-target-class =“true”/>。
Spring MVC 3.1中的@RequestMapping方法的新支援類
Spring 3.1分別為@RequestMapping方法引入了一組新的支援類,分別叫做RequestMappingHandlerMapping和RequestMappingHandlerAdapter。它們被推薦使用,甚至需要利用Spring MVC 3.1中的新功能和未來。預設情況下,MVC名稱空間和MVC Java配置啟用新的支援類,但是如果不使用,則必須顯式配置。本節介紹舊支援類和新支援類之間的一些重要區別。
在Spring 3.1之前,型別和方法級請求對映在兩個單獨的階段進行了檢查 – 首先由DefaultAnnotationHandlerMapping選擇一個控制器,並且實際的呼叫方法被AnnotationMethodHandlerAdapter縮小。
使用Spring 3.1中的新支援類,RequestMappingHandlerMapping是唯一可以決定哪個方法應該處理請求的地方。將控制器方法作為從型別和方法級@RequestMapping資訊派生的每個方法的對映的唯一端點的集合。
這使得一些新的可能性。一旦HandlerInterceptor或HandlerExceptionResolver現在可以期望基於物件的處理程式是HandlerMethod,它允許它們檢查確切的方法,其引數和關聯的註釋。 URL的處理不再需要跨不同的控制器進行拆分。
還有下面幾件事情已經不復存在了:
- 首先使用SimpleUrlHandlerMapping或BeanNameUrlHandlerMapping選擇控制器,然後基於@RequestMapping註釋來縮小方法。
- 依賴於方法名稱作為一種落後機制,以消除兩個@RequestMapping方法之間的差異,這兩個方法沒有明確的路徑對映URL路徑, 通過HTTP方法。 在新的支援類中,@RequestMapping方法必須被唯一地對映。
- 如果沒有其他控制器方法更具體地匹配,請使用單個預設方法(無顯式路徑對映)處理請求。 在新的支援類中,如果找不到匹配方法,則會引發404錯誤。
上述功能仍然支援現有的支援類。 不過要利用新的Spring MVC 3.1功能,您需要使用新的支援類。
URI 模版模式
可以使用URI模板方便地訪問@RequestMapping方法中URL的所選部分。
URI模板是一個類似URI的字串,包含一個或多個變數名稱。 當您替換這些變數的值時,模板將成為一個URI。 所提出的RFC模板RFC定義了URI如何引數化。 例如,URI模板http://www.example.com/users/{userId}包含變數userId。 將fred的值分配給變數會得到http://www.example.com/users/fred。
在Spring MVC中,您可以使用方法引數上的@PathVariable註釋將其繫結到URI模板變數的值:
@GetMapping("/owners/{ownerId}") public String findOwner(@PathVariable String ownerId, Model model) { Owner owner = ownerService.findOwner(ownerId); model.addAttribute("owner", owner); return "displayOwner"; } URI模板“/ owners / {ownerId}”指定變數名ownerId。 當控制器處理此請求時,ownerId的值將設定為在URI的適當部分中找到的值。 例如,當/ owner / fred出現請求時,ownerId的值為fred。
@GetMapping("/owners/{ownerId}") public String findOwner(@PathVariable("ownerId") String theOwner, Model model) { // implementation omitted }
或者如果URI模板變數名稱與方法引數名稱匹配,則可以省略該詳細資訊。 只要您的程式碼使用除錯資訊或Java 8上的引數編譯器標記進行編譯,Spring MVC將將方法引數名稱與URI模板變數名稱相匹配:
@GetMapping("/owners/{ownerId}") public String findOwner(@PathVariable String ownerId, Model model) { // implementation omitted } 一個方法能夠有任意數量的
@PathVariable註解:
@GetMapping("/owners/{ownerId}/pets/{petId}") 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"; }
當在Map <String,String>引數上使用@PathVariable註釋時,對映將填充所有URI模板變數。 URI模板可以從型別和方法級別@RequestMapping註釋中進行組合。 因此,可以使用/ owner / 42 / pets / 21等URL呼叫findPet()方法。
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping("/pets/{petId}") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } } 一個@PathVariable引數可以是任何簡單的型別,如int,long,Date等。如果沒有這樣做,Spring將自動轉換為適當的型別或者丟擲一個TypeMismatchException。 您還可以註冊解析其他資料型別的支援。. See the section called “Method Parameters And Type Conversion” 和the section called “Customizing WebDataBinder initialization”.
具有正則表示式的URI模板模式
有時您需要更精確地定義URI模板變數。 考慮URL“/spring-web/spring-web-3.0.5.jar”。 你怎麼把它分解成多個部分?
@RequestMapping註釋支援在URI模板變數中使用正則表示式。 語法是{varName:regex},其中第一部分定義了變數名,第二部分定義了正則表示式。 例如:
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String extension) { // ... }
路徑模式
除了URI模板之外,@RequestMapping註釋和所有組合的@RequestMapping變體也支援Ant樣式的路徑模式(例如/myPath/*.do)。 還支援URI模板變數和Ant-style glob的組合(例如/ owners / * / pets / {petId})。
路徑模式比較
當URL匹配多個模式時,使用排序來查詢最具體的匹配。
具有較低數量URI變數和萬用字元的模式被認為更具體。 例如/hotels/ {hotel} / *具有1個URI變數和1個萬用字元,被認為比/hotels/ {hotel} / **更具體,其中1個URI變數和2個萬用字元。
如果兩個模式具有相同的計數,那麼較長的模式被認為更具體。 例如/ foo / bar *比較長,被認為比/ foo / *更具體。
當兩個模式具有相同的計數和長度時,具有較少萬用字元的模式被認為更具體。 例如/hotels/ {hotel}比/hotels/ *更具體。
下面有些額外增加的特殊的規則:
- 預設對映模式/ **比任何其他模式都要小。 例如/ api / {a} / {b} / {c}更具體。
- 諸如/ public / **之類的字首模式比不包含雙萬用字元的任何其他模式都不那麼具體。 例如/ public / path3 / {a} / {b} / {c}更具體。
For 有關詳細資訊,請參閱AntPathMatcher中的AntPatternComparator。 請注意,可以自定義PathMatcher(參見Section 18.16.11, “Path Matching” ).
具有佔位符的路徑模式
@RequestMapping註釋中的模式支援對本地屬性和/或系統屬性和環境變數的$ {…}佔位符。 在將控制器對映到的路徑可能需要通過配置進行定製的情況下,這可能是有用的。 有關佔位符的更多資訊,請參閱PropertyPlaceholderConfigurer類的javadocs。
字尾模式匹配
預設情況下,Spring MVC執行“。*”字尾模式匹配,以便對映到/ person的控制器也隱式對映到/person.*。這使得通過URL路徑(例如/person.pdf,/person.xml)可以輕鬆地請求資源的不同表示。
字尾模式匹配可以關閉或限制為一組明確註冊用於內容協商的路徑擴充套件。通常建議通過諸如/ person / {id}之類的常見請求對映來減少歧義,其中點可能不表示副檔名,例如/person/[email protected] vs /person/[email protected]。此外,如下面的說明中所解釋的,字尾模式匹配以及內容協商可能在某些情況下用於嘗試惡意攻擊,並且有充分的理由有意義地限制它們。
有關字尾模式匹配配置,請參見第18.16.11節“路徑匹配”,內容協商配置第18.16.6節“內容協商”。
字尾模式匹配和RFD
反思檔案下載(RFD)攻擊是由Trustwave在2014年的一篇論文中首次描述的。攻擊類似於XSS,因為它依賴於響應中反映的輸入(例如查詢引數,URI變數)。然而,不是將JavaScript插入到HTML中,如果基於副檔名(例如.bat,.cmd)雙擊,則RFD攻擊依賴於瀏覽器切換來執行下載並將響應視為可執行指令碼。
在Spring MVC @ResponseBody和ResponseEntity方法存在風險,因為它們可以呈現客戶端可以通過URL路徑擴充套件請求的不同內容型別。但是請注意,單獨禁用字尾模式匹配或禁用僅用於內容協商的路徑擴充套件都可以有效地防止RFD攻擊。
為了全面保護RFD,在呈現響應體之前,Spring MVC添加了Content-Disposition:inline; filename = f.txt頭來建議一個固定和安全的下載檔案。只有當URL路徑包含既不是白名單的副檔名,也沒有明確註冊用於內容協商的目的,這是完成的。但是,當URL直接輸入瀏覽器時,可能會產生副作用。
許多常見的路徑副檔名預設為白名單。此外,REST API呼叫通常不是直接用於瀏覽器中的URL。然而,使用自定義HttpMessageConverter實現的應用程式可以明確地註冊用於內容協商的副檔名,並且不會為此類擴充套件新增Content-Disposition頭。見第18.16.6節“Content Negotiation”。
這是CVE-2015-5211工作的一部分。 以下是報告中的其他建議:
- 編碼而不是轉義JSON響應。 這也是OWASP XSS的建議。 有關Spring的例子,請參閱spring-jackson-owasp.
- 將字尾模式匹配配置為關閉或僅限於明確註冊的字尾
- 配置使用屬性“useJaf”和“ignoreUnknownPathExtensions”設定為false的內容協商,這將導致具有未知副檔名的URL的406響應。 但是請注意,如果URL自然希望有一個結束點,這可能不是一個選擇。
- 新增X-Content-Type-Options:nosniff頭到響應。 Spring Security 4預設情況下執行此操作。
矩陣變數
URI規範RFC 3986定義了在路徑段中包含名稱 – 值對的可能性。規格中沒有使用具體術語。可以應用一般的“URI路徑引數”,儘管來自Tim Berners-Lee的舊帖子的更獨特的“Matrix URI”也經常被使用並且是相當熟知的。在Spring MVC中,這些被稱為矩陣變數。
矩陣變數可以出現在任何路徑段中,每個矩陣變數用“;”分隔(分號)。例如:“/ cars; color = red; year = 2012”。多個值可以是“,”(逗號)分隔“color = red,green,blue”,或者變數名稱可以重複“color = red; color = green; color = blue”。
如果URL預期包含矩陣變數,則請求對映模式必須使用URI模板來表示它們。這確保了請求可以正確匹配,無論矩陣變數是否存在,以及它們提供什麼順序。
以下是提取矩陣變數“q”的示例:
// GET /pets/42;q=11;r=22 @GetMapping("/pets/{petId}") public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 } 由於所有路徑段都可能包含矩陣變數,因此在某些情況下,您需要更具體地確定變數預期位於何處:
// GET /owners/42;q=11/pets/21;q=22 @GetMapping("/owners/{ownerId}/pets/{petId}") public void findPet( @MatrixVariable(name="q", pathVar="ownerId") int q1, @MatrixVariable(name="q", pathVar="petId") int q2) { // q1 == 11 // q2 == 22 } 矩陣變數可以定義為可選引數,並指定一個預設值:
// GET /pets/42 @GetMapping("/pets/{petId}") public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) { // q == 1 } 所有矩陣變數可以在Map中獲得:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @GetMapping("/owners/{ownerId}/pets/{petId}") public void findPet( @MatrixVariable MultiValueMap<String, String> matrixVars, @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] // petMatrixVars: ["q" : 11, "s" : 23] } 請注意,為了使用矩陣變數,您必須將RequestMappingHandlerMapping的removeSemicolonContent屬性設定為false。 預設設定為true。 MVC Java配置和MVC名稱空間都提供了使用矩陣變數的選項。 如果您使用Java配置,使用MVC Java Config的高階自定義部分將介紹如何自定義RequestMappingHandlerMapping。 在MVC名稱空間中,<mvc:annotation-driven>元素具有一個應該設定為true的enable-matrix-variables屬性。 預設情況下設定為false。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven enable-matrix-variables="true"/> </beans>
Consumable Media 型別
您可以通過指定consumable media型別的列表來縮小主要對映。 只有當Content-Type請求頭與指定的媒體型別匹配時,才會匹配該請求。 例如:
@PostMapping(path = "/pets", consumes = "application/json") public void addPet(@RequestBody Pet pet, Model model) { // implementation omitted } consumable media型別表示式也可以在!text / plain中否定,以匹配除Content-Type of text / plain之外的所有請求。 還要考慮使用MediaType中提供的常量,例如APPLICATION_JSON_VALUE和APPLICATION_JSON_UTF8_VALUE。
Producible Media 型別
您可以通過指定producible media型別列表來縮小主要對映。 只有當Accept請求頭匹配這些值之一時,才會匹配該請求。 此外,使用產生條件確保用於產生響應的實際內容型別與產生條件中指定的媒體型別相關。 例如:
@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody public Pet getPet(@PathVariable String petId, Model model) { // implementation omitted } 就像消費一樣,可生產的媒體型別表示式可以被否定為!text / plain,以匹配除了接受標頭檔案值為text / plain的所有請求。 還要考慮使用MediaType中提供的常量,例如APPLICATION_JSON_VALUE和APPLICATION_JSON_UTF8_VALUE。
請求引數和頭部值
您可以通過請求引數條件(如“myParam”,“!myParam”或“myParam = myValue”)縮小請求匹配。 前兩個測試請求引數存在/不存在,第三個為特定引數值。 下面是一個請求引數值條件的例子:
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } }
也可以根據特定的請求頭值來測試請求頭存在/不存在或匹配:
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @GetMapping(path = "/pets", headers = "myHeader=myValue") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } }
HTTP 頭部和 HTTP 可選項
對映到“GET”的@RequestMapping方法也隱式對映到“HEAD”,即不需要明確宣告“HEAD”。處理HTTP HEAD請求就像是HTTP GET一樣,而不是僅寫入正文,僅計數字節數,並設定“Content-Length”頭。
@RequestMapping方法內建支援HTTP選項。預設情況下,通過將所有@RequestMapping方法上顯式宣告的具有匹配URL模式的HTTP方法設定為“允許”響應頭來處理HTTP OPTIONS請求。當沒有明確宣告HTTP方法時,“允許”標題設定為“GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS”。理想地總是宣告@RequestMapping方法要處理的HTTP方法,或者使用專用的組合@RequestMapping變體之一(參見“Composed @RequestMapping Variants”一節)。
雖然不需要@RequestMapping方法可以對映到HTTP HEAD或HTTP選項,也可以兩者相容。
18.3.3 定義@RequestMapping 處理方法
@RequestMapping處理方法可以有非常靈活的簽名。 支援的方法引數和返回值將在以下部分中介紹。 大多數引數可以按任意順序使用,唯一的例外是BindingResult引數。 這將在下一節中介紹。
支援的方法引數型別
下面是支援的方法引數型別:
org.springframework.web.context.request.WebRequest
ororg.springframework.web.context.request.NativeWebRequest
. 允許通用請求引數訪問以及請求/會話屬性訪問,而不涉及本機Servlet API。- Request or response objects (Servlet API). 選擇任意特定的請求或響應型別, for example
ServletRequest
orHttpServletRequest
or Spring’sMultipartRequest
/MultipartHttpServletRequest
. - Session物件(Servlet API)型別為HttpSession。 此型別的引數強制存在相應的會話。 因此,這樣的論證從不為空。
會話訪問可能不是執行緒安全的,特別是在Servlet環境中。 如果允許多個請求同時訪問會話,請考慮將RequestMappingHandlerAdapter的“synchronizeOnSession”標誌設定為“true”。
- java.servlet.http.PushBuilder用於關聯的Servlet 4.0推送構建器API,允許程式設計的HTTP / 2資源推送。
- java.security.Principal(或一個特定的Principal實現類(如果已知)),包含當前驗證的使用者。
- org.springframework.http.HttpMethod為HTTP請求方法,表示為Spring的HttpMethod列舉。
- 由當前請求區域設定的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公開的原始InputStream / Reader。
- java.io.OutputStream / java.io.Writer用於生成響應的內容。該值是由Servlet API公開的原始OutputStream / Writer。
- @SessionAttribute用於訪問現有的永久會話屬性(例如,使用者認證物件)的註釋引數,而不是通過@SessionAttributes作為控制器工作流的一部分臨時儲存在會話中的模型屬性。
- @RequestAttribute用於訪問請求屬性的註釋引數。
- HttpEntity <?>引數訪問Servlet請求HTTP頭和內容。請求流將使用HttpMessageConverters轉換為實體。請參閱 the section called “Using HttpEntity”.
- java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap用於豐富暴露於Web檢視的隱式模型。
- org.springframework.web.servlet.mvc.support.RedirectAttributes來指定在重定向情況下使用的精確的屬性集,並且還新增Flash屬性(臨時儲存在伺服器端的屬性,使其可以在請求之後使用重定向)。請參見 the section called “Passing Data To the Redirect Target” 和Section 18.6, “Using flash attributes”.
- 根據@InitBinder方法和/或HandlerAdapter配置,命令或表單物件將請求引數繫結到bean屬性(通過setter)或直接轉換為欄位,並進行可定製的型別轉換。請參閱RequestMappingHandlerAdapter上的webBindingInitializer屬性。預設情況下,這些命令物件及其驗證結果將作為模型屬性公開,使用命令類名稱 – 例如。對於“some.package.OrderAddress”型別的命令物件的model屬性“orderAddress”。 ModelAttribute註釋可以用於方法引數來自定義所使用的模型屬性名稱。
- org.springframework.validation.Errors / org.springframework.validation.BindingResult驗證前一個命令或表單物件的結果(即在前面的方法引數)。
- 用於將表單處理標記為完整的org.springframework.web.bind.support.SessionStatus狀態控制代碼,它觸發在處理程式型別級別上由@SessionAttributes註釋指示的會話屬性的清除。
- org.springframework.web.util.UriComponentsBuilder用於準備與當前請求的主機,埠,方案,上下文路徑以及servlet對映的文字部分相關的URL的構建器。
錯誤或BindingResult引數必須遵循正在繫結的模型物件,因為方法簽名可能有多個模型物件,Spring將為每個模型物件建立一個單獨的BindingResult例項,因此以下示例將不起作用:
BindingResult和@ModelAttribute的排序無效。
@PostMapping public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result