1. Spring Web MVC
Spring Web MVC是在Servlet API上構建的原始Web框架,從一開始就包含在Spring框架中。其正式名稱“Spring Web MVC”來自其源模塊(Spring -webmvc)的名稱,但它更常見的名稱是“Spring MVC”。
與Spring Web MVC並行,Spring Framework 5.0引入了一個響應堆棧Web框架,其名稱“Spring WebFlux”也基於其源模塊(Spring - WebFlux)。本節介紹Spring Web MVC。下一節討論Spring WebFlux。
有關基線信息以及與Servlet容器和Java EE版本範圍的兼容性,請參閱Spring Framework Wiki。
1.1. DispatcherServlet
與許多其他web框架一樣,Spring MVC也是圍繞前端控制器模式設計的,其中一箇中央Servlet DispatcherServlet提供了一個用於請求處理的共享算法,而實際工作是由可配置的委託組件執行的。該模型靈活,支持多種工作流程。
與任何Servlet一樣,DispatcherServlet需要使用Java配置或web.xml根據Servlet規範聲明和映射。然後,DispatcherServlet使用Spring配置來發現請求映射、視圖解析、異常處理等所需的委託組件。
下面的Java配置實例註冊並初始化DispatcherServlet,它由Servlet容器自動檢測(參見 Servlet Config):
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
除了直接使用ServletContext API之外,您還可以擴展AbstractAnnotationConfigDispatcherServletInitializer並覆蓋特定的方法(參見上下文層次結構下的示例 Context Hierarchy)。
下面的web.xml配置實例註冊並初始化DispatcherServlet:
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</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>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
注意:Spring引導遵循不同的初始化順序。Spring Boot沒有掛接到Servlet容器的生命週期中,而是使用Spring配置來引導自身和嵌入的Servlet容器。過濾器和Servlet聲明在Spring配置中檢測到,並向Servlet容器註冊。有關更多細節,請參閱 Spring Boot documentation。
1.1.1. Context層次結構
DispatcherServlet期望WebApplicationContext(普通ApplicationContext的擴展)用於自己的配置。WebApplicationContext有一個指向ServletContext及其關聯的Servlet的鏈接。它還綁定到ServletContext,以便應用程序可以使用requestcontext上的靜態方法來查找WebApplicationContext(如果它們需要訪問它)。
對於許多應用程序來說,擁有一個單一的WebApplicationContext就足夠了。也可以有一個上下文層次結構,其中一個根WebApplicationContext在多個DispatcherServlet(或其他Servlet)實例之間共享,每個實例都有自己的子WebApplicationContext配置。有關上下文層次結構特性的更多信息,請參見ApplicationContext的附加功能。
根WebApplicationContext通常包含基礎設施bean,比如需要在多個Servlet實例之間共享的數據存儲庫和業務服務。這些bean可以有效地繼承,並且可以在特定於Servlet的子WebApplicationContext中重寫(即重新聲明),該上下文通常包含給定Servlet的本地bean。下圖顯示了這種關係:
下面的例子配置了一個WebApplicationContext層次結構:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
注意:如果不需要應用程序上下文層次結構,則應用程序可以通過getRootConfigClasses()返回所有配置,並通過getServletConfigClasses()返回null。
下面的示例展示了web.xml的等效版本:
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
注意:如果不需要應用程序上下文層次結構,那麼應用程序可能只配置一個“根”上下文,而將contextConfigLocation Servlet參數設置爲空。
1.1.2。特殊的Bean類型
DispatcherServlet將委託給特殊bean來處理請求並呈現適當的響應。我們所說的“特殊bean”是指實現框架契約的spring管理對象實例。這些通常帶有內置的契約,但是您可以自定義它們的屬性並擴展或替換它們。
下表列出了DispatcherServlet檢測到的特殊bean:
Bean type | Explanation |
---|---|
|
將請求與用於預處理和後處理的攔截器列表映射到處理程序。映射基於一些標準,其細節因HandlerMapping實現而異。 兩個主要的HandlerMapping實現是RequestMappingHandlerMapping(它支持@RequestMapping註釋的方法)和SimpleUrlHandlerMapping(它維護URI路徑模式到處理程序的顯式註冊)。 |
|
幫助DispatcherServlet調用映射到請求的處理程序,而不管實際如何調用處理程序。例如,調用帶註釋的控制器需要解析註釋。HandlerAdapter的主要目的是保護DispatcherServlet不受這些細節的影響。 |
解決異常的策略,可能將異常映射到處理程序、HTML錯誤視圖或其他目標。看到異常。 |
|
將從處理程序返回的基於邏輯字符串的視圖名稱解析爲要呈現給響應的實際視圖。參見 View Resolution和View Technologies。 |
|
解析客戶端正在使用的語言環境和時區,以便能夠提供國際化的視圖。看Locale。 | |
解析web應用程序可以使用的主題——例如,提供個性化的佈局。看到Themes。 |
|
在一些多部分解析庫的幫助下解析多部分請求(例如,瀏覽器表單文件上傳)的抽象。看 Multipart Resolver。 |
|
存儲和檢索“輸入”和“輸出”FlashMap,它們可用於將屬性從一個請求傳遞到另一個請求,通常是通過重定向。看Flash Attributes. |
1.1.3 Web MVC配置
應用程序可以聲明在處理請求所需的特殊Bean類型中列出的基礎設施Bean。DispatcherServlet檢查每個特殊bean的WebApplicationContext。如果沒有匹配的bean類型,則返回到DispatcherServlet.properties
中列出的默認類型。
在大多數情況下, MVC Config是最好的起點。它以Java或XML聲明所需的bean,並提供高級配置回調API對其進行自定義。
注意:Spring Boot依賴於MVC Java配置來配置Spring MVC,並提供了許多額外的方便選項。
1.1.4 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("/");
}
}
WebApplicationInitializer是Spring MVC提供的一個接口,它可以確保檢測到您的實現並自動用於初始化Servlet 3容器。名爲AbstractDispatcherServletInitializer的WebApplicationInitializer的抽象基類實現使註冊DispatcherServlet更加容易,方法是覆蓋指定servlet映射和DispatcherServlet配置位置的方法。
對於使用基於java的Spring配置的應用程序,建議這樣做,如下面的示例所示:
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[] { "/" };
}
}
如果使用基於xml的Spring配置,應該直接從AbstractDispatcherServletInitializer擴展,如下面的示例所示:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
AbstractDispatcherServletInitializer還提供了一種方便的方法來添加過濾器實例,並將它們自動映射到DispatcherServlet,如下面的示例所示:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
每個過濾器都根據其具體類型添加一個默認名稱,並自動映射到DispatcherServlet。
AbstractDispatcherServletInitializer的isAsyncSupported保護方法提供了一個單獨的位置來在DispatcherServlet和映射到它的所有過濾器上啓用異步支持。默認情況下,此標誌設置爲true。
最後,如果您需要進一步定製DispatcherServlet本身,您可以覆蓋createDispatcherServlet方法。
1.1.5。Processing
DispatcherServlet按如下方式處理請求:
- 在請求中搜索WebApplicationContext並將其綁定爲控制器和流程中的其他元素可以使用的屬性。默認情況下,它是在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE下綁定的關鍵。
- 語言環境解析器被綁定到請求,以讓流程中的元素解析處理請求時使用的語言環境(呈現視圖、準備數據,等等)。如果不需要區域設置解析,則不需要區域設置解析器。
- 主題解析器被綁定到讓元素(如視圖)決定使用哪個主題的請求。如果不使用主題,可以忽略它。
- 如果指定了多部分文件解析器,將檢查請求是否包含多部分。如果找到了多部分,則將請求包裝在MultipartHttpServletRequest中,以便進程中的其他元素進行進一步處理。有關多部件處理的詳細信息,請參閱多部件解析器。
- 尋找合適的處理程序。如果找到處理程序,則執行與處理程序(前處理器、後處理器和控制器)關聯的執行鏈,以便準備模型或呈現。另外,對於帶註釋的控制器,可以(在HandlerAdapter中)呈現響應,而不是返回視圖。
- 如果返回模型,則呈現視圖。如果沒有返回模型(可能是由於預處理程序或後處理程序攔截了請求,也可能是出於安全原因),就不會呈現視圖,因爲請求可能已經被滿足了。
在WebApplicationContext中聲明的HandlerExceptionResolver bean用於解決請求處理期間拋出的異常。這些異常解決程序允許定製處理異常的邏輯。有關更多細節,請參見例外。
Spring DispatcherServlet還支持返回Servlet API指定的最後修改日期。確定特定請求的最後修改日期的過程非常簡單:DispatcherServlet查找適當的處理程序映射,並測試找到的處理程序是否實現LastModified接口。如果是,則將LastModified接口的long getLastModified(request)方法的值返回給客戶機。
您可以通過向web.xml文件中的Servlet聲明添加Servlet初始化參數(init-param元素)來定製各個DispatcherServlet實例。下表列出了支持的參數:
Parameter | Explanation |
---|---|
|
實現ConfigurableWebApplicationContext的類,由這個Servlet實例化並在本地配置。默認情況下,使用XmlWebApplicationContext。 |
|
傳遞給上下文實例(由contextClass指定)的字符串,以指示在何處可以找到上下文。字符串可能由多個字符串(使用逗號作爲分隔符)組成,以支持多個上下文。對於定義了兩次的多個上下文位置的bean,最新位置優先。 |
|
WebApplicationContext的名稱空間。默認爲servlet-name servlet。 |
|
當沒有爲請求找到處理程序時,是否拋出NoHandlerFoundException。然後可以使用HandlerExceptionResolver(例如,通過使用@ExceptionHandler控制器方法)捕獲異常,並像處理其他任何異常一樣處理它。 默認情況下,這個設置爲false,在這種情況下,DispatcherServlet將響應狀態設置爲404 (NOT_FOUND),而不會引發異常。 注意,如果還配置了默認的servlet處理,未解析的請求總是會被轉發到默認的servlet,並且不會引發404。 |
1.1.6。攔截
所有HandlerMapping實現都支持處理程序攔截器,當您希望將特定功能應用於特定請求時,這些攔截器非常有用——例如,檢查主體。攔截器必須從org.springframe .web實現HandlerInterceptor。servlet包有三種方法,可以提供足夠的靈活性來進行各種預處理和後處理:
- preHandle(..):在實際的處理程序執行之前
- postHandle(..):處理程序執行後
- afterCompletion(..):完成請求後
preHandle(..)方法返回一個布爾值。您可以使用此方法中斷或繼續執行鏈的處理。當此方法返回true時,處理程序執行鏈將繼續。當它返回false時,DispatcherServlet假設攔截器本身已經處理了請求(例如,呈現了一個適當的視圖),並且沒有繼續執行執行鏈中的其他攔截器和實際的處理程序。
有關如何配置攔截器的示例,請參閱MVC配置一節中的攔截器。您還可以通過在各個HandlerMapping實現上使用setter來直接註冊它們.
注意,postHandle在@ResponseBody和ResponseEntity方法中不太有用,因爲響應是在HandlerAdapter中寫入並提交的,而且是在postHandle之前。這意味着對響應進行任何更改都太晚了,比如添加額外的標題。對於這樣的場景,您可以實現ResponseBodyAdvice,或者將它聲明爲控制器通知bean,或者直接在RequestMappingHandlerAdapter上配置它。
1.1.7。異常
如果在請求映射期間發生異常,或者從請求處理程序(例如@Controller)拋出異常,DispatcherServlet將委託給HandlerExceptionResolver bean鏈來解決異常並提供替代處理,這通常是一個錯誤響應。
下表列出了可用的HandlerExceptionResolver實現:
HandlerExceptionResolver |
Description |
---|---|
|
異常類名和錯誤視圖名之間的映射。用於在瀏覽器應用程序中呈現錯誤頁面。 |
解決Spring MVC引發的異常,並將它們映射到HTTP狀態碼。參見備用的ResponseEntityExceptionHandler和REST API異常REST API exceptions.。 | |
|
使用@ResponseStatus註釋解決異常,並根據註釋中的值將它們映射到HTTP狀態代碼。 |
|
通過調用@Controller或@ControllerAdvice類中的@ExceptionHandler方法來解決異常。看@ExceptionHandler methods. |
解析器鏈
通過在Spring配置中聲明多個HandlerExceptionResolver bean並根據需要設置它們的order屬性,可以形成一個異常解析器鏈。order屬性越高,異常解析器的位置就越晚。
HandlerExceptionResolver的契約規定它可以返回:
- 指向錯誤視圖的ModelAndView。
- 如果在解析器中處理了異常,則爲空的ModelAndView。
- 如果異常未得到解決,則爲null,以便後續解析器嘗試,如果異常在最後仍然存在,則允許它向上冒泡到Servlet容器。
MVC配置自動爲默認Spring MVC異常、@ResponseStatus註釋異常和@ExceptionHandler方法支持聲明內置解析器。您可以自定義該列表或替換它。
容器錯誤頁面
如果任何HandlerExceptionResolver都無法解決異常,因此只能傳播,或者如果響應狀態設置爲錯誤狀態(即4xx、5xx), Servlet容器可以在HTML中呈現一個默認的錯誤頁面。要自定義容器的默認錯誤頁面,可以在web.xml中聲明錯誤頁面映射。下面的例子演示瞭如何做到這一點:
<error-page>
<location>/error</location>
</error-page>
對於前面的示例,當出現異常或響應出現錯誤狀態時,Servlet容器在容器內將錯誤分派到配置的URL(例如/error)。然後由DispatcherServlet處理,可能會將其映射到一個@Controller,該控制器可以實現爲返回一個帶有模型的錯誤視圖名或呈現一個JSON響應,如下面的示例所示:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
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;
}
}
注意:Servlet API沒有提供在Java中創建錯誤頁面映射的方法。但是,您可以同時使用WebApplicationInitializer和最小的web.xml。
1.1.8。視圖解析
Spring MVC定義了ViewResolver和View接口,它們允許您在瀏覽器中呈現模型,而不必將您綁定到特定的視圖技術。ViewResolver提供了視圖名稱和實際視圖之間的映射。視圖處理數據在移交給特定視圖技術之前的準備工作。
下表提供了更多關於ViewResolver層次結構的細節:
ViewResolver | Description |
---|---|
|
AbstractCachingViewResolver的子類緩存它們解析的視圖實例。緩存提高了某些視圖技術的性能。您可以通過將緩存屬性設置爲false來關閉緩存。此外,如果必須在運行時刷新某個視圖(例如,修改FreeMarker模板時),可以使用removeFromCache(String viewName, Locale loc)方法。 |
|
ViewResolver的實現,它接受使用與Spring的XML bean工廠相同的DTD用XML編寫的配置文件。默認的配置文件是/WEB-INF/views.xml。 |
|
在ResourceBundle中使用bean定義的ViewResolver的實現,由包的基本名稱指定。對於每個要解析的視圖,它使用屬性[viewname].(class)的值作爲視圖類,使用屬性[viewname]的值。url作爲視圖url。您可以在有關視圖技術 View Technologies的章節中找到示例。 |
|
ViewResolver接口的簡單實現,它影響邏輯視圖名稱到url的直接解析,而不需要顯式的映射定義。如果您的邏輯名稱與視圖資源的名稱直接匹配,而不需要任意映射,那麼這是合適的。 |
|
UrlBasedViewResolver的方便子類,它支持內部資源視圖(實際上是servlet和jsp)和子類,如JstlView和TilesView。可以使用setViewClass(..)爲這個解析器生成的所有視圖指定視圖類。有關詳細信息,請參閱UrlBasedViewResolver javadoc。 |
|
UrlBasedViewResolver的方便子類,它支持FreeMarkerView和它們的自定義子類。 |
|
ViewResolver接口的實現,它根據請求文件名或Accept頭解析視圖。看Content Negotiation. |
Handling
您可以通過聲明多個衝突解決程序bean來鏈接視圖衝突解決程序,如果需要,還可以通過設置order屬性來指定順序。請記住,order屬性越高,視圖解析器在鏈中的位置就越晚。
ViewResolver的契約指定它可以返回null來表示找不到視圖。但是,對於JSP和InternalResourceViewResolver,判斷JSP是否存在的惟一方法是通過RequestDispatcher執行分派。因此,您必須始終將一個InternalResourceViewResolver配置爲在視圖解析器的整體順序中最後一個。
配置視圖解析非常簡單,只需將ViewResolver bean添加到Spring配置中即可。MVC配置爲視圖解析器和添加無邏輯視圖控制器提供了專用的配置API,這對於沒有控制器邏輯的HTML模板呈現非常有用。
Redirecting
視圖名稱中的特殊重定向:前綴允許執行重定向。UrlBasedViewResolver(及其子類)將其視爲需要重定向的指令。視圖名稱的其餘部分是重定向URL。
最終效果與控制器返回RedirectView相同,但是現在控制器本身可以根據邏輯視圖名進行操作。邏輯視圖名(如redirect:/myapp/some/resource)相對於當前Servlet上下文進行重定向,而名稱(如redirect: https://myhost.com/some/仲裁員/path)則重定向到一個絕對URL。
注意,如果用@ResponseStatus註釋控制器方法,則註釋值優先於RedirectView設置的響應狀態。
Forwarding
您還可以使用一個特殊的forward:前綴來表示最終由UrlBasedViewResolver和子類解析的視圖名稱。這將創建一個InternalResourceView,它執行一個RequestDispatcher.forward()。因此,這個前綴在InternalResourceViewResolver和InternalResourceView(用於JSP)中並不有用,但是如果您使用另一種視圖技術,但仍然希望強制Servlet/JSP引擎處理資源的轉發,那麼它可能會有幫助。請注意,您還可以鏈接多個視圖解析器。
內容協商
contentatingviewresolver本身並不解析視圖,而是將視圖委託給其他視圖解析器,並選擇與客戶端請求的表示形式相似的視圖。可以從Accept頭或查詢參數(例如,“/path?format=pdf”)確定表示。
contentatingviewresolver通過比較請求媒體類型和與每個viewresolver關聯的視圖所支持的媒體類型(也稱爲Content-Type)來選擇一個適當的視圖來處理請求。具有兼容內容類型的列表中的第一個視圖將表示返回給客戶機。如果ViewResolver鏈不能提供兼容的視圖,則會參考通過DefaultViews屬性指定的視圖列表。後一個選項適用於單例視圖,它可以呈現當前資源的適當表示,而不管邏輯視圖名稱如何。Accept標頭可以包含通配符(例如text/*),在這種情況下,內容類型爲text/xml的視圖是兼容的。
有關配置細節,請參閱 MVC Config下的視圖解析器。
1.1.9. Locale
Spring架構的大多數部分都支持國際化,就像Spring web MVC框架一樣。DispatcherServlet允許您通過使用客戶端的區域設置來自動解析消息。這是通過LocaleResolver對象完成的。
當請求傳入時,DispatcherServlet將查找語言環境解析器,如果找到的話,它將嘗試使用它來設置語言環境。通過使用RequestContext.getLocale()方法,您總是可以檢索由語言環境解析器解析的語言環境。
除了自動區域解析之外,您還可以將一個攔截器附加到處理程序映射(有關處理程序映射攔截器的更多信息,請參見攔截),以在特定環境下更改區域設置(例如,基於請求中的參數)。
區域設置解析器和攔截器是在org.springframework.web.servlet中定義的。i18n包,並在應用程序上下文中以常規方式配置。以下區域設置解析器的選擇包含在Spring中。
時區
除了獲得客戶機的地區之外,瞭解它的時區通常也很有用。LocaleContextResolver接口提供了對LocaleResolver的擴展,該擴展允許解析器提供更豐富的LocaleContext,其中可能包括時區信息。
如果可用,可以使用RequestContext.getTimeZone()方法獲得用戶的時區。任何在Spring的ConversionService中註冊的日期/時間轉換器和格式化程序對象都會自動使用時區信息。
Header解析器
此區域設置解析器檢查客戶機(例如,web瀏覽器)發送的請求中的accept-language頭。通常,這個頭字段包含客戶端操作系統的區域設置。請注意,此解析器不支持時區信息。
Cookie 解析器
此區域設置解析器檢查客戶端上可能存在的Cookie,以查看是否指定了區域設置或時區。如果是,則使用指定的詳細信息。通過使用此區域設置解析器的屬性,可以指定cookie的名稱和最大年齡。下面的例子定義了一個CookieLocaleResolver:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="clientlanguage"/>
<!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
<property name="cookieMaxAge" value="100000"/>
</bean>
下表描述了CookieLocaleResolver的屬性:
Property | Default | Description |
---|---|---|
|
classname + LOCALE |
The name of the cookie |
|
Servlet container default |
cookie在客戶端上持續的最長時間。如果指定-1,cookie將不會被持久化。它只在客戶端關閉瀏覽器之前可用。 |
|
/ |
將cookie的可見性限制在站點的某個部分。當指定cookiePath時,cookie僅對該路徑及其下面的路徑可見。 |
會話解析器
SessionLocaleResolver允許您從可能與用戶請求相關聯的會話中檢索語言環境和時區。與CookieLocaleResolver不同,此策略將本地選擇的地區設置存儲在Servlet容器的HttpSession中。因此,這些設置對於每個會話都是臨時的,因此,當每個會話終止時,這些設置就會丟失。
注意,它與外部會話管理機制(如Spring會話項目)沒有直接關係。此SessionLocaleResolver針對當前HttpServletRequest計算並修改相應的HttpSession屬性。
現場攔截器
您可以通過將LocaleChangeInterceptor添加到HandlerMapping定義之一來啓用地區更改。它檢測請求中的一個參數,並相應地更改地區,在dispatcher的應用程序上下文中調用LocaleResolver上的setLocale方法。下一個示例顯示調用all *。包含名爲siteLanguage的參數的資源現在將更改區域設置。例如,一個URL請求https://www.sf.net/home.view?站點語言=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>
1.1.10。主題
您可以應用Spring Web MVC框架主題來設置應用程序的整體外觀,從而增強用戶體驗。主題是影響應用程序視覺樣式的靜態資源(通常是樣式表和圖像)的集合。
定義一個主題
要在web應用程序中使用主題,必須設置org.springframework.ui.context的實現。ThemeSource接口。WebApplicationContext接口擴展了ThemeSource,但將其職責委託給一個專用的實現。默認情況下,委託是一個org.springframe .ui.context.support。ResourceBundleThemeSource實現,它從類路徑的根目錄加載屬性文件。要使用自定義的ThemeSource實現或配置ResourceBundleThemeSource的基本名稱前綴,您可以在應用程序上下文中使用保留的名稱ThemeSource註冊bean。web應用程序上下文自動檢測具有該名稱的bean並使用它。
當您使用ResourceBundleThemeSource時,主題是在一個簡單的屬性文件中定義的。屬性文件列出了組成主題的資源,如下例所示:
styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
屬性的鍵是引用視圖代碼中的主題元素的名稱。對於JSP,通常使用spring:theme自定義標記來實現這一點,它與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使用空的基名稱前綴。結果,屬性文件從類路徑的根目錄加載。於是,你就放涼了。類路徑(例如,/WEB-INF/classes)根目錄中的屬性主題定義。ResourceBundleThemeSource使用標準的Java資源包加載機制,允許主題的完全國際化。例如,我們可以有一個/WEB-INF/classes/cool_nl。引用帶有荷蘭文字的特殊背景圖像的屬性。
Resolving 主題
在定義主題(如前一節所述)之後,您將決定使用哪個主題。DispatcherServlet查找一個名爲themeResolver的bean,以確定使用哪個themeResolver實現。主題解析器的工作方式與LocaleResolver非常相似。它檢測用於特定請求的主題,還可以更改請求的主題。下表描述了Spring提供的主題解析器:
Class | Description |
---|---|
|
選擇使用defaultThemeName屬性設置的固定主題。 |
|
主題在用戶的HTTP會話中維護。它只需要爲每個會話設置一次,而不是在會話之間持久化。 |
|
所選主題存儲在客戶端的cookie中。 |
Spring還提供了一個ThemeChangeInterceptor,它允許使用一個簡單的請求參數來更改每個請求的主題。
1.1.11。多部分解析器
來自org.springframe .web的MultipartResolver。多部分包是一種解析包括文件上傳在內的多部分請求的策略。有一種實現基於Commons FileUpload,另一種實現基於Servlet 3.0多部分請求解析。
要啓用多部分處理,您需要在DispatcherServlet Spring配置中聲明一個名爲MultipartResolver的MultipartResolver bean。DispatcherServlet檢測它並將其應用於傳入的請求。當接收到內容類型爲multipart/form-data的POST時,解析器解析內容並將當前HttpServletRequest包裝爲MultipartHttpServletRequest,以提供對已解析部分的訪問,並將其作爲請求參數公開。
Apache Commons FileUpload
要使用Apache Commons FileUpload,可以使用multipartResolver配置CommonsMultipartResolver類型的bean。您還需要將commons-fileupload作爲類路徑的依賴項。
Servlet 3.0
Servlet 3.0多部分解析需要通過Servlet容器配置來啓用。這樣做:
- 在Java中,在Servlet註冊時設置一個MultipartConfigElement。
- 在網絡上。將“<multipart-config>”部分添加到servlet聲明中。
下面的示例展示瞭如何在Servlet註冊時設置MultipartConfigElement:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// ...
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
}
}
一旦Servlet 3.0配置就緒,您就可以添加一個類型爲StandardServletMultipartResolver、名稱爲multipartResolver的bean。
1.1.12。日誌記錄
Spring MVC中的調試級日誌記錄被設計爲緊湊、最少且對人類友好。它側重於反覆有用的高價值信息,而不是隻在調試特定問題時有用的信息。
跟蹤級日誌通常遵循與調試相同的原則(例如,也不應該是消防軟管),但是可以用於調試任何問題。此外,一些日誌消息可能在跟蹤和調試時顯示不同級別的詳細信息。
良好的日誌記錄來自於使用日誌的經驗。如果你發現任何不符合既定目標的地方,請告訴我們。
敏感數據
調試和跟蹤日誌記錄可能會記錄敏感信息。這就是爲什麼請求參數和報頭在默認情況下是隱藏的,並且必須通過DispatcherServlet上的enableLoggingRequestDetails屬性顯式地啓用它們的完整日誌記錄。
下面的例子展示瞭如何通過使用Java配置來做到這一點:
public class MyInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return ... ;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return ... ;
}
@Override
protected String[] getServletMappings() {
return ... ;
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setInitParameter("enableLoggingRequestDetails", "true");
}
}
1.2。Filters過濾器
spring-web模塊提供了一些有用的過濾器:
1.2.1。表單數據
瀏覽器只能通過HTTP GET或HTTP POST提交表單數據,而非瀏覽器客戶機也可以使用HTTP PUT、PATCH和DELETE。Servlet API需要ServletRequest.getParameter*()方法來僅支持HTTP POST的表單字段訪問。
spring web模塊提供FormContentFilter攔截HTTP PUT,補丁,和刪除請求的內容類型應用程序/ x-www-form-urlencoded,從身體的讀取表單數據請求,並將ServletRequest表單數據可以通過ServletRequest.getParameter *()的方法。
1.2.2. Forwarded Headers
當請求通過代理(如負載平衡器)時,主機、端口和模式可能會發生變化,這使得從客戶端角度創建指向正確主機、端口和模式的鏈接成爲一項挑戰。
RFC 7239定義了轉發的HTTP頭,代理可以使用它來提供關於原始請求的信息。還有其他非標準頭文件,包括X-Forwarded-Host
, X-Forwarded-Port
, X-Forwarded-Proto
, X-Forwarded-Ssl
, and X-Forwarded-Prefix
。
forwarding headerfilter是一個Servlet過濾器,它修改請求以a)根據轉發的頭更改主機、端口和方案,b)刪除這些頭以消除進一步的影響。篩選器依賴於包裝請求,因此它必須先於其他篩選器(如RequestContextFilter)排序,後者應該處理修改後的請求,而不是原始請求。
由於應用程序無法知道報頭是由代理按預期添加的還是由惡意客戶端添加的,因此對於轉發的報頭存在安全考慮。這就是爲什麼信任邊界處的代理應該配置爲刪除來自外部的不受信任的轉發頭。還可以使用removeOnly=true配置forwarding headerfilter,在這種情況下,它刪除但不使用header。
爲了支持異步請求和錯誤調度,這個過濾器應該使用DispatcherType映射。ASYNC和DispatcherType.ERROR。如果使用Spring框架的AbstractAnnotationConfigDispatcherServletInitializer(請參閱Servlet配置),則所有篩選器都將自動註冊爲所有分派類型。但是,如果通過web.xml註冊過濾器,或者在Spring啓動時通過FilterRegistrationBean註冊過濾器,則一定要包含DispatcherType.ASYNC
和DispatcherType.ERROR。除了DispatcherType.REQUEST之外。
1.2.3。Shallow ETag
ShallowEtagHeaderFilter過濾器通過緩存寫入響應的內容並從中計算MD5散列來創建“淺層”ETag。下一次客戶端發送時,它也會執行相同的操作,但它也會將計算的值與if - none - match請求標頭進行比較,如果兩者相等,則返回304 (NOT_MODIFIED)。
這種策略節省了網絡帶寬,但不節省CPU,因爲每個請求都必須計算完整的響應。前面描述的控制器級的其他策略可以避免計算。看HTTP Caching。
這個過濾器有一個writeWeakETag參數,它將過濾器配置爲寫弱ETags,類似於以下內容:W/“02a2d595e6ed9a0b24f027f2b63b134d6”(如RFC 7232部分2.3中定義的那樣)。
爲了支持 asynchronous requests,這個過濾器必須使用DispatcherType映射。異步,以便篩選器可以延遲併成功地生成ETag,直到最後一次異步調度結束。如果使用Spring框架的AbstractAnnotationConfigDispatcherServletInitializer(請參閱 Servlet Config),則所有篩選器都將自動註冊爲所有分派類型。但是,如果通過web.xml註冊過濾器,或者在Spring啓動時通過FilterRegistrationBean註冊過濾器,則一定要包含DispatcherType.ASYNC。
1.2.4。CORS
Spring MVC通過對控制器的註釋爲CORS配置提供了細粒度的支持。但是,當與Spring Security一起使用時,我們建議使用內置的CorsFilter,它必須在Spring Security的過濾器鏈之前訂購。
有關 CORS和CORS Filter的詳細信息,請參閱相關章節。
1.3. Annotated Controllers
Spring MVC提供了一個基於註釋的編程模型,其中@Controller和@RestController組件使用註釋來表示請求映射、請求輸入、異常處理等。帶註釋的控制器具有靈活的方法簽名,不需要擴展基類或實現特定的接口。下面的例子展示了一個由註解定義的控制器:
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
在前面的示例中,該方法接受模型並以字符串的形式返回視圖名,但是還有許多其他選項,本章稍後將對此進行解釋。
關於spring.io的指南和教程。使用本節中描述的基於註釋的編程模型。
1.3.1. Declaration
您可以在Servlet的WebApplicationContext中使用標準的Spring bean定義來定義控制器bean。@Controller原型允許自動檢測,與Spring一般支持的在類路徑中檢測@Component類以及爲它們自動註冊bean定義相一致。它還充當帶註釋的類的原型,指示它作爲web組件的角色。
要啓用自動檢測這樣的@Controller bean,您可以將組件掃描添加到您的Java配置中,如下面的示例所示:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
下面的例子展示了與前面例子等價的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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.web"/>
<!-- ... -->
</beans>
@RestController是一個複合註釋composed annotation,它本身是由@Controller和@ResponseBody組成的元註釋,用於指示一個控制器,該控制器的每個方法都繼承類型級別的@ResponseBody註釋,因此,它直接寫入響應體而不是視圖解析,並使用HTML模板進行呈現。
AOP代理
在某些情況下,可能需要在運行時用AOP代理裝飾控制器。例如,如果您選擇直接在控制器上使用@Transactional註釋。在這種情況下,特別是對於控制器,我們建議使用基於類的代理。這通常是控制器的默認選擇。但是,如果控制器必須實現不是Spring上下文回調的接口(例如InitializingBean、*Aware等),則可能需要顯式地配置基於類的代理。例如,使用<tx: annotated -driven/>可以更改爲<tx: annotated -driven proxy-target-class="true"/>,使用@EnableTransactionManagement可以更改爲@EnableTransactionManagement(proxyTargetClass = true)。
1.3.2。請求映射
可以使用@RequestMapping註釋將請求映射到控制器方法。它有各種屬性,可以根據URL、HTTP方法、請求參數、標題和媒體類型進行匹配。您可以在類級使用它來表示共享映射,或者在方法級使用它來縮小到特定端點映射。
也有HTTP方法特定的快捷方式變量@RequestMapping:
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
快捷鍵是提供的自定義註釋,因爲大多數控制器方法應該映射到特定的HTTP方法,而不是使用@RequestMapping,後者默認情況下與所有HTTP方法匹配。與此同時,在類級別上仍然需要@RequestMapping來表示共享映射。
下面的例子有類型和方法級別的映射:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URI模式
您可以使用以下全局模式和通配符來映射請求:
- ? 匹配一個字符
- * 匹配路徑段中的零個或多個字符
- ** 匹配零個或多個路徑段
您還可以聲明URI變量並使用@PathVariable訪問它們的值,如下面的示例所示:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
您可以在類和方法級別聲明URI變量,如下面的示例所示:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
URI變量被自動轉換爲適當的類型,或者拋出TypeMismatchException。默認情況下支持簡單類型(int、long、Date等),您可以爲任何其他數據類型註冊支持。參見類型轉換和數據綁定。
您可以顯式地命名URI變量(例如,@PathVariable(“customId”)),但是如果名稱相同,並且您的代碼是用調試信息或Java 8上的-parameters編譯器標誌編譯的,那麼您可以省略這個細節。
語法{varName:regex}用一個正則表達式聲明一個URI變量,該正則表達式的語法爲{varName:regex}。例如,給定URL“/spring-web-3.0.5 .jar”,下面的方法提取名稱、版本和文件擴展名:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
URI路徑模式還可以嵌入${…}佔位符,這些佔位符在啓動時使用PropertyPlaceHolderConfigurer對本地、系統、環境和其他屬性源進行解析。例如,可以使用它根據外部配置對基本URL進行參數化。
注意:Spring MVC使用PathMatcher契約和來自spring-core的AntPathMatcher實現實現URI路徑匹配。
模式對比
當多個模式匹配一個URL時,必須對它們進行比較才能找到最佳匹配。這是通過使用AntPathMatcher完成的。getPatternComparator(字符串路徑),它查找更具體的模式。
如果一個模式的URI變量計數較低(計數爲1)、單個通配符(計數爲1)和雙通配符(計數爲2),那麼該模式就不那麼具體。給定相同的分數和長度,則選擇具有比通配符更多的URI變量的模式。
默認的映射模式(/**)被排除在得分之外,並且總是最後排序。此外,前綴模式(如/public/**)被認爲比其他沒有雙通配符的模式更不具體。
有關詳細信息,請參閱AntPathMatcher中的AntPatternComparator,並請記住您可以自定義PathMatcher實現。請參閱配置部分中的路徑匹配。
後綴匹配
默認情況下,Spring MVC執行.*後綴模式匹配,因此映射到/person的控制器也隱式映射到/person.*。然後,文件擴展名用於解釋用於響應的請求內容類型(即,而不是Accept標頭)——例如/person.pdf
, /person.xml
等。
當瀏覽器用於發送難以一致解釋的Accept標頭時,以這種方式使用文件擴展名是必要的。目前,這不再是必需的,使用Accept標頭應該是首選。
隨着時間的推移,文件擴展名的使用在許多方面都被證明是有問題的。當與使用URI變量、路徑參數和URI編碼重疊時,可能會導致歧義。關於基於url的授權和安全性的推理(參見下一節瞭解更多細節)也變得更加困難。
要完全禁用文件擴展名的使用,您必須設置以下兩個:
- useSuffixPatternMatching(false),看到PathMatchConfigurer
- favorPathExtension(false),看到ContentNegotiationConfigurer
基於URL的內容協商仍然很有用(例如,在瀏覽器中輸入URL時)。爲了實現這一點,我們建議使用基於查詢參數的策略來避免文件擴展所帶來的大多數問題。另外,如果必須使用文件擴展名,可以考慮通過contentconsultationconfigurer的mediaTypes屬性將其限制爲顯式註冊的擴展名列表。
後綴匹配和RFD
反映文件下載(RFD)攻擊與XSS相似,因爲它依賴於響應中反映的請求輸入(例如,查詢參數和URI變量)。然而,RFD攻擊不是將JavaScript插入HTML,而是依賴於瀏覽器切換來執行下載,並在稍後雙擊時將響應視爲可執行腳本。
在Spring MVC中,@ResponseBody和ResponseEntity方法是有風險的,因爲它們可以呈現不同的內容類型,客戶端可以通過URL路徑擴展請求不同的內容類型。禁用後綴模式匹配和使用內容協商的路徑擴展可以降低風險,但不足以防止RFD攻擊。
爲了防止RFD攻擊,在呈現響應體之前,Spring MVC添加了一個content - dispose:inline;filename=f.t theader來建議一個固定且安全的下載文件。只有當URL路徑包含一個文件擴展名,該文件擴展名既不是白名單,也不是爲內容協商顯式註冊的,纔會執行此操作。然而,當url被直接輸入到瀏覽器中時,它可能有潛在的副作用。
許多常見的路徑擴展在默認情況下是白名單的。具有自定義HttpMessageConverter實現的應用程序可以顯式地註冊用於內容協商的文件擴展名,以避免爲這些擴展添加內容配置頭。看 Content Types.。
有關fd的其他建議見 CVE-2015-5211。
可消費的媒體類型
您可以根據請求的內容類型縮小請求映射,如下面的示例所示:
@PostMapping(path = "/pets", consumes = "application/json") //1
public void addPet(@RequestBody Pet pet) {
// ...
}
//1使用屬性根據內容類型縮小映射範圍。
屬性還支持否定表達式——例如,!text/plain表示除text/plain之外的任何內容類型。
您可以在類級別聲明一個shared屬性。但是,與大多數其他請求映射屬性不同的是,當在類級別使用時,方法級別的屬性會覆蓋而不是擴展類級別聲明。
注意:MediaType爲常用的媒體類型(如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE)提供常量。
可延長的媒體類型
您可以根據Accept請求頭和控制器方法產生的內容類型列表來縮小請求映射,如下面的示例所示:
@GetMapping(path = "/pets/{petId}", produces = "application/json") //1
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
使用produces屬性根據內容類型縮小映射範圍。
媒體類型可以指定一個字符集。否定表達式是受支持的-例如,!text/plain表示除“text/plain”以外的任何內容類型。
您可以在類級別聲明shared produces屬性。但是,與大多數其他請求映射屬性不同的是,當在類級別使用時,方法級別生成屬性覆蓋而不是擴展類級別聲明。
注意:MediaType爲常用的媒體類型(如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE)提供常量。
Parameters, headers
可以根據請求參數條件縮小請求映射。您可以測試請求參數(myParam)是否存在,是否缺少一個參數(!myParam),或者特定的值(myParam=myValue)。下面的例子展示瞭如何測試一個特定的值:
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") //1
public void findPet(@PathVariable String petId) {
// ...
}
//1 測試myParam是否等於myValue
你也可以使用相同的請求頭條件,如下面的例子所示:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
/ /……
}
注意:您可以使用header條件來匹配Content-Type和Accept,但是使用和produces更好。
HTTP HEAD, OPTIONS
@GetMapping(和@RequestMapping(method=HttpMethod.GET))對請求映射透明地支持HTTP頭。控制器方法不需要更改。響應包裝器,應用於javax.servlet.http。HttpServlet,確保將內容長度頭設置爲寫入的字節數(不實際寫入響應)。
@GetMapping(和@RequestMapping(method=HttpMethod.GET))被隱式映射到並支持HTTP頭。處理HTTP頭請求的方式與處理HTTP GET類似,不同之處是不寫入主體,而是計算字節數並設置內容長度頭。
默認情況下,HTTP選項是通過將Allow響應頭設置爲所有具有匹配URL模式的@RequestMapping方法中列出的HTTP方法列表來處理的。
對於沒有HTTP方法聲明的@RequestMapping,允許標頭設置爲GET、HEAD、POST、PUT、PATCH、DELETE和選項。控制器方法應該總是聲明支持的HTTP方法(例如,通過使用HTTP方法特定的變體:@GetMapping、@PostMapping和其他)。
您可以顯式地將@RequestMapping方法映射到HTTP HEAD和HTTP選項,但在通常情況下這不是必需的。
自定義註解
Spring MVC支持使用複合註釋進行請求映射。這些註釋本身是由@RequestMapping組成的元註釋,用於重新聲明@RequestMapping屬性的子集(或全部),具有更窄、更具體的用途。
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping和@PatchMapping都是複合註釋的例子。之所以提供這些方法,是因爲大多數控制器方法應該映射到特定的HTTP方法,而不是使用@RequestMapping,後者默認情況下與所有HTTP方法匹配。如果需要複合註釋的示例,請查看如何聲明它們。
Spring MVC還支持自定義請求映射屬性和自定義請求匹配邏輯。這是一個更高級的選項,需要子類化RequestMappingHandlerMapping並覆蓋getCustomMethodCondition方法,在該方法中,您可以檢查自定義屬性並返回您自己的RequestCondition。
明確的註冊
您可以以編程方式註冊處理程序方法,這些方法可用於動態註冊或高級情況,例如在不同url下相同處理程序的不同實例。下面的例子註冊了一個處理程序方法:
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) //1
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build(); //2
Method method = UserHandler.class.getMethod("getUser", Long.class); //3
mapping.registerMapping(info, handler, method); //4
}
}
- 爲控制器注入目標處理程序和處理程序映射。
- 準備請求映射元數據。
- 獲取處理程序方法。
- 添加註冊。
1.3.3。處理程序方法
@RequestMapping處理程序方法有一個靈活的簽名,可以從一系列受支持的控制器方法參數和返回值中進行選擇。
方法參數
下表描述了支持的控制器方法參數。不支持任何參數的響應類型。
JDK 8 java.util.Optional
可選的方法參數與具有required屬性的註釋(例如,@RequestParam、@RequestHeader等)相結合,相當於required=false。
Controller method argument | Description |
---|---|
|
對請求參數和請求和會話屬性的通用訪問,而不直接使用Servlet API。 |
|
選擇任何特定的請求或響應類型——例如,ServletRequest、HttpServletRequest或Spring的MultipartRequest、MultipartHttpServletRequest。 |
|
強制會話的存在。因此,這樣的參數永遠不會爲空。注意,會話訪問不是線程安全的。如果允許多個請求同時訪問一個會話,可以考慮將RequestMappingHandlerAdapter實例的synchronizeOnSession標誌設置爲true。 |
|
Servlet 4.0推送構建器API,用於程序化的HTTP/2資源推送。注意,根據Servlet規範,如果客戶端不支持HTTP/2特性,則注入的PushBuilder實例可以爲空。 |
|
當前已驗證的用戶—如果已知,可能是特定的主體實現類。 |
|
請求的HTTP方法。 |
|
當前請求區域設置,由最特定的可用LocaleResolver(實際上是已配置的LocaleResolver或LocaleContextResolver)決定。 |
|
與當前請求相關的時區,由LocaleContextResolver確定。 |
|
用於訪問Servlet API公開的原始請求體。 |
|
用於訪問Servlet API公開的原始響應體。 |
|
用於訪問URI模板變量。看 URI patterns. |
|
用於訪問URI路徑段中的名稱-值對。看 Matrix Variables. |
|
用於訪問Servlet請求參數,包括多部分文件。參數值被轉換爲聲明的方法參數類型。參見@RequestParam和Multipart。 注意,對於簡單的參數值,@RequestParam是可選的。見表末“任何其他參數”。 |
|
用於訪問請求標頭。標頭值被轉換爲聲明的方法參數類型。看 |
|
獲取cookies。cookie值被轉換爲聲明的方法參數類型。看 |
|
用於訪問HTTP請求體。通過使用HttpMessageConverter實現,將正文內容轉換爲聲明的方法參數類型。See |
|
用於訪問請求頭和正文。主體通過HttpMessageConverter進行轉換。看HttpEntity. |
|
或者訪問多部件/表單數據請求中的部件,使用HttpMessageConverter轉換部件的主體。See Multipart. |
|
用於訪問HTML控制器中使用的模型,並將其作爲視圖呈現的一部分公開給模板。 |
|
指定重定向(即附加到查詢字符串)時使用的屬性,並臨時存儲flash屬性,直到重定向後的請求。See Redirect Attributes and Flash Attributes. |
|
用於訪問模型中的現有屬性(如果不存在則實例化),並應用數據綁定和驗證。參見@ModelAttribute以及Model和DataBinder。 注意,使用@ModelAttribute是可選的(例如,設置它的屬性)。見表末的“任何其他參數”。 |
|
用於訪問來自命令對象(即@ModelAttribute參數)的驗證和數據綁定的錯誤,或來自@RequestBody或@RequestPart參數的驗證的錯誤。必須在驗證方法參數之後立即聲明錯誤或BindingResult參數。 |
|
對於標記表單處理完成,這將觸發通過類級別@SessionAttributes註釋聲明的會話屬性的清理。更多細節見 |
|
或者準備一個相對於當前請求的主機、端口、方案、上下文路徑和servlet映射的文字部分的URL。 See URI Links. |
|
對於任何會話屬性的訪問,與作爲類級@SessionAttributes聲明的結果而存儲在會話中的模型屬性不同。See |
|
用於訪問請求屬性. See |
Any other argument |
如果方法參數沒有與此表中任何較早的值匹配,並且它是一個簡單類型(由BeanUtils#isSimpleProperty確定,它被解析爲@RequestParam。否則,它將解析爲@ModelAttribute。 |
Return Values
Controller method return value | Description |
---|---|
|
返回值通過HttpMessageConverter實現轉換並寫入響應。 See |
|
指定完整響應(包括HTTP報頭和正文)的返回值將通過HttpMessageConverter實現進行轉換並寫入響應。See ResponseEntity. |
|
用於返回帶有標題但沒有正文的響應。 |
|
使用ViewResolver實現解析並與隱式模型一起使用的視圖名——通過命令對象和@ModelAttribute方法確定。處理程序方法還可以通過聲明模型參數以編程方式豐富模型 (see Explicit Registrations). |
|
一個視圖實例,用於與通過命令對象和@ModelAttribute方法確定的隱式模型一起呈現。處理程序方法還可以通過聲明模型參數以編程方式豐富模型(see Explicit Registrations). |
|
要添加到隱式模型中的屬性,通過RequestToViewNameTranslator隱式確定視圖名稱。 |
|
要添加到模型中的屬性,視圖名稱通過RequestToViewNameTranslator隱式確定。 注意@ModelAttribute是可選的。請參閱該表末尾的“任何其他返回值”。 |
|
要使用的視圖和模型屬性,以及(可選的)響應狀態. |
|
如果具有void返回類型(或null返回值)的方法還具有ServletResponse、OutputStream參數或@ResponseStatus註釋,則認爲該方法已經完全處理了響應。如果控制器做了一個積極的ETag或lastModified時間戳檢查,也是一樣的 (see Controllers for details). 如果以上都不是真的,void返回類型也可以表示REST控制器的“無響應體”,或者HTML控制器的默認視圖名選擇。 |
|
從任何線程異步生成前面的任何返回值——例如,作爲某個事件或回調的結果。 See Asynchronous Requests and |
|
在Spring mvc管理的線程中異步生成上述任何一個返回值See Asynchronous Requests and |
|
作爲DeferredResult的替代,以提供便利(例如,當一個底層服務返回其中一個時)。 |
|
使用HttpMessageConverter實現異步發送要寫入響應的對象流。也支持作爲響應實體的主體。 See Asynchronous Requests and HTTP Streaming. |
|
異步寫入響應OutputStream。也支持作爲響應實體的主體。See Asynchronous Requests and HTTP Streaming. |
Reactive types — Reactor, RxJava, or others through |
替代使用多值流(例如,通量,可觀察到的)收集到列表中的DeferredResult。 See Asynchronous Requests and Reactive Types. |
Any other return value |
任何返回值不匹配任何之前的值在這個表,是一個字符串或空白都被視爲一個視圖名稱(默認視圖名稱選擇通過RequestToViewNameTranslator適用),它不是一個簡單的類型,提供由BeanUtils # isSimpleProperty。簡單類型的值仍未解析。 |
類型轉換
一些表示基於字符串的請求輸入的帶註釋的控制器方法參數(如@RequestParam、@RequestHeader、@PathVariable、@MatrixVariable和@CookieValue)可能需要類型轉換,如果參數聲明爲字符串以外的內容。
對於這種情況,將根據配置的轉換器自動應用類型轉換。默認情況下,支持簡單類型(int、long、Date和其他類型)。您可以通過WebDataBinder(請參閱DataBinder)或通過向FormattingConversionService註冊格式化程序來定製類型轉換。See Spring Field Formatting
Matrix Variables
RFC 3986討論路徑段中的名稱-值對。在Spring MVC中,我們根據Tim Berners-Lee的一篇“舊文章”將它們稱爲“矩陣變量”,但它們也可以稱爲URI路徑參數。
矩陣變量可以出現在任何路徑段中,每個變量由分號分隔,多個值由逗號分隔(例如,/cars;color=red,green;year=2012)。還可以通過重複變量名指定多個值(例如,color=red;color=green;color=blue)。
如果期望URL包含矩陣變量,則控制器方法的請求映射必須使用URI變量來屏蔽該變量內容,並確保能夠成功地匹配請求,而不依賴於矩陣變量的順序和存在性。下面的例子使用了一個矩陣變量:
// 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
}
matrix variable(矩陣變量)可以定義爲可選的,也可以指定一個默認值,如下例所示:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
要獲得所有矩陣變量,可以使用MultiValueMap,如下例所示:
// 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" : 22, "s" : 23]
}
注意,您需要啓用矩陣變量。在MVC Java配置中,需要使用removeSemicolonContent=false通過路徑匹配設置UrlPathHelper。在MVC XML名稱空間中,您可以設置< MVC:annotation-driven enable-matrix-variables="true"/>。
@RequestParam
可以使用@RequestParam註釋將Servlet請求參數(即查詢參數或表單數據)綁定到控制器中的方法參數。
下面的例子演示瞭如何做到這一點:
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
默認情況下,使用該註釋的方法參數是必需的,但是您可以通過將@RequestParam註釋的required標記設置爲false或通過使用java.util聲明參數來指定方法參數是可選的。可選的包裝器。
如果目標方法參數類型不是字符串,則自動應用類型轉換。看到類型轉換 Type Conversion。
將參數類型聲明爲數組或列表允許解析相同參數名稱的多個參數值。
當@RequestParam註釋聲明爲Map<String、String>或MultiValueMap<String、String>,而沒有在註釋中指定參數名時,將使用每個給定參數名的請求參數值填充映射。
注意,使用@RequestParam是可選的(例如,設置它的屬性)。默認情況下,任何簡單值類型的參數(由BeanUtils#isSimpleProperty決定)都不會被任何其他參數解析器解析,就像用@RequestParam註釋一樣。
@RequestHeader
可以使用@RequestHeader註釋將請求頭綁定到控制器中的方法參數。
考慮下面的請求和標題:
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
下面的示例獲取Accept-Encoding和Keep-Alive頭信息的值:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
如果目標方法參數類型不是字符串,則自動應用類型轉換。看到類型轉換。
當在Map<String、String>、MultiValueMap<String、String>或HttpHeaders參數上使用@RequestHeader註釋時,映射將使用所有標頭值填充。
注意:內置的支持可用於將逗號分隔的字符串轉換爲類型轉換系統已知的字符串數組或字符串集合或其他類型。例如,@RequestHeader("Accept")註釋的方法參數可以是String類型,也可以是String[]或List<String>。
@CookieValue
可以使用@CookieValue註釋將HTTP cookie的值綁定到控制器中的方法參數。
考慮使用以下cookie的請求:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下面的例子展示瞭如何獲取cookie值:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
如果目標方法參數類型不是字符串,則自動應用類型轉換。看到類型轉換。
@ModelAttribute
您可以在方法參數上使用@ModelAttribute註釋來從模型中訪問屬性,或者在不存在的情況下實例化它。模型屬性還與HTTP Servlet請求參數的值重疊,這些參數的名稱與字段名稱匹配。這稱爲數據綁定,它使您不必解析和轉換各個查詢參數和表單字段。下面的例子演示瞭如何做到這一點:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
上面的Pet實例解析如下:
- 如果已經使用模型添加,則從模型中添加。
- 通過使用@SessionAttributes來自HTTP會話。
- 通過轉換器傳遞的URI路徑變量(參見下一個示例)。
- 默認構造函數的調用。
- 使用與Servlet請求參數匹配的參數調用“主構造函數”。參數名通過JavaBeans @ConstructorProperties或字節碼中運行時保留的參數名確定。
雖然通常使用模型用屬性填充模型,但另一種替代方法是依賴轉換器<String, T>和URI路徑變量約定。在下面的示例中,模型屬性名account與URI路徑變量account匹配,通過將字符串帳號傳遞給註冊的轉換器<String,Account >:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
獲取模型屬性實例後,應用數據綁定。WebDataBinder類將Servlet請求參數名稱(查詢參數和表單字段)與目標對象上的字段名稱匹配。在需要時,在應用類型轉換之後填充匹配字段。有關數據綁定(和驗證)的更多信息,請參見驗證 Validation。有關自定義數據綁定的詳細信息,請參閱DataBinder
。
數據綁定可能導致錯誤。默認情況下,會引發BindException。但是,要在控制器方法中檢查這些錯誤,您可以在@ModelAttribute旁邊添加一個BindingResult參數,如下面的示例所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
在某些情況下,您可能希望在不進行數據綁定的情況下訪問模型屬性。對於這種情況,您可以將模型注入控制器並直接訪問它,或者設置@ModelAttribute(binding=false),如下面的例子所示:
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) {
// ...
}
通過添加javax.validation,可以在數據綁定之後自動應用驗證。有效註釋或Spring的@Validated註釋( Bean Validation and Spring validation)。下面的例子演示瞭如何做到這一點:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
注意,使用@ModelAttribute是可選的(例如,設置它的屬性)。默認情況下,任何不是簡單值類型的參數(由BeanUtils#isSimpleProperty決定),並且不被任何其他參數解析器解析的參數都被視爲使用@ModelAttribute註釋的。
@SessionAttributes
@SessionAttributes用於在請求之間的HTTP Servlet會話中存儲模型屬性。它是類型級別的註釋,聲明特定控制器使用的會話屬性。這通常列出模型屬性的名稱或模型屬性的類型,它們應該透明地存儲在會話中,以便後續請求訪問。
下面的例子使用了@SessionAttributes註釋:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
在第一個請求中,當名稱爲pet的模型屬性被添加到模型中時,它會被自動提升到HTTP Servlet會話中並保存。直到另一個控制器方法使用一個SessionStatus方法參數來清除存儲,如下面的例子所示:
@Controller
@SessionAttributes("pet") //1
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete(); //2
// ...
}
}
}
- 在Servlet會話中存儲Pet值。
- 從Servlet會話中清除Pet值。
@SessionAttribute
如果您需要訪問全局管理的預先存在的會話屬性(即,在控制器之外——例如,通過過濾器),並且可能存在也可能不存在,那麼您可以在方法參數上使用@SessionAttribute註釋,如下面的示例所示:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
對於需要添加或刪除會話屬性的用例,可以考慮注入org.springframework.web.context.request.WebRequest
或javax.servlet.http.HttpSession到
控制器方法中。
對於作爲控制器工作流一部分的會話中的模型屬性的臨時存儲,可以考慮使用@SessionAttributes,如@SessionAttributes中所述。
@RequestAttribute
與@SessionAttribute類似,您可以使用@RequestAttribute註釋來訪問先前創建的已存在的請求屬性(例如,通過Servlet過濾器或HandlerInterceptor):
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
重定向的屬性
默認情況下,所有模型屬性都被認爲是作爲重定向URL中的URI模板變量公開的。在其餘屬性中,那些原始類型或原始類型的集合或數組將自動附加爲查詢參數。
如果模型實例是專門爲重定向準備的,那麼將原語類型屬性附加爲查詢參數可能是所需的結果。但是,在帶註釋的控制器中,模型可以包含爲呈現而添加的其他屬性(例如,下拉字段值)。爲了避免這些屬性出現在URL中,@RequestMapping方法可以聲明一個RedirectAttributes類型的參數,並使用它來指定RedirectView可用的確切屬性。如果方法確實需要重定向,則使用RedirectAttributes的內容。否則,將使用模型的內容。
RequestMappingHandlerAdapter提供了一個名爲ignoreDefaultModelOnRedirect的標誌,您可以使用該標誌來指示如果控制器方法重定向,則不應該使用默認模型的內容。相反,控制器方法應該聲明RedirectAttributes類型的屬性,或者,如果不這樣做,就不應該將任何屬性傳遞給RedirectView。MVC名稱空間和MVC Java配置都將此標誌設置爲false,以保持向後兼容性。但是,對於新的應用程序,我們建議將其設置爲true。
注意,當展開重定向URL時,當前請求中的URI模板變量將自動可用,您不需要通過模型或重定向屬性顯式地添加它們。下面的例子演示瞭如何定義重定向:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
另一種向重定向目標傳遞數據的方法是使用flash屬性。與其他重定向屬性不同,flash屬性保存在HTTP會話中(因此不會出現在URL中)。有關更多信息,請參見Flash屬性。
Flash屬性
Flash屬性爲一個請求提供了一種方法來存儲另一個請求將要使用的屬性。這是在重定向時最常見的需求—例如,後重定向-獲取模式。在重定向(通常在會話中)之前臨時保存Flash屬性,以便在重定向之後提供給請求,並立即刪除這些屬性。
Spring MVC有兩個主要的抽象來支持flash屬性。FlashMap用於保存flash屬性,而FlashMapManager用於存儲、檢索和管理FlashMap實例。
Flash屬性支持總是“on”,不需要顯式地啓用。但是,如果不使用它,就不會導致HTTP會話創建。對於每個請求,都有一個“輸入”FlashMap,其中包含前一個請求(如果有的話)傳遞的屬性,以及一個“輸出”FlashMap,其中包含爲後續請求保存的屬性。這兩個FlashMap實例都可以通過requestcontext中的靜態方法從Spring MVC中的任何地方訪問。
帶註釋的控制器通常不需要直接使用FlashMap。相反,@RequestMapping方法可以接受類型爲RedirectAttributes的參數,並使用它爲重定向場景添加flash屬性。通過RedirectAttributes添加的Flash屬性會自動傳播到“output”FlashMap中。類似地,在重定向之後,來自“input”FlashMap的屬性會自動添加到服務於目標URL的控制器模型中。
將請求匹配到flash屬性
flash屬性的概念存在於許多其他web框架中,並已被證明有時會出現併發性問題。這是因爲,根據定義,flash屬性將一直存儲到下一個請求。然而,下一個請求可能不是預期的接收方,而是另一個異步請求(例如,輪詢或資源請求),在這種情況下,flash屬性被過早地刪除了。
爲了減少這種問題的可能性,RedirectView會自動“標記”FlashMap實例的路徑和目標重定向URL的查詢參數。然後,默認的FlashMapManager在查找“輸入”FlashMap時將該信息與傳入的請求匹配。
這並不能完全消除併發性問題的可能性,但是可以通過重定向URL中已有的信息大大減少併發性問題。因此,我們建議您主要在重定向場景中使用flash屬性。
Multipart(多部分)
啓用MultipartResolver後,將解析帶有多部分/表單數據的POST請求的內容,並將其作爲常規請求參數進行訪問。下面的示例訪問一個常規表單字段和一個上傳文件:
@Controller
public class FileUploadController {
@PostMapping("/form")
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";
}
}
將參數類型聲明爲列表<MultipartFile>允許爲相同的參數名稱解析多個文件。
當@RequestParam註釋聲明爲Map<String、MultipartFile>或MultiValueMap<String、MultipartFile>,而沒有在註釋中指定參數名時,將使用每個給定參數名的多部分文件填充映射。
使用Servlet 3.0多部分解析,您還可以聲明jjavax.servlet.http.Part作爲方法參數或集合值類型,而不是Spring的MultipartFile。
您還可以使用multipart內容作爲到命令對象command object的數據綁定的一部分。例如,前面示例中的表單字段和文件可以是表單對象上的字段,如下例所示:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
if (!form.getFile().isEmpty()) {
byte[] bytes = form.getFile().getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
在RESTful服務場景中,也可以從非瀏覽器客戶端提交多部分請求。下面的例子顯示了一個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 ...
您可以使用@RequestParam作爲字符串訪問“元數據”部分,但是您可能希望它從JSON反序列化(類似於@RequestBody)。使用@RequestPart註釋在使用 HttpMessageConverter進行轉換後訪問多部分:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
您可以將@RequestPart與javax.validation結合使用。有效或使用Spring的@Validated註解,兩者都會應用標準Bean驗證。默認情況下,驗證錯誤將導致MethodArgumentNotValidException異常,該異常將轉換爲400 (BAD_REQUEST)響應。或者,您可以通過error或BindingResult參數在控制器中本地處理驗證錯誤,如下面的示例所示:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
@RequestBody
可以使用@RequestBody註釋通過HttpMessageConverter將請求體讀取並反序列化爲對象。下面的例子使用了@RequestBody參數:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
您可以使用MVC配置的消息轉換器選項來配置或自定義消息轉換。
您可以結合使用@RequestBody和javax.validation。有效的或Spring的@Validated註解,兩者都會應用標準Bean驗證。默認情況下,驗證錯誤將導致MethodArgumentNotValidException異常,該異常將轉換爲400 (BAD_REQUEST)響應。或者,您可以通過error或BindingResult參數在控制器中本地處理驗證錯誤,如下面的示例所示:
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
// ...
}
HttpEntity
HttpEntity或多或少與使用@RequestBody相同,但它基於公開請求頭和請求體的容器對象。下面的清單顯示了一個例子:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@ResponseBody
您可以使用方法上的@ResponseBody註釋,通過HttpMessageConverter將返回序列化到響應體。下面的清單顯示了一個例子:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@ResponseBody在類級別上也受支持,在這種情況下,它被所有控制器方法繼承。這就是@RestController的作用,它只不過是一個標記爲@Controller和@ResponseBody的元註釋。
您可以將@ResponseBody與反應式類型一起使用。有關更多詳細信息,請參見異步請求和響應類型。
您可以使用MVC配置的消息轉換器選項來配置或自定義消息轉換。
您可以將@ResponseBody方法與JSON序列化視圖相結合。有關詳細信息,請參見Jackson JSON。
ResponseEntity
ResponseEntity類似於@ResponseBody,但是有狀態和標題。例如:
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).build(body);
}
Spring MVC支持使用單值反應類型異步地生成ResponseEntity,以及/或主體的單值和多值反應類型。
Jackson JSON
Spring提供了對Jackson JSON庫的支持。
Jackson JSON
Spring MVC爲Jackson的序列化視圖提供了內置的支持,它只允許呈現對象中所有字段的一個子集。要將它與@ResponseBody或ResponseEntity控制器方法一起使用,您可以使用Jackson的@JsonView註釋來激活一個序列化視圖類,如下例所示:
@RestController
public class UserController {
@GetMapping("/user")
@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;
}
}
注意:@JsonView允許一個視圖類數組,但每個控制器方法只能指定一個。如果需要激活多個視圖,可以使用複合接口。
對於依賴於視圖解析的控制器,可以將序列化視圖類添加到模型中,如下例所示:
@Controller
public class UserController extends AbstractController {
@GetMapping("/user")
public String getUser(Model model) {
model.addAttribute("user", new User("eric", "7!jd#h23"));
model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
return "userView";
}
}
1.3.4。模型
您可以使用@ModelAttribute註釋:
- 在@RequestMapping方法中的方法參數上創建或訪問模型中的對象,並通過WebDataBinder將其綁定到請求。
- 作爲@Controller或@ControllerAdvice類中的方法級註釋,幫助在任何@RequestMapping方法調用之前初始化模型。
- 在@RequestMapping方法上標記它的返回值是一個模型屬性。
本節討論@ModelAttribute方法—前一列表中的第二項。一個控制器可以有任意數量的@ModelAttribute方法。所有這些方法都在同一個控制器中的@RequestMapping方法之前調用。@ModelAttribute方法也可以通過@ControllerAdvice在控制器之間共享。有關更多細節,請參見控制器建議一節。
@ModelAttribute方法具有靈活的方法簽名。它們支持許多與@RequestMapping方法相同的參數,除了@ModelAttribute本身或與請求體相關的任何東西。
下面的例子展示了一個@ModelAttribute方法:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
下面的例子只添加了一個屬性:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
注意:如果沒有顯式指定名稱,則根據對象類型選擇默認名稱,如javadoc for約定中所述。您總是可以通過使用重載的addAttribute方法或通過@ModelAttribute上的name屬性(用於返回值)來分配顯式名稱。
您還可以使用@ModelAttribute作爲@RequestMapping方法上的方法級註釋,在這種情況下,@RequestMapping方法的返回值被解釋爲一個模型屬性。這通常不是必需的,因爲這是HTML控制器中的默認行爲,除非返回值是一個字符串,否則將被解釋爲一個視圖名。@ModelAttribute也可以自定義模型屬性名,如下例所示:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
1.3.5。DataBinder
@Controller或@ControllerAdvice類可以有@InitBinder方法來初始化WebDataBinder的實例,而這些方法又可以:
- 將請求參數(即表單或查詢數據)綁定到模型對象。
- 將基於字符串的請求值(如請求參數、路徑變量、標頭、cookie等)轉換爲控制器方法參數的目標類型。
- 在呈現HTML表單時,將模型對象值格式化爲字符串值。
@InitBinder方法可以註冊特定於控制器的java.bean。PropertyEditor或Spring轉換器和格式化程序組件。此外,可以使用MVC配置在全局共享的FormattingConversionService中註冊轉換器和格式化程序類型。
@InitBinder方法支持許多與@RequestMapping方法相同的參數,除了@ModelAttribute(命令對象)參數。通常,它們用WebDataBinder參數(用於註冊)和void返回值聲明。下面的清單顯示了一個例子:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
或者,當您通過共享FormattingConversionService使用基於格式化的設置時,您可以重用相同的方法並註冊特定於控制器的格式化程序實現,如下面的示例所示:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
1.3.6。異常
@Controller和@ControllerAdvice類可以有@ExceptionHandler方法來處理來自控制器方法的異常,如下面的例子所示:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
異常可能與傳播的頂級異常(即拋出的直接IOException)或頂級包裝器異常中的直接原因相匹配(例如,在IllegalStateException中包裝的IOException)。
爲了匹配異常類型,最好將目標異常聲明爲方法參數,如上面的示例所示。當多個異常方法匹配時,根異常匹配通常比原因異常匹配更可取。更具體地說,ExceptionDepthComparator用於根據拋出的異常類型的深度對異常進行排序。
另外,註釋聲明可以縮小異常類型以匹配,如下面的示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
您甚至可以使用帶有非常通用的參數簽名的特定異常類型列表,如下面的示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
注意:
根異常匹配和原因異常匹配之間的區別可能令人吃驚。
在前面顯示的IOException變體中,通常使用實際的FileSystemException或RemoteException實例作爲參數來調用方法,因爲它們都是從IOException擴展而來的。但是,如果在包裝器異常(本身就是IOException)中傳播任何此類匹配異常,則傳入的異常實例就是該包裝器異常。
句柄(異常)變體的行爲更簡單。在包裝場景中,這總是與包裝器異常一起調用,在這種情況下,通過ex.getCause()找到實際匹配的異常。傳入的異常是實際的FileSystemException或RemoteException實例,只有在作爲頂級異常拋出時才拋出這些異常。
在一個multi-@ControllerAdvice安排中,我們建議在一個@ControllerAdvice上聲明主根異常映射,並按相應的順序進行優先級排序。雖然根異常匹配是首選的原因,但這是在給定控制器或@ControllerAdvice類的方法中定義的。這意味着高優先級@ControllerAdvice bean上的原因匹配比低優先級@ControllerAdvice bean上的任何匹配(例如根)更受歡迎。
最後但並非最不重要的一點是,@ExceptionHandler方法實現可以選擇通過重新拋出給定的異常實例的原始形式來退出處理。這在您只對根級別的匹配或無法靜態確定的特定上下文中的匹配感興趣的場景中非常有用。重新拋出的異常通過剩餘的解析鏈傳播,就好像給定的@ExceptionHandler方法一開始就不匹配一樣。
Spring MVC中對@ExceptionHandler方法的支持是建立在DispatcherServlet級別、HandlerExceptionResolver機制之上的。
方法參數
@ExceptionHandler方法支持以下參數:
Method argument | Description |
---|---|
Exception type |
For access to the raised exception. |
|
For access to the controller method that raised the exception. |
|
Generic access to request parameters and request and session attributes without direct use of the Servlet API. |
|
Choose any specific request or response type (for example, |
|
Enforces the presence of a session. As a consequence, such an argument is never |
|
Currently authenticated user — possibly a specific |
|
The HTTP method of the request. |
|
The current request locale, determined by the most specific |
|
The time zone associated with the current request, as determined by a |
|
For access to the raw response body, as exposed by the Servlet API. |
|
For access to the model for an error response. Always empty. |
|
Specify attributes to use in case of a redirect — (that is to be appended to the query string) and flash attributes to be stored temporarily until the request after the redirect. See Redirect Attributes and Flash Attributes. |
|
For access to any session attribute, in contrast to model attributes stored in the session as a result of a class-level |
|
For access to request attributes. See |
返回值
@ExceptionHandler方法支持以下返回值:
Return value | Description |
---|---|
|
The return value is converted through |
|
The return value specifies that the full response (including the HTTP headers and the body) be converted through |
|
A view name to be resolved with |
|
A |
|
Attributes to be added to the implicit model with the view name implicitly determined through a |
|
An attribute to be added to the model with the view name implicitly determined through a Note that |
|
The view and model attributes to use and, optionally, a response status. |
|
A method with a If none of the above is true, a |
Any other return value |
If a return value is not matched to any of the above and is not a simple type (as determined by BeanUtils#isSimpleProperty), by default, it is treated as a model attribute to be added to the model. If it is a simple type, it remains unresolved. |
REST API的例外
REST服務的一個常見需求是在響應體中包含錯誤細節。Spring框架不會自動這樣做,因爲響應體中的錯誤細節表示是特定於應用程序的。但是,@RestController可以使用@ExceptionHandler方法和ResponseEntity返回值來設置狀態和響應體。也可以在@ControllerAdvice類中聲明這些方法,以便全局應用它們。
在響應體中實現帶有錯誤細節的全局異常處理的應用程序應該考慮擴展ResponseEntityExceptionHandler,它爲Spring MVC引發的異常提供處理,並提供定製響應體的鉤子。要使用它,創建ResponseEntityExceptionHandler的子類,用@ControllerAdvice註釋它,覆蓋必要的方法,並將它聲明爲Spring bean。
1.3.7。控制器的建議
通常@ExceptionHandler、@InitBinder和@ModelAttribute方法在聲明它們的@Controller類(或類層次結構)中應用。如果希望這些方法更全局地(跨控制器)應用,可以在一個用@ControllerAdvice或@RestControllerAdvice註釋的類中聲明它們。
@ControllerAdvice由@Component註釋,這意味着這些類可以通過組件掃描註冊爲Spring bean。@RestControllerAdvice是一個複合註釋,它同時使用@ControllerAdvice和@ResponseBody進行註釋,這本質上意味着@ExceptionHandler方法是通過消息轉換(而不是視圖解析或模板呈現)呈現給響應體的。
在啓動時,用於@RequestMapping和@ExceptionHandler方法的基礎設施類檢測用@ControllerAdvice註釋的Spring bean,然後在運行時應用它們的方法。全局@ExceptionHandler方法(來自@ControllerAdvice)在局部方法(來自@Controller)之後應用。相比之下,全局@ModelAttribute和@InitBinder方法被應用在本地方法之前
默認情況下,@ControllerAdvice方法適用於每個請求(即所有控制器),但是您可以通過使用註釋上的屬性將其縮小到控制器的子集,如下面的示例所示:
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
前面示例中的選擇器是在運行時進行評估的,如果廣泛使用,可能會對性能產生負面影響。有關更多細節,請參見@ControllerAdvice javadoc。
1.4。功能性端點
Spring Web MVC包含了WebMvc。fn是一種輕量級的函數式編程模型,其中函數用於路由和處理請求和契約,它是爲不變性而設計的。它是基於註釋的編程模型的另一種選擇,但也可以在相同的DispatcherServlet上運行。
1.4.1。概述
在WebMvc。一個HTTP請求由一個HandlerFunction處理:一個接受ServerRequest並返回一個ServerResponse的函數。作爲響應對象的兩個請求都具有不可變的契約,這些契約提供對HTTP請求和響應的JDK 8友好訪問。HandlerFunction相當於基於註釋的編程模型中的@RequestMapping方法體。
傳入的請求被路由到一個帶有RouterFunction的處理函數:該函數接受ServerRequest並返回一個可選的HandlerFunction(即可選的<HandlerFunction>)。當路由器函數匹配時,返回處理函數;否則爲空可選。RouterFunction相當於@RequestMapping註釋,但主要區別在於,路由器函數不僅提供數據,還提供行爲。
route()提供了一個路由器生成器,方便路由器的創建,如下例所示:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
public class PersonHandler {
// ...
public ServerResponse listPeople(ServerRequest request) {
// ...
}
public ServerResponse createPerson(ServerRequest request) {
// ...
}
public ServerResponse getPerson(ServerRequest request) {
// ...
}
}
如果您將RouterFunction註冊爲bean,例如通過在@Configuration類中公開它,servlet將自動檢測它,如運行服務器中所述。
1.4.2。HandlerFunction
ServerRequest和ServerResponse是不可變的接口,提供對HTTP請求和響應的JDK 8友好訪問,包括頭、正文、方法和狀態代碼。
ServerRequest
ServerRequest提供對HTTP方法、URI、標頭和查詢參數的訪問,而對主體的訪問是通過主體方法提供的。
下面的例子將請求體提取爲一個字符串:
String string = request.body(String.class);
下面的示例將body提取到一個列表<Person>,其中Person對象是從序列化形式(如JSON或XML)解碼而來的:
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
下面的例子展示瞭如何訪問參數:
MultiValueMap<String, String> params = request.params();
ServerResponse
ServerResponse提供對HTTP響應的訪問,因爲它是不可變的,所以您可以使用構建方法來創建它。您可以使用生成器設置響應狀態、添加響應標頭或提供正文。下面的例子用JSON內容創建了一個200 (OK)響應:
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
下面的例子展示瞭如何構建一個201 (CREATED)響應,它只有一個Location頭,沒有body:
下面的例子展示瞭如何構建一個201 (CREATED)響應,它只有一個Location頭,沒有body:
處理程序類
我們可以把處理函數寫成lambda,如下例所示:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body("Hello World");
這很方便,但是在一個應用程序中,我們需要多個函數,而多個內聯lambda可能會變得混亂。因此,將相關的處理程序函數分組到一個處理程序類中是很有用的,這個處理程序類在基於註釋的應用程序中扮演類似於@Controller的角色。例如,下面的類公開了一個反應性的人員存儲庫:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public ServerResponse listPeople(ServerRequest request) { //1
List<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people);
}
public ServerResponse createPerson(ServerRequest request) throws Exception { //2
Person person = request.body(Person.class);
repository.savePerson(person);
return ok().build();
}
public ServerResponse getPerson(ServerRequest request) { //3
int personId = Integer.parseInt(request.pathVariable("id"));
Person person = repository.getPerson(personId);
if (person != null) {
return ok().contentType(APPLICATION_JSON).body(person))
}
else {
return ServerResponse.notFound().build();
}
}
}
- listPeople是一個處理函數,它以JSON的形式返回存儲庫中找到的所有Person對象。
- createPerson是一個處理函數,用於存儲請求體中包含的新Person。
- getPerson是一個處理函數,它返回一個由id路徑變量標識的person。我們從存儲庫檢索那個人,並創建一個JSON響應(如果找到的話)。如果沒有找到,則返回一個404 not found響應。
驗證
功能端點可以使用Spring的驗證功能將驗證應用到請求體。例如,給定一個人的自定義Spring驗證器實現:
public class PersonHandler {
private final Validator validator = new PersonValidator(); //1
// ...
public ServerResponse createPerson(ServerRequest request) {
Person person = request.body(Person.class);
validate(person); //2
repository.savePerson(person);
return ok().build();
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString()); //3
}
}
}
- 創建驗證器實例。
- 應用驗證。
- 爲400個響應拋出異常。
處理程序還可以使用標準的bean驗證API (JSR-303),方法是基於LocalValidatorFactoryBean創建並注入一個全局驗證器實例。看到 Spring Validation。
1.4.3 RouterFunction
路由器函數用於將請求路由到相應的HandlerFunction。通常,您不需要自己編寫路由器函數,而是使用RouterFunctions實用程序類上的方法來創建路由器函數。route()(沒有參數)爲您提供了一個流暢的構建器來創建路由器函數,而RouterFunctions。route(RequestPredicate, HandlerFunction)提供了創建路由器的直接方法。
通常,建議使用route()構建器,因爲它爲典型的映射場景提供了方便的捷徑,而不需要很難發現的靜態導入。例如,router函數生成器提供GET(String, HandlerFunction)方法來創建GET請求的映射;和POST(String, HandlerFunction)用於POST。
除了基於HTTP方法的映射之外,route builder還提供了在映射到請求時引入額外謂詞的方法。對於每個HTTP方法,都有一個重載的變量,它以RequestPredicate作爲參數,但是可以表示哪些額外的約束。
Predicates(判斷式)
您可以編寫自己的RequestPredicate,但RequestPredicates實用程序類提供了常用的實現,基於請求路徑、HTTP方法、content-type等。下面的例子使用一個請求謂詞來創建一個基於Accept標頭的約束:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
您可以使用以下方法組合多個請求謂詞:
- RequestPredicate.and(RequestPredicate)——兩者必須匹配。
- RequestPredicate.or(RequestPredicate)——兩者都可以匹配。
Many of the predicates from RequestPredicates
are composed. For example, RequestPredicates.GET(String)
is composed from RequestPredicates.method(HttpMethod)
and RequestPredicates.path(String)
. The example shown above also uses two request predicates, as the builder uses RequestPredicates.GET
internally, and composes that with the accept
predicate.
Routes
路由器函數按順序計算:如果第一個路由不匹配,則計算第二個路由,依此類推。因此,在一般路由之前聲明更具體的路由是有意義的。注意,這種行爲與基於註釋的編程模型不同,後者自動選擇“最特定”的控制器方法。
當使用router函數生成器時,所有定義的路由都被組合成一個從build()返回的RouterFunction。也有其他方式來組合多個路由器功能在一起:
add(RouterFunction)
on theRouterFunctions.route()
builderRouterFunction.and(RouterFunction)
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
— shortcut forRouterFunction.and()
with nestedRouterFunctions.route()
.
下面的例子展示了四種路由的組成:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) //1
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) //2
.POST("/person", handler::createPerson) //3
.add(otherRoute) //3
.build();
- 帶有與JSON匹配的Accept頭的GET /person/{id}被路由到PersonHandler.getPerson
- 帶有與JSON匹配的Accept頭的GET /person被路由到PersonHandler.listPeople
- 沒有附加謂詞的POST /person被映射到PersonHandler。createPerson和otherRoute是在其他地方創建的路由器函數,並添加到構建的路由中。
嵌套的路線
一組路由器函數通常具有共享謂詞,例如共享路徑。在上面的示例中,共享謂詞將是一個匹配/person的路徑謂詞,由三個路由使用。在使用註釋時,您可以通過使用映射到/person的類型級別@RequestMapping註釋來刪除這種重複。在WebMvc。路徑謂詞可以通過路由器函數生成器上的路徑方法共享。例如,上面例子的最後幾行可以通過使用嵌套路由來改進:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder //1
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
//1 注意,path的第二個參數是一個消費者,它接受路由器生成器。
儘管基於路徑的嵌套是最常見的,但是您可以通過在構建器上使用nest方法在任何類型的謂詞上進行嵌套。上面仍然包含一些以共享Accept-header謂詞形式出現的重複。我們可以通過結合使用nest方法和accept來進一步改進:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.build();
1.4.4。運行一個服務器
通常通過MVC配置在基於DispatcherHandle
r的設置中運行路由器函數,該配置使用Spring配置來聲明處理請求所需的組件。MVC Java配置聲明瞭以下基礎設施組件來支持功能端點:
- RouterFunctionMapping:檢測一個或多個RouterFunction<?在Spring配置中的> bean,通過RouterFunction組合它們。然後,將請求路由到最終的合成RouterFunction。
- HandlerFunctionAdapter:簡單的適配器,允許DispatcherHandler調用映射到請求的HandlerFunction。
前面的組件使功能端點適合DispatcherServlet請求處理生命週期,並且(可能)與帶註釋的控制器一起運行(如果聲明瞭控制器)。這也是Spring Boot Web starter啓用功能端點的方式。
下面的例子顯示了WebFlux的Java配置:
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
1.4.5。過濾處理程序函數
您可以使用routing function builder上的before、after或filter方法來過濾處理函數。使用註釋,您可以通過使用@ControllerAdvice、ServletFilter或兩者同時使用來實現類似的功能。過濾器將應用於由生成器構建的所有路由。這意味着嵌套路由中定義的過濾器並不適用於“頂級”路由。例如,考慮下面的例子:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople)
.before(request -> ServerRequest.from(request) //1
.header("X-RequestHeader", "Value")
.build()))
.POST("/person", handler::createPerson))
.after((request, response) -> logResponse(response)) //2
.build();
- 添加自定義請求頭的before篩選器僅應用於兩個GET路由。
- 記錄響應的after過濾器應用於所有路由,包括嵌套路由。
路由器生成器上的filter方法接受一個HandlerFilterFunction:一個接受ServerRequest和HandlerFunction並返回一個ServerResponse的函數。處理函數參數表示鏈中的下一個元素。這通常是被路由到的處理程序,但是如果應用了多個過濾器,它也可以是另一個過濾器。
現在,我們可以向路由添加一個簡單的安全過濾器,假設我們有一個SecurityManager來確定是否允許特定的路徑。下面的例子演示瞭如何做到這一點:
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
前面的示例演示了調用next.handle(ServerRequest)是可選的。我們只允許在允許訪問時執行處理函數。
除了在路由器函數生成器上使用filter方法外,還可以通過RouterFunction.filter(HandlerFilterFunction)將一個過濾器應用到現有的路由器函數上。
注意:功能端點的CORS支持是通過專用的CorsWebFilter提供的。
1.5。URI的鏈接
本節描述Spring框架中可用於處理URI的各種選項。
1.5.1。UriComponents
Spring MVC和Spring WebFlux
UriComponentsBuilder有助於從帶有變量的URI模板構建URI,如下面的示例所示:
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") //1
.queryParam("q", "{q}") //2
.encode() //3
.build(); //4
URI uri = uriComponents.expand("Westin", "123").toUri(); //5
- 帶有URI模板的靜態工廠方法。
- 添加或替換URI組件。
- 請求對URI模板和URI變量進行編碼。
- 建立一個UriComponents。
- 拓展變量並獲得URI。
前面的例子可以合併成一個鏈,並使用buildAndExpand縮短,如下面的例子所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri();
你可以通過直接進入一個URI(這意味着編碼)來進一步縮短它,如下面的例子所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
使用完整的URI模板進一步縮短它,如下面的示例所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");
1.5.2。UriBuilder
Spring MVC和Spring WebFlux
UriComponentsBuilder實現UriBuilder。您可以使用UriBuilderFactory創建一個UriBuilder。UriBuilderFactory和UriBuilder一起提供了一種可插拔的機制,可以根據共享配置(如基本URL、編碼首選項和其他詳細信息)從URI模板構建URI。
您可以使用UriBuilderFactory配置RestTemplate和WebClient來定製uri的準備工作。DefaultUriBuilderFactory是UriBuilderFactory的默認實現,它在內部使用UriComponentsBuilder並公開共享配置選項。
下面的示例展示瞭如何配置RestTemplate:
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
下面的示例配置一個WebClient:
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
此外,您還可以直接使用DefaultUriBuilderFactory。它類似於使用UriComponentsBuilder,但它不是靜態工廠方法,而是一個保存配置和首選項的實際實例,如下面的示例所示:
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
1.5.3。更正URI編碼
UriComponentsBuilder在兩個級別公開編碼選項:
- UriComponentsBuilder#encode():先對URI模板進行預編碼,然後在展開時嚴格地對URI變量進行編碼。
- UriComponents#encode():在URI變量展開後對URI組件進行編碼。
這兩個選項都用轉義的八位字符替換非ascii字符和非法字符。但是,第一個選項也替換URI變量中出現的具有保留含義的字符。
注意:考慮“;”,它在路徑中是合法的,但是保留了意義。第一個選項將URI變量中的“;”替換爲“%3B”,但不在URI模板中。相反,第二個選項永遠不會取代“;”,因爲它是路徑中的一個合法字符。
對於大多數情況,第一個選項可能會給出預期的結果,因爲它將URI變量視爲要完全編碼的不透明數據,而選項2僅在URI變量有意包含保留字符時纔有用。
下面的例子使用了第一個選項:
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri();
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
您可以通過直接轉到URI(這意味着編碼)來縮短前面的示例,如下面的示例所示:
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.build("New York", "foo+bar")
您可以使用完整的URI模板進一步縮短它,如下面的示例所示:
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}")
.build("New York", "foo+bar")
WebClient和RestTemplate通過UriBuilderFactory策略在內部擴展和編碼URI模板。兩者都可以配置自定義策略。如下例所示:
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
DefaultUriBuilderFactory實現在內部使用UriComponentsBuilder來擴展和編碼URI模板。作爲一個工廠,它提供了一個單一的地方來配置編碼的方法,基於以下編碼模式之一:
- TEMPLATE_AND_VALUES:使用UriComponentsBuilder#encode(),對應於前面列表中的第一個選項,對URI模板進行預編碼,並在展開時嚴格編碼URI變量。
- VALUES_ONLY:不對URI模板進行編碼,而是在將URI變量擴展到模板之前,通過UriUtils#encodeUriUriVariables對URI變量進行嚴格的編碼。
- URI_COMPONENTS:使用UriComponents#encode(),對應於前面列表中的第二個選項,在URI變量展開後對URI組件值進行編碼。
- NONE:不應用任何編碼。
將RestTemplate設置爲EncodingMode。歷史原因和向後兼容性的URI_COMPONENTS。WebClient依賴於DefaultUriBuilderFactory中的默認值,該值已從EncodingMode更改。URI_COMPONENTS在5.0。x EncodingMode。TEMPLATE_AND_VALUES在5.1。
1.5.4。相對Servlet請求
您可以使用ServletUriComponentsBuilder來創建相對於當前請求的uri,如下面的示例所示:
HttpServletRequest request = ...
// Re-uses host, scheme, port, path and query string...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
您可以創建相對於上下文路徑的uri,如下面的示例所示:
// Re-uses host, port and context path...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts").build()
您可以創建相對於Servlet(例如/main/*)的uri,如下面的示例所示:
// Re-uses host, port, context path, and Servlet prefix...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
注意:從5.1開始,ServletUriComponentsBuilder將忽略來自轉發和x轉發-*報頭的信息,這些報頭指定了源自客戶端的地址。考慮使用forwarding headerfilter來提取和使用或丟棄這些頭。
1.5.5。鏈接到控制器
Spring MVC提供了一種機制來準備指向控制器方法的鏈接。例如,下面的MVC控制器允許創建鏈接:
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
你可以通過引用方法的名稱來準備一個鏈接,如下面的例子所示:
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
在前面的示例中,我們提供了實際的方法參數值(在本例中是long值:21),用於作爲路徑變量並插入到URL中。此外,我們提供值42來填充任何剩餘的URI變量,例如從類型級請求映射繼承的hotel變量。如果方法有更多的參數,我們可以爲URL不需要的參數提供null。通常,只有@PathVariable和@RequestParam參數與構造URL相關。
還有其他方法可以使用MvcUriComponentsBuilder。例如,可以使用類似於通過代理進行模擬測試的技術來避免通過名稱引用控制器方法,如下面的示例所示(該示例假設靜態導入MvcUriComponentsBuilder.on):
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
注意:當控制器方法簽名被認爲可以用fromMethodCall創建鏈接時,它們的設計就受到了限制。除了需要正確的參數簽名外,返回類型還有技術限制(即,爲link builder調用生成運行時代理),因此返回類型不能是final。特別是,用於視圖名稱的公共字符串返回類型在這裏不起作用。您應該使用ModelAndView甚至普通對象(帶有字符串返回值)來代替。
前面的示例使用了MvcUriComponentsBuilder中的靜態方法。在內部,它們依靠ServletUriComponentsBuilder從當前請求的方案、主機、端口、上下文路徑和servlet路徑準備一個基本URL。這在大多數情況下都很有效。然而,有時,它可能是不夠的。例如,您可能在請求的上下文之外(例如準備鏈接的批處理過程),或者可能需要插入路徑前綴(例如從請求路徑中刪除的地區前綴,需要重新插入鏈接)。
對於這種情況,可以使用接受UriComponentsBuilder的靜態fromXxx重載方法來使用基本URL。或者,您可以使用一個基本URL創建MvcUriComponentsBuilder的實例,然後使用基於實例的withXxx方法。例如,下面的清單使用了withMethodCall:
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();
注意:從5.1版開始,MvcUriComponentsBuilder將忽略來自轉發和x轉發-*報頭的信息,這些報頭指定了源自客戶機的地址。考慮使用forwarding headerfilter來提取和使用或丟棄這些頭。
1.5.6。在視圖的鏈接
在Thymeleaf、FreeMarker或JSP等視圖中,您可以通過引用隱式或顯式地爲每個請求映射分配的名稱來構建到帶註釋的控制器的鏈接。
考慮下面的例子:
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
根據前面的控制器,您可以從JSP中準備一個鏈接,如下所示:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>
前面的示例依賴於Spring標記庫中聲明的mvcUrl函數(即META-INF/ Spring .tld),但是很容易定義自己的函數或爲其他模板技術準備類似的函數。
這是如何工作的。在啓動時,每個@RequestMapping都通過HandlerMethodMappingNamingStrategy分配一個默認名稱,其默認實現使用類的大寫字母和方法名(例如,ThingController中的getThing方法變成“TC#getThing”)。如果存在名稱衝突,可以使用@RequestMapping(name="..")來分配顯式名稱或實現自己的HandlerMethodMappingNamingStrategy。
1.6。異步請求
Spring MVC與Servlet 3.0異步請求處理有廣泛的集成:
- 控制器方法中的
DeferredResult
和Callable
的返回值,並提供對單個異步返回值的基本支持。 - 控制器可以傳輸多個值,包括SSE和原始數據。
- 控制器可以使用響應客戶端並返回響應處理的響應類型。
1.6.1。DeferredResult
一旦在Servlet容器中啓用異步請求處理特性,控制器方法就可以使用DeferredResult包裝任何受支持的控制器方法返回值,如下例所示:
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
}
// From some other thread...
deferredResult.setResult(result);
控制器可以從不同的線程異步地生成返回值——例如,響應外部事件(JMS消息)、定時任務或其他事件。
1.6.2。Callable(
可調用的)
WebFlux相比
控制器可以用java.util.concurrent包裝任何受支持的返回值。可調用,如下例所示:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
然後可以通過配置的TaskExecutor運行給定的任務來獲得返回值。
1.6.3。處理
下面是Servlet異步請求處理的簡要概述:
- 可以通過調用request.startAsync()將ServletRequest置於異步模式。這樣做的主要效果是Servlet(以及任何過濾器)可以退出,但是響應仍然是開放的,以便稍後完成處理。
- 對request.startAsync()的調用將返回AsyncContext,您可以使用它進一步控制異步處理。例如,它提供分派方法,該方法類似於Servlet API的轉發,只是它允許應用程序在Servlet容器線程上恢復請求處理。
- ServletRequest提供對當前DispatcherType的訪問,您可以使用它來區分處理初始請求、異步分派、轉發和其他分派器類型。
延遲處理工作如下:
- 控制器返回一個DeferredResult,並將其保存在某個可以訪問它的內存隊列或列表中。
- Spring MVC調用request.startAsync()。
- 同時,DispatcherServlet和所有配置的篩選器退出請求處理線程,但是響應保持打開狀態。
- 應用程序從某個線程設置DeferredResult, Spring MVC將請求發送回Servlet容器。
- 再次調用DispatcherServlet,然後繼續處理異步生成的返回值。
可調用的處理工作如下:
- 控制器返回一個可調用的。
- Spring MVC調用request.startAsync()並將調用提交給TaskExecutor,以便在單獨的線程中進行處理。
- 同時,DispatcherServlet和所有篩選器退出Servlet容器線程,但是響應保持打開狀態。
- 最後,Callable產生一個結果,Spring MVC將請求發送回Servlet容器以完成處理。
- 再次調用DispatcherServlet,然後處理從Callable異步生成的返回值。
要了解更多的背景和上下文,您還可以閱讀Spring MVC 3.2中介紹異步請求處理支持的博客文章。
異常處理
當您使用DeferredResult時,您可以選擇調用setResult還是setErrorResult(帶有異常)。在這兩種情況下,Spring MVC都將請求發送回Servlet容器以完成處理。然後,要麼將其視爲控制器方法返回給定值,要麼將其視爲產生給定異常。然後異常通過常規的異常處理機制(例如,調用@ExceptionHandler方法)。
當您使用Callable時,會出現類似的處理邏輯,主要的區別是結果是從Callable返回的,還是由它引發的異常。
攔截
HandlerInterceptor實例可以是AsyncHandlerInterceptor類型,用於在開始異步處理的初始請求上接收afterConcurrentHandlingStarted回調(而不是在完成之後)。
HandlerInterceptor實現還可以註冊CallableProcessingInterceptor或DeferredResultProcessingInterceptor,以便更深入地集成異步請求的生命週期(例如,處理超時事件)。有關更多詳細信息,請參見AsyncHandlerInterceptor。
DeferredResult提供onTimeout(Runnable)和onCompletion(Runnable)回調。有關詳細信息,請參閱DeferredResult的javadoc。可以用Callable替換WebAsyncTask,後者爲超時和完成回調提供了額外的方法。
WebFlux相比
Servlet API最初是爲通過過濾器-Servlet鏈進行單次傳遞而構建的。在Servlet 3.0中添加的異步請求處理允許應用程序退出過濾器-Servlet鏈,但保留響應以供進一步處理。Spring MVC異步支持就是圍繞這種機制構建的。當控制器返回一個DeferredResult時,過濾器-Servlet鏈被退出,Servlet容器線程被釋放。稍後,當設置DeferredResult時,將進行異步分派(到相同的URL),在此期間將再次映射控制器,但不是調用它,而是使用DeferredResult值(就像控制器返回它一樣)來恢復處理。
相比之下,Spring WebFlux既不是基於Servlet API構建的,也不需要這樣的異步請求處理特性,因爲它在設計上就是異步的。異步處理構建在所有框架契約中,並在請求處理的所有階段得到本質上的支持。
從編程模型的角度來看,Spring MVC和Spring WebFlux都支持異步和響應類型作爲控制器方法的返回值。Spring MVC甚至支持流,包括無功回壓。但是,對響應的各個寫操作仍然是阻塞的(並且是在單獨的線程上執行的),這與WebFlux不同,後者依賴於非阻塞I/O,並且每次寫操作都不需要額外的線程。
另一個基本區別是,Spring MVC在控制器方法參數中不支持異步或響應類型(例如,@RequestBody、@RequestPart等),也不支持將異步和響應類型作爲模型屬性。Spring WebFlux確實支持所有這些。
1.6.4 HTTP流媒體
您可以對單個異步返回值使用DeferredResult和Callable。如果希望生成多個異步值並將其寫入響應中,該怎麼辦?本節將介紹如何做到這一點。
對象
您可以使用ResponseBodyEmitter返回值來生成對象流,其中每個對象都使用HttpMessageConverter序列化並寫入響應,如下面的示例所示:
@GetMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..
return emitter;
}
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
您還可以使用ResponseBodyEmitter作爲ResponseEntity中的主體,讓您自定義響應的狀態和標題。
當發射器拋出IOException(例如,如果遠程客戶端消失)時,應用程序不負責清理連接,不應調用exit .complete或exit . completewitherror。相反,servlet容器會自動啓動一個AsyncListener錯誤通知,在這個通知中,Spring MVC會執行一個completeWithError調用。然後,這個調用對應用程序執行最後一次異步分派,在此期間,Spring MVC調用已配置的異常解析器並完成請求。
SSE
SseEmitter (ResponseBodyEmitter的子類)提供對服務器發送事件的支持,從服務器發送的事件根據W3C SSE規範進行格式化。要從控制器生成SSE流,返回SseEmitter,如下面的示例所示:
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
SseEmitter emitter = new SseEmitter();
// 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();
雖然SSE是流媒體到瀏覽器的主要選項,但請注意Internet Explorer不支持服務器發送的事件。考慮使用Spring的WebSocket消息傳遞和SockJS回退傳輸(包括SSE),這些回退傳輸針對廣泛的瀏覽器。
有關異常處理的說明,請參閱前一節。
原始數據
有時,繞過消息轉換並直接將流發送到響應OutputStream是很有用的(例如,對於文件下載)。你可以使用StreamingResponseBody返回值類型來完成,如下面的例子所示:
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
您可以使用StreamingResponseBody作爲ResponseEntity中的主體來定製響應的狀態和標題。
1.6.5。反應類型
Spring MVC支持在控制器中使用反應性的客戶端庫(也可以閱讀WebFlux部分的反應性庫)。這包括Spring -webflux的WebClient和其他的,比如Spring Data反應性數據倉庫。在這樣的場景中,可以方便地從控制器方法返回響應類型。
無功返回值處理如下:
- 與使用DeferredResult類似,也適用於單值承諾。例如Mono(反應器)或Single (RxJava)。
- 與使用ResponseBodyEmitter或SseEmitter類似,適應具有流媒體類型(例如應用程序/流+json或文本/事件流)的多值流。例如Flux (Reactor)或Observable (RxJava)。應用程序還可以返回Flux<ServerSentEvent>或Observable<ServerSentEvent>。
- 與使用DeferredResult<List<?>>類似,可以適應任何其他媒體類型(如application/json)的多值流。
注意:Spring MVC通過Spring -core的ReactiveAdapterRegistry支持Reactor和RxJava,這使它能夠適應多個反應性庫。
對於響應的流,支持反應性回壓,但是對響應的寫操作仍然是阻塞的,並通過配置的TaskExecutor在單獨的線程上執行,以避免阻塞上游的源(例如從WebClient返回的通量)。默認情況下,SimpleAsyncTaskExecutor用於阻塞寫,但在負載下不適合。如果您計劃使用反應式類型進行流處理,則應該使用MVC配置來配置任務執行程序。
1.6.6。斷開連接
Servlet API在遠程客戶端離開時不提供任何通知。因此,在通過SseEmitter或reactive類型流到響應時,定期發送數據是很重要的,因爲如果客戶機斷開連接,寫操作就會失敗。發送可以採用空的(僅供引用的)SSE事件或任何其他數據的形式,而另一方必須將這些數據解釋爲心跳並忽略它們。
或者,考慮使用具有內置心跳機制的web消息傳遞解決方案(such as STOMP over WebSocket or WebSocket with SockJS)。
1.6.7。配置
異步請求處理特性必須在Servlet容器級別啓用。MVC配置還爲異步請求提供了幾個選項。
Servlet容器
過濾器和Servlet聲明有一個asyncSupported標記,需要將其設置爲true以啓用異步請求處理。此外,應該聲明過濾器映射來處理ASYNC javax.servlet.DispatchType。
在Java配置中,當您使用AbstractAnnotationConfigDispatcherServletInitializer來初始化Servlet容器時,這是自動完成的。
在web.xml配置中,可以將< ASYNC支持的>true</ ASYNC支持的>添加到DispatcherServlet並過濾聲明,將<dispatcher>ASYNC</dispatcher>添加到過濾映射。
Spring MVC
MVC配置公開了以下與異步請求處理相關的選項:
- Java配置:在WebMvcConfigurer上使用configureyncsupport回調。
- XML命名空間:使用<mvc:註釋驅動的>下的<async-support>元素。
您可以配置以下內容:
- 異步請求的默認超時值(如果沒有設置)取決於底層的Servlet容器。
- AsyncTaskExecutor用於在使用響應類型流時進行寫阻塞,以及執行從控制器方法返回的可調用實例。我們強烈建議配置此屬性,如果您使用響應類型或具有返回可調用的控制器方法,因爲在默認情況下,它是一個SimpleAsyncTaskExecutor。
- DeferredResultProcessingInterceptor實現和CallableProcessingInterceptor實現。
注意,您還可以在DeferredResult、ResponseBodyEmitter和SseEmitter上設置默認的超時值。對於Callable,可以使用WebAsyncTask提供超時值。
1.7。CORS
Spring MVC允許您處理CORS(跨源資源共享)。本節將介紹如何做到這一點。
1.7.1上。介紹
出於安全原因,瀏覽器禁止AJAX調用當前源之外的資源。例如,你可以在一個標籤中顯示你的銀行賬戶,在另一個標籤中顯示evil.com。來自evil.com的腳本不應該能夠使用您的憑證向您的銀行API發出AJAX請求——例如從您的帳戶中取款!
跨源資源共享(Cross-Origin Resource Sharing, CORS)是大多數瀏覽器實現的W3C規範,它允許您指定授權的跨域請求類型,而不是使用基於IFRAME或JSONP的不太安全、功能不太強大的工作區。
1.7.2。處理
CORS規範區分了飛行前、簡單請求和實際請求。要了解CORS是如何工作的,您可以閱讀這篇文章以及其他許多文章,或者查看規範瞭解更多細節。
Spring MVC HandlerMapping實現提供了對CORS的內置支持。成功地將請求映射到處理程序之後,HandlerMapping實現將檢查給定請求和處理程序的CORS配置並採取進一步的操作。飛行前請求直接處理,而簡單的和實際的CORS請求被攔截、驗證,並需要設置CORS響應頭。
爲了啓用跨源請求(即,源標頭存在並且與請求的主機不同),您需要一些顯式聲明的CORS配置。如果沒有找到匹配的CORS配置,飛行前請求將被拒絕。沒有CORS標頭被添加到簡單的和實際的CORS請求的響應中,因此,瀏覽器會拒絕它們。
每個HandlerMapping都可以使用基於URL模式的CorsConfiguration映射單獨配置。在大多數情況下,應用程序使用MVC Java配置或XML名稱空間來聲明這樣的映射,這會導致將單個全局映射傳遞給所有HandlerMappping實例。
您可以將HandlerMapping級別的全局CORS配置與更細粒度的、handler級別的CORS配置組合起來。例如,帶註釋的控制器可以使用類級或方法級@CrossOrigin註釋(其他處理程序可以實現CorsConfigurationSource)。
組合全局和本地配置的規則通常是附加的——例如,所有全局和所有本地源。對於那些只能接受單個值的屬性(如allowCredentials和maxAge),本地覆蓋全局值。有關更多細節,請參見CorsConfiguration#combine(CorsConfiguration)
。
要了解更多的源代碼或作出先進的自定義,檢查背後的代碼:
- CorsConfiguration
- CorsProcessor, DefaultCorsProcessor
- AbstractHandlerMapping
1.7.3。@CrossOrigin
@CrossOrigin註釋支持對帶註釋的控制器方法的跨源請求,如下例所示:
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
默認情況下,@CrossOrigin允許:
- 所有的起源。
- 所有的標題。
- 控制器方法映射到的所有HTTP方法。
默認情況下不啓用allowedCredentials,因爲這會建立一個信任級別,公開敏感的用戶特定信息(比如cookie和CSRF令牌),並且應該只在適當的地方使用。
maxAge設置爲30分鐘。
@CrossOrigin在類級也受支持,並且被所有方法繼承,如下面的例子所示:
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
您可以在類級和方法級使用@CrossOrigin,如下面的示例所示:
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
1.7.4。全局配置
除了細粒度的控制器方法級配置外,您可能還需要定義一些全局CORS配置。您可以在任何HandlerMapping上單獨設置基於url的CorsConfiguration映射。但是,大多數應用程序都使用MVC Java配置或MVC XML名稱空間來實現這一點。
默認情況下,全局配置支持以下功能:
- 所有的起源。
- 所有的標題。
- GET、HEAD和POST方法。
默認情況下不啓用allowedCredentials,因爲這會建立一個信任級別,公開敏感的用戶特定信息(比如cookie和CSRF令牌),並且應該只在適當的地方使用。
maxAge設置爲30分鐘。
Java配置
要在MVC Java配置中啓用CORS,可以使用CorsRegistry回調,如下面的示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
XML配置
要在XML名稱空間中啓用CORS,可以使用<mvc:cors >元素,如下面的示例所示:
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="https://domain1.com, https://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="true"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="https://domain1.com" />
</mvc:cors>
1.7.5。CORS 過濾器
您可以通過內置的CorsFilter應用CORS支持。
如果您嘗試將CorsFilter與Spring Security一起使用,請記住Spring Security有對CORS的內置支持。
要配置過濾器,請將CorsConfigurationSource傳遞給它的構造函數,如下面的示例所示:
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
CorsFilter filter = new CorsFilter(source);
1.8。網絡安全
Spring Security項目爲保護web應用程序免受惡意攻擊提供了支持。參見 Spring Security參考文檔,包括:
HDIV是另一個與Spring MVC集成的web安全框架。
1.9. HTTP Caching
HTTP緩存可以顯著提高web應用程序的性能。HTTP緩存圍繞着Cache-Control響應標頭,隨後是條件請求標頭(如Last-Modified和ETag)。Cache-Control建議私有(例如,瀏覽器)和公共(例如,代理)緩存如何緩存和重用響應。ETag頭用於發出一個條件請求,如果內容沒有更改,則可能導致304 (NOT_MODIFIED)不帶正文。ETag可以被看作是Last-Modified的標題的更復雜的繼承者。
本節介紹Spring Web MVC中提供的與HTTP緩存相關的選項。
1.9.1。CacheControl
CacheControl支持配置與Cache-Control頭文件相關的設置,並且在很多地方作爲參數被接受:
雖然RFC 7234描述了Cache-Control響應頭文件的所有可能的指令,但是CacheControl類型採用了一種面向用例的方法,它主要關注常見的場景:
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
WebContentGenerator還接受一個更簡單的cachePeriod屬性(以秒爲單位定義),其工作方式如下:
- -1值不會生成Cache-Control響應標頭。
- 0值通過使用“Cache-Control: no-store”指令來防止緩存。
- 通過使用'Cache-Control: max-age=n'指令,一個n > 0值緩存給定的響應n秒。
1.9.2。控制器
控制器可以添加對HTTP緩存的顯式支持。我們建議這樣做,因爲在與條件請求頭進行比較之前,需要計算資源的lastModified或ETag值。一個控制器可以添加一個ETag標頭和Cache-Control設置到一個ResponseEntity,如下例所示:
@GetMapping("/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 is also available
.body(book);
}
如果與條件請求頭的比較表明內容沒有更改,那麼前面的示例將發送一個304 (NOT_MODIFIED)響應,該響應的主體爲空。否則,ETag和Cache-Control頭將添加到響應中。
你也可以在控制器中檢查條件請求頭,如下例所示:
@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {
long eTag = ... //1
if (request.checkNotModified(eTag)) {
return null; //2
}
model.addAttribute(...); //3
return "myViewName";
}
- 特定於應用程序的計算。
- 響應被設置爲304 (NOT_MODIFIED)—沒有進一步處理。
- 繼續請求處理。
有三種變體用於根據eTag值、lastModified值或兩者同時檢查條件請求。對於條件GET和HEAD請求,可以將響應設置爲304 (NOT_MODIFIED)。對於有條件的POST、PUT和DELETE,您可以將響應設置爲412 (PRECONDITION_FAILED),以防止併發修改。
1.9.3。靜態資源
您應該使用緩存控制和條件響應頭來提供靜態資源,以獲得最佳性能。參見 Static Resources.一節。
1.9.4。ETag過濾器
您可以使用ShallowEtagHeaderFilter來添加從響應內容計算出來的“shallow”eTag值,從而節省帶寬,但不節省CPU時間。看Shallow ETag.
1.10。視圖技術
Spring MVC中視圖技術的使用是可插拔的,無論您決定使用Thymeleaf、Groovy標記模板、jsp還是其他技術,這主要是一個配置更改的問題。本章將介紹與Spring MVC集成的視圖技術。我們假設您已經熟悉 View Resolution。
1.10.1. Thymeleaf
Thymeleaf是一個現代的服務器端Java模板引擎,強調自然的HTML模板,可以在瀏覽器中預覽雙擊,這是非常有幫助的獨立工作的UI模板(例如,由設計師)不需要運行的服務器。如果您想要替換jsp, Thymeleaf提供了一組最廣泛的特性來簡化這種轉換。Thymeleaf是積極發展和維護。有關更完整的介紹,請參見Thymeleaf項目主頁。
與Spring MVC的Thymeleaf集成由Thymeleaf項目管理。配置涉及到一些bean聲明,如ServletContextTemplateResolver、SpringTemplateEngine和ThymeleafViewResolver。更多細節見Thymeleaf+Spring。
1.10.2。FreeMarker
Apache FreeMarker是一個模板引擎,用於生成從HTML到電子郵件等任何類型的文本輸出。Spring框架內置了使用Spring MVC和FreeMarker模板的集成。
查看配置
下面的例子展示瞭如何配置FreeMarker作爲一個視圖技術:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
// Configure FreeMarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
return configurer;
}
}
下面的示例演示如何在XML中配置相同的配置:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:freemarker/>
</mvc:view-resolvers>
<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>
或者,您也可以聲明FreeMarkerConfigurer bean來完全控制所有屬性,如下面的示例所示:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>
您的模板需要存儲在前面示例中所示的FreeMarkerConfigurer指定的目錄中。根據前面的配置,如果控制器返回一個welcome視圖名,則解析器將查找/WEB-INF/freemarker/welcome.ftl模板。
FreeMarker配置
通過在FreeMarkerConfigurer bean上設置適當的bean屬性,可以將FreeMarker 'Settings'和'SharedVariables'直接傳遞給FreeMarker Configuration對象(由Spring管理)。freemarkerSettings屬性需要一個java.util。屬性對象,而freemarkerVariables屬性需要一個java.util.Map。下面的例子演示瞭如何使用FreeMarkerConfigurer:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape"/>
</map>
</property>
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>
有關應用於配置對象的設置和變量的詳細信息,請參閱FreeMarker文檔。
表單處理
Spring提供了一個用於jsp的標記庫,其中包含一個< Spring:bind/>元素。這個元素主要讓表單顯示來自表單支持對象的值,並顯示來自web或業務層驗證器的失敗驗證的結果。Spring還支持FreeMarker中的相同功能,併爲生成表單輸入元素本身提供了額外的方便宏。
結合宏
在spring-webmvc.jar文件中爲FreeMarker維護了一組標準的宏,因此它們始終對適當配置的應用程序可用。
Spring模板庫中定義的一些宏被認爲是內部的(私有的),但是在宏定義中不存在這樣的作用域,這使得所有宏對於調用代碼和用戶模板都是可見的。下面的部分只關注您需要從模板中直接調用的宏。如果希望直接查看宏代碼,則該文件稱爲spring。它位於org.springframework.web.servlet.view.freemarker包中。
簡單的綁定
在基於FreeMarker模板(充當Spring MVC控制器的表單視圖)的HTML表單中,可以使用類似下一個示例的代碼綁定到字段值,並以類似於JSP的方式顯示每個輸入字段的錯誤消息。下面的例子顯示了一個personForm視圖:
<!-- FreeMarker macros have to be imported into a namespace.
We strongly recommend sticking to 'spring'. -->
<#import "/spring.ftl" as spring/>
<html>
...
<form action="" method="POST">
Name:
<@spring.bind "personForm.name"/>
<input type="text"
name="${spring.status.expression}"
value="${spring.status.value?html}"/><br />
<#list spring.status.errorMessages as error> <b>${error}</b> <br /> </#list>
<br />
...
<input type="submit" value="submit"/>
</form>
...
</html>
<@spring.bind>需要一個“path”參數,該參數由您的命令對象的名稱(它是“command”,除非您在控制器配置中更改了它)、句點和您希望綁定到的命令對象上的字段名稱組成。您還可以使用嵌套字段,比如command.address.street。綁定宏假設默認的HTML轉義行爲是由ServletContext參數defaultHtmlEscape在web.xml中指定的。
宏的另一種形式稱爲<@spring.bindEscaped>接受第二個參數,該參數顯式地指定是否應該在狀態錯誤消息或值中使用HTML轉義。您可以根據需要將其設置爲true或false。額外的表單處理宏簡化了HTML轉義的使用,您應該儘可能地使用這些宏。下一節將對此進行解釋。
輸入宏
FreeMarker附加的方便宏簡化了綁定和表單生成(包括驗證錯誤顯示)。從來沒有必要使用這些宏來生成表單輸入字段,您可以將它們與簡單的HTML或直接調用Spring bind宏(我們在前面突出顯示的)混合和匹配。
下表的可用宏顯示FreeMarker模板(FTL)的定義和參數列表,每個:
macro | FTL definition |
---|---|
|
<@spring.message code/> |
|
<@spring.messageText code, text/> |
|
<@spring.url relativeUrl/> |
|
<@spring.formInput path, attributes, fieldType/> |
|
<@spring.formHiddenInput path, attributes/> |
|
<@spring.formPasswordInput path, attributes/> |
|
<@spring.formTextarea path, attributes/> |
|
<@spring.formSingleSelect path, options, attributes/> |
|
<@spring.formMultiSelect path, options, attributes/> |
|
<@spring.formRadioButtons path, options separator, attributes/> |
|
<@spring.formCheckboxes path, options, separator, attributes/> |
|
<@spring.formCheckbox path, attributes/> |
|
<@spring.showErrors separator, classOrStyle/> |
注意:在FreeMarker模板中,formHiddenInput和formPasswordInput實際上不是必需的,因爲您可以使用常規的formInput宏,將hidden或password指定爲fieldType參數的值。
以上任何一個宏的參數都具有一致的含義:
- path:要綁定到的字段的名稱(即“command.name”)。
- options:可以從輸入字段中選擇的所有可用值的映射。映射的鍵表示從表單發回並綁定到command對象的值。根據鍵存儲的映射對象是表單上顯示給用戶的標籤,可能與表單返回的相應值不同。通常,這樣的映射是由控制器作爲參考數據提供的。您可以使用任何映射實現,具體取決於所需的行爲。對於嚴格排序的映射,可以使用SortedMap(例如TreeMap)和適當的比較器,對於應該按插入順序返回值的任意映射,可以使用LinkedHashMap或公共集合中的LinkedMap。
- separator:當多個選項作爲離散元素(單選按鈕或複選框)可用時,用於分隔列表中每個元素的字符序列(如<br>)
- attributes:HTML標記本身中包含的任意標記或文本的附加字符串。該字符串由宏回顯。例如,在textarea字段中,您可以提供屬性(如'rows="5" cols="60"'),或者您可以傳遞樣式信息,如'style="border:1px solid silver"'。
- classOrStyle:對於showErrors宏,包裝每個錯誤的span元素使用的CSS類的名稱。如果沒有提供任何信息(或者值爲空),則錯誤被包裝在標記中。
下面幾節概述了宏的示例。
輸入字段
formInput宏接受path參數(command.name)和一個附加屬性參數(在下面的示例中爲空)。該宏與所有其他表單生成宏一起,對path參數執行隱式Spring綁定。在出現新的綁定之前,綁定仍然有效,因此showErrors宏不需要再次傳遞path參數—它對上一次創建綁定的字段進行操作。
showErrors宏接受一個分隔符參數(用於分隔給定字段上的多個錯誤的字符),並接受第二個參數——這一次是類名或樣式屬性。注意,FreeMarker可以爲attributes參數指定默認值。下面的例子展示瞭如何使用formInput和showWErrors宏:
<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>
下一個示例顯示錶單片段的輸出,生成name字段並在提交表單後顯示驗證錯誤,該字段中沒有值。驗證通過Spring的驗證框架進行。
生成的HTML類似於下面的例子:
Name:
<input type="text" name="name" value="">
<br>
<b>required</b>
<br>
<br>
formTextarea宏的工作方式與formInput宏相同,並且接受相同的參數列表。通常,第二個參數(屬性)用於傳遞文本區域的樣式信息或行和cols屬性。
選擇的字段
您可以使用四個選擇字段宏來生成HTML表單中常見的UI值選擇輸入:
-
formSingleSelect
-
formMultiSelect
-
formRadioButtons
-
formCheckboxes
這四個宏中的每一個都接受一個選項映射,其中包含表單字段的值和對應於該值的標籤。值和標籤可以是相同的。
下一個例子是FTL中的單選按鈕。form-backing對象爲這個字段指定默認值'London',因此不需要驗證。在呈現表單時,要選擇的整個城市列表將作爲參考數據提供給模型中的“cityMap”。下面的清單顯示了示例:
...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>
前面的清單顯示了一行單選按鈕,其中一個代表cityMap中的每個值,並使用分隔符“”。沒有提供額外的屬性(缺少宏的最後一個參數)。cityMap對映射中的每個鍵-值對使用相同的字符串。映射的鍵是表單作爲POST請求參數實際提交的內容。映射值是用戶看到的標籤。在前面的例子中,給定三個著名城市的列表和表單支持對象中的默認值,HTML類似於以下內容:
Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>
如果您的應用程序期望通過內部代碼處理城市(例如),您可以創建具有適當鍵的代碼映射,如下面的示例所示:
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
Map<String, String> cityMap = new LinkedHashMap<>();
cityMap.put("LDN", "London");
cityMap.put("PRS", "Paris");
cityMap.put("NYC", "New York");
Map<String, Object> model = new HashMap<>();
model.put("cityMap", cityMap);
return model;
}
代碼現在產生的輸出中,無線電值是相關的代碼,但用戶仍然看到更友好的城市名稱,如下所示:
Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>
HTML轉義
前面描述的表單宏的默認用法導致HTML元素符合HTML 4.01,並且使用web.xml文件中定義的HTML轉義的默認值,如Spring的綁定支持所使用的。爲了使元素符合XHTML或者覆蓋默認的HTML轉義值,您可以在模板中指定兩個變量(或者在模型中,它們對模板是可見的)。在模板中指定它們的好處是,它們可以在稍後的模板處理中更改爲不同的值,從而爲表單中的不同字段提供不同的行爲。
要切換到符合XHTML的標記,爲一個名爲xhtmlCompliant的模型或上下文變量指定一個值true,如下面的例子所示:
<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>
在處理這個指令之後,由Spring宏生成的任何元素現在都符合XHTML。
以類似的方式,可以爲每個字段指定HTML轉義,如下面的示例所示:
<#-- until this point, default HTML escaping is used -->
<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name"/>
<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->
1.10.3。Groovy的標記
Groovy Markup Template Engine的主要目標是生成類似於XML的標記(XML、XHTML、HTML5等),但是您可以使用它來生成任何基於文本的內容。Spring框架有一個內置的集成,用於將Spring MVC與Groovy標記結合使用。
Groovy標記模板引擎需要Groovy 2.3.1+.
配置
下面的示例展示瞭如何配置Groovy標記模板引擎:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.groovy();
}
// Configure the Groovy Markup Template Engine...
@Bean
public GroovyMarkupConfigurer groovyMarkupConfigurer() {
GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
configurer.setResourceLoaderPath("/WEB-INF/");
return configurer;
}
}
下面的示例演示如何在XML中配置相同的配置:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:groovy/>
</mvc:view-resolvers>
<!-- Configure the Groovy Markup Template Engine... -->
<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
例子
與傳統的模板引擎不同,Groovy標記依賴於使用生成器語法的DSL。下面的例子展示了一個HTML頁面的樣本模板:
yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
head {
meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
title('My page')
}
body {
p('This is an example of HTML contents')
}
}
1.10.4。腳本視圖
Spring框架有一個內置的集成,可以使用Spring MVC和任何可以在JSR-223 Java腳本引擎上運行的模板庫。我們已經在不同的腳本引擎上測試了以下模板庫:
Scripting Library | Scripting Engine |
---|---|
集成任何其他腳本引擎的基本規則是,它必須實現ScriptEngine和Invocable接口。
Requirements
你需要在你的類路徑中有腳本引擎,它的細節因腳本引擎而異:
納什霍恩JavaScript引擎由Java 8+提供。強烈建議使用可用的最新更新版本。
應該添加JRuby作爲Ruby支持的依賴項。
Jython應該作爲Python支持的依賴項添加。
org.jetbrains.kotlin:kotlin-script-util依賴關係和元inf /services/javax.script。包含org.jetbrain.kotlin.script.jsr223的ScriptEngineFactory文件。爲了支持Kotlin腳本,應該添加KotlinJsr223JvmLocalScriptEngineFactory行。有關更多細節,請參見此示例。
您需要有腳本模板庫。一種實現Javascript的方法是通過 WebJars。
腳本模板
您可以聲明ScriptTemplateConfigurer bean來指定要使用的腳本引擎、要加載的腳本文件、要調用什麼函數來呈現模板,等等。下面的例子使用了Mustache模板和Nashorn JavaScript引擎:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
}
下面的例子展示了XML中的相同安排:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:script-template/>
</mvc:view-resolvers>
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
<mvc:script location="mustache.js"/>
</mvc:script-template-configurer>
對於Java和XML配置,控制器看起來沒有什麼不同,如下面的例子所示:
@Controller
public class SampleController {
@GetMapping("/sample")
public String test(Model model) {
model.addAttribute("title", "Sample title");
model.addAttribute("body", "Sample body");
return "template";
}
}
下面的例子展示了Mustache模板:
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<p>{{body}}</p>
</body>
</html>
使用以下參數調用render函數:
- String template:模板內容
- Map model:視圖模型
- RenderingContext renderingContext: RenderingContext允許訪問應用程序上下文、語言環境、模板加載器和URL(從5.0開始)
Mustache.render()與這個簽名是天生兼容的,所以您可以直接調用它。
如果您的模板技術需要一些自定義,您可以提供實現自定義呈現函數的腳本。例如,Handlerbars需要在使用模板之前進行編譯,並且需要一個polyfill來模擬服務器端腳本引擎中不可用的一些瀏覽器功能。
下面的例子演示瞭如何做到這一點:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}
注意:在使用非線程安全的腳本引擎時,需要將sharedEngine屬性設置爲false,這些腳本引擎的模板庫不是爲併發性設計的,比如在Nashorn上運行的把手或React。在這種情況下,由於這個bug,需要Java SE 8 update 60,但是通常建議在任何情況下都使用最近的Java SE補丁版本。
js只定義了車Handlebars 正常運行所需要的window對象,如下:
var window = {};
render.js實現在使用模板之前先編譯模板。準備生產的實現還應該存儲任何重用的緩存模板或預編譯模板。您可以在腳本端這樣做(並處理您需要的任何自定義—例如管理模板引擎配置)。下面的例子演示瞭如何做到這一點:
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
有關更多配置示例,請參閱Spring框架單元測試、Java和參考資料。
1.10.5。JSP和JSTL
Spring框架有一個內置的集成,用於將Spring MVC與JSP和JSTL結合使用。
視圖解析器
使用jsp開發時,您可以聲明一個InternalResourceViewResolver
或ResourceBundleViewResolver bean。
ResourceBundleViewResolver依賴於一個屬性文件來定義映射到類和URL的視圖名稱。使用ResourceBundleViewResolver,您可以通過只使用一個解析器混合不同類型的視圖,如下面的例子所示:
<!-- the ResourceBundleViewResolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
# And a sample properties file is used (views.properties in WEB-INF/classes):
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp
productList.(class)=org.springframework.web.servlet.view.JstlView
productList.url=/WEB-INF/jsp/productlist.jsp
InternalResourceViewResolver也可以用於jsp。作爲一種最佳實踐,我們強烈建議將JSP文件放在“WEB-INF”目錄下的目錄中,這樣客戶機就不能直接訪問。
<bean id="viewResolver" 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>
JSPs versus JSTL
當使用JSP標準標記庫(JSTL)時,您必須使用一個特殊的視圖類JstlView,因爲JSTL需要一些準備工作,比如I18N特性才能工作。
Spring的JSP標記庫
Spring提供請求參數到命令對象的數據綁定,如前面幾章所述。爲了促進JSP頁面與那些數據綁定特性的結合,Spring提供了一些使事情變得更簡單的標記。所有的Spring標記都有HTML轉義特性來啓用或禁用字符轉義。
spring.tld標記庫描述符(tld)包含在spring-webmvc.jar中。有關單個標記的全面參考,請瀏覽API參考或查看標記庫描述。
Spring的表單標記庫
從2.0版本開始,Spring提供了一組全面的數據綁定標記,用於在使用JSP和Spring Web MVC時處理表單元素。每個標記都支持對應的HTML標記對應的一組屬性,使標記的使用更加熟悉和直觀。標籤生成的HTML是HTML 4.01/XHTML 1.0兼容的。
與其他表單/輸入標記庫不同,Spring的表單標記庫與Spring Web MVC集成在一起,允許標記訪問控制器處理的命令對象和引用數據。正如我們在下面的示例中所示,表單標記使jsp更容易開發、閱讀和維護。
我們將遍歷表單標記並查看如何使用每個標記的示例。我們已經包含了生成的HTML片段,其中某些標籤需要進一步的註釋。
配置
表單標記庫綁定在spring-webmvc.jar中。庫描述符稱爲spring-form.tld。
要使用這個庫中的標記,請將以下指令添加到JSP頁面的頂部:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
其中form是要用於來自這個庫的標記的標記名稱前綴。
表單標籤
此標記呈現HTML 'form'元素,並將綁定路徑公開給用於綁定的內部標記。它將命令對象放在PageContext中,以便內部標記可以訪問命令對象。這個庫中的所有其他標記都是表單標記的嵌套標記。
假設我們有一個名爲User的域對象。它是一個JavaBean,具有firstName和lastName等屬性。我們可以使用它作爲表單控制器的表單支持對象,它返回form.jsp。下面的示例展示了form.jsp的樣子:
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
下面的清單顯示了生成的HTML,它看起來像一個標準的表單:
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value="Harry"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value="Potter"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
前面的JSP假設表單支持對象的變量名是command。如果您已經將表單支持對象放入模型中,並使用另一個名稱(當然是最佳實踐),則可以將表單綁定到指定的變量,如下面的示例所示:
<form:form modelAttribute="user">
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
輸入標籤
此標記默認呈現一個具有綁定值和type='text'的HTML輸入元素。有關此標記的示例,請參見表單標記。您還可以使用html5特有的類型,如電子郵件、電話、日期等。
標籤的複選框
此標記呈現類型設置爲複選框的HTML輸入標記。
假設我們的用戶有偏好,比如訂閱時事通訊和愛好列表。下面的例子顯示了Preferences類:
public class Preferences {
private boolean receiveNewsletter;
private String[] interests;
private String favouriteWord;
public boolean isReceiveNewsletter() {
return receiveNewsletter;
}
public void setReceiveNewsletter(boolean receiveNewsletter) {
this.receiveNewsletter = receiveNewsletter;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests = interests;
}
public String getFavouriteWord() {
return favouriteWord;
}
public void setFavouriteWord(String favouriteWord) {
this.favouriteWord = favouriteWord;
}
}
相應的form.jsp可以類似於以下內容:
<form:form>
<table>
<tr>
<td>Subscribe to newsletter?:</td>
<%-- Approach 1: Property is of type java.lang.Boolean --%>
<td><form:checkbox path="preferences.receiveNewsletter"/></td>
</tr>
<tr>
<td>Interests:</td>
<%-- Approach 2: Property is of an array or of type java.util.Collection --%>
<td>
Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
</td>
</tr>
<tr>
<td>Favourite Word:</td>
<%-- Approach 3: Property is of type java.lang.Object --%>
<td>
Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
</td>
</tr>
</table>
</form:form>
有三種方法可以實現複選框標記,它們應該滿足您的所有複選框需求。
- 方法一:當綁定值是java.lang類型時。如果綁定值爲true,則輸入(複選框)被標記爲已選中。value屬性對應於setValue(Object)值屬性的解析值。
- 方法二:當綁定值是array或java.util類型時。如果已配置的setValue(Object)值出現在綁定集合中,則輸入(複選框)被標記爲已選中。
- 方法三:對於任何其他綁定值類型,如果配置的setValue(對象)等於綁定值,則輸入(複選框)被標記爲已選中。
注意,無論採用哪種方法,都會生成相同的HTML結構。下面的HTML片段定義了一些複選框:
<tr>
<td>Interests:</td>
<td>
Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
<input type="hidden" value="1" name="_preferences.interests"/>
</td>
</tr>
您可能不希望在每個複選框後看到額外的隱藏字段。當HTML頁面中的複選框沒有被選中時,表單提交後,它的值不會作爲HTTP請求參數的一部分發送到服務器,因此我們需要一個解決方案來解決HTML中的這個問題,以便Spring表單數據綁定能夠工作。複選框標記遵循現有的Spring約定,即爲每個複選框包含一個以下劃線(_)爲前綴的隱藏參數。通過這樣做,您實際上是在告訴Spring“複選框在表單中是可見的,我希望表單數據綁定到的對象能夠反映複選框的狀態,無論如何。”
複選框的標籤
此標記呈現多個類型設置爲複選框的HTML輸入標記。
本節以前面複選框標記部分中的示例爲基礎。有時,您不希望在JSP頁面中列出所有可能的愛好。您寧願在運行時提供可用選項的列表並將其傳遞給標記。這就是複選框標記的用途。可以傳入一個數組、一個列表或一個包含items屬性中可用選項的映射。通常,綁定屬性是一個集合,因此它可以包含用戶選擇的多個值。下面的例子展示了一個使用這個標籤的JSP:
<form:form>
<table>
<tr>
<td>Interests:</td>
<td>
<%-- Property is of an array or of type java.util.Collection --%>
<form:checkboxes path="preferences.interests" items="${interestList}"/>
</td>
</tr>
</table>
</form:form>
本例假設興趣列表是一個可用的模型屬性列表,其中包含要從中選擇的值的字符串。如果使用映射,則將映射條目鍵用作值,並將映射條目的值用作要顯示的標籤。您還可以使用自定義對象,其中可以通過使用itemValue爲值提供屬性名稱,通過使用itemLabel提供標籤。
radiobutton標記
此標記呈現類型設置爲radio的HTML輸入元素。
典型的使用模式是將多個標記實例綁定到相同的屬性,但是具有不同的值,如下面的示例所示:
<tr>
<td>Sex:</td>
<td>
Male: <form:radiobutton path="sex" value="M"/> <br/>
Female: <form:radiobutton path="sex" value="F"/>
</td>
</tr>
radiobuttons標籤
此標記呈現多個類型設置爲radio的HTML輸入元素。
與複選框標記一樣,您可能希望將可用選項作爲運行時變量傳遞。對於這種用法,可以使用radiobuttons標記。傳入一個數組、一個列表或一個包含items屬性中可用選項的映射。如果使用映射,則使用映射條目鍵作爲值,並使用映射條目的值作爲要顯示的標籤。您還可以使用自定義對象,其中可以通過使用itemValue提供值的屬性名,通過使用itemLabel提供標籤,如下例所示:
<tr>
<td>Sex:</td>
<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>
密碼標記
此標記呈現類型設置爲password並帶有綁定值的HTML輸入標記。
<tr>
<td>Password:</td>
<td>
<form:password path="password"/>
</td>
</tr>
注意,默認情況下不顯示密碼值。如果希望顯示密碼值,可以將showPassword屬性的值設置爲true,如下面的示例所示:
<tr>
<td>Password:</td>
<td>
<form:password path="password" value="^76525bvHGq" showPassword="true"/>
</td>
</tr>
選擇標記
此標記呈現HTML“select”元素。它支持對所選選項的數據綁定,以及使用嵌套的選項和選項標籤。
假設用戶有一個技能列表。相應的HTML可以是:
<tr>
<td>Skills:</td>
<td><form:select path="skills" items="${skills}"/></td>
</tr>
如果用戶的技能是草藥學的,那麼“技能”行的HTML源可以如下:
<tr>
<td>Skills:</td>
<td>
<select name="skills" multiple="true">
<option value="Potions">Potions</option>
<option value="Herbology" selected="selected">Herbology</option>
<option value="Quidditch">Quidditch</option>
</select>
</td>
</tr>
選擇標記
此標記呈現HTML選項元素。它根據綁定值設置所選的值。下面的HTML顯示了它的典型輸出:
<tr>
<td>House:</td>
<td>
<form:select path="house">
<form:option value="Gryffindor"/>
<form:option value="Hufflepuff"/>
<form:option value="Ravenclaw"/>
<form:option value="Slytherin"/>
</form:select>
</td>
</tr>
如果用戶的房子在格蘭芬多,“房子”行的HTML源代碼如下:
<tr>
<td>House:</td>
<td>
<select name="house">
<option value="Gryffindor" selected="selected">Gryffindor</option>
<option value="Hufflepuff">Hufflepuff</option>
<option value="Ravenclaw">Ravenclaw</option>
<option value="Slytherin">Slytherin</option>
</select>
</td>
</tr>
選擇標記
此標記呈現HTML選項元素列表。它根據綁定值設置所選屬性。下面的HTML顯示了它的典型輸出:
<tr>
<td>Country:</td>
<td>
<form:select path="country">
<form:option value="-" label="--Please Select"/>
<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
</form:select>
</td>
</tr>
如果用戶居住在英國,則“Country”行的HTML源代碼如下:
<tr>
<td>Country:</td>
<td>
<select name="country">
<option value="-">--Please Select</option>
<option value="AT">Austria</option>
<option value="UK" selected="selected">United Kingdom</option>
<option value="US">United States</option>
</select>
</td>
</tr>
正如前面的示例所示,選項標記和選項標記的組合使用生成了相同的標準HTML,但是允許您在JSP中顯式地指定一個僅用於顯示(屬於它的地方)的值,例如示例中的默認字符串:“——請選擇”。
項屬性通常由項對象的集合或數組填充。itemValue和itemLabel指的是那些item對象的bean屬性(如果指定的話)。否則,項對象本身將被轉換爲字符串。或者,您可以指定項目的映射,在這種情況下,映射鍵被解釋爲選項值,而映射值對應於選項標籤。如果同時指定了itemValue或itemLabel(或兩者都指定),則item value屬性應用於映射鍵,而item label屬性應用於映射值。
文本區域標記
此標記呈現HTML textarea元素。下面的HTML顯示了它的典型輸出:
<tr>
<td>Notes:</td>
<td><form:textarea path="notes" rows="3" cols="20"/></td>
<td><form:errors path="notes"/></td>
</tr>
隱藏的標籤
此標記呈現類型設置爲隱藏綁定值的HTML輸入標記。要提交未綁定的隱藏值,請使用類型設置爲hidden的HTML輸入標記。下面的HTML顯示了它的典型輸出:
<form:hidden path="house"/>
如果我們選擇提交隱藏的房屋價值,HTML將如下:
<input name="house" type="hidden" value="Gryffindor"/>
錯誤的標籤
此標記在HTML span元素中呈現字段錯誤。它提供對在控制器中創建的錯誤或與控制器關聯的任何驗證器創建的錯誤的訪問。
public class UserValidator implements Validator {
public boolean supports(Class candidate) {
return User.class.isAssignableFrom(candidate);
}
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
}
}
The form.jsp
could be as follows:
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<%-- Show errors for firstName field --%>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<%-- Show errors for lastName field --%>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
如果我們提交的表單中firstName和lastName是空值,如下所示:
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<%-- Associated errors to firstName field displayed --%>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<%-- Associated errors to lastName field displayed --%>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
如果我們想要顯示給定頁面的整個錯誤列表,該怎麼辦?下一個示例顯示errors標記還支持一些基本的通配符功能。
-
path="*"
: 展示所有的錯誤 -
path="lastName"
: 展示與lastName相關的所有錯誤 -
如果省略path,則只顯示對象錯誤。
下面的例子在頁面的頂部顯示了一個錯誤列表,然後在字段旁邊顯示了字段特定的錯誤:
<form:form>
<form:errors path="*" cssClass="errorBox"/>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
The HTML would be as follows:
<form method="POST">
<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
spring-form.tld
標記庫描述符(tld)包含在spring-webmvc.jar中。有關單個標記的全面參考,請瀏覽API參考或查看標記庫描述。
HTTP方法轉換
REST的一個關鍵原則是使用“統一接口”。這意味着所有資源(url)都可以通過使用相同的四種HTTP方法來操作:GET、PUT、POST和DELETE。對於每個方法,HTTP規範都定義了準確的語義。例如,GET應該總是一個安全的操作,這意味着它沒有副作用,而PUT或DELETE應該是冪等的,這意味着您可以一遍又一遍重複這些操作,但是最終結果應該是相同的。HTTP定義了這四個方法,而HTML只支持兩個:GET和POST。幸運的是,有兩種可能的變通方法:您可以使用JavaScript執行PUT或DELETE操作,也可以使用“real”方法作爲附加參數(在HTML表單中建模爲隱藏的輸入字段)進行POST操作。Spring的HiddenHttpMethodFilter使用了後一種技巧。這個過濾器是一個普通的Servlet過濾器,因此,它可以與任何web框架(不僅僅是Spring MVC)結合使用。將此篩選器添加到您的web。和帶有隱藏方法參數的POST被轉換爲相應的HTTP方法請求。
爲了支持HTTP方法轉換,Spring MVC表單標籤被更新爲支持設置HTTP方法。例如,下面的片段來自寵物診所示例:
<form:form method="delete">
<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>
前面的示例執行一個HTTP POST,“real”DELETE方法隱藏在一個請求參數後面。它由在web中定義的HiddenHttpMethodFilter獲取web.xml,如下例所示:
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<servlet-name>petclinic</servlet-name>
</filter-mapping>
下面的例子展示了對應的@Controller方法:
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
this.clinic.deletePet(petId);
return "redirect:/owners/" + ownerId;
}
HTML5標籤
Spring表單標籤庫允許輸入動態屬性,這意味着您可以輸入任何HTML5特定的屬性。
表單輸入標記支持輸入文本以外的類型屬性。這是爲了允許呈現新的HTML5特定的輸入類型,如電子郵件、日期、範圍等。注意,不需要輸入type='text',因爲text是默認類型。
1.10.6。Tiles
您可以在使用Spring的web應用程序中集成Tiles——就像其他任何視圖技術一樣。本節從廣義上描述瞭如何做到這一點。
本節主要討論Spring對org.springframe .web.servlet.view中第3版Tiles的支持。tiles3包。
依賴關係
爲了能夠使用Tiles,您必須在Tiles版本3.0.1或更高版本上添加一個依賴項,並將其傳遞依賴項添加到您的項目中。
配置
爲了能夠使用Tiles,您必須使用包含定義的文件來配置它(有關定義和其他Tiles概念的基本信息,請參見https://tiles.apache.org)。在春季,這是通過使用TilesConfigurer完成的。下面的示例ApplicationContext配置展示瞭如何做到這一點:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
</bean>
前面的示例定義了五個包含定義的文件。這些文件都位於WEB-INF/defs目錄中。在WebApplicationContext初始化時,加載文件,並初始化定義工廠。完成之後,定義文件中包含的tile可以用作Spring web應用程序中的視圖。爲了能夠使用視圖,您必須有一個ViewResolver,就像Spring使用的任何其他視圖技術一樣。可以使用UrlBasedViewResolver和ResourceBundleViewResolver這兩種實現之一。
您可以通過添加一個下劃線和區域設置來指定特定於區域設置的Tiles定義,如下面的示例所示:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/tiles.xml</value>
<value>/WEB-INF/defs/tiles_fr_FR.xml</value>
</list>
</property>
</bean>
對於前面的配置,tiles_fr_FR.xml用於fr_FR地區的請求,而tiles_fr_FR.xml是默認使用的。
注意:由於下劃線用於指示區域設置,我們建議不要在tile定義的文件名中使用它們。
UrlBasedViewResolver
UrlBasedViewResolver爲它要解析的每個視圖實例化給定的viewClass。下面的bean定義了一個UrlBasedViewResolver:
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>
ResourceBundleViewResolver
ResourceBundleViewResolver必須提供一個屬性文件,其中包含解析器可以使用的視圖名稱和視圖類。下面的例子展示了ResourceBundleViewResolver的bean定義,以及相應的視圖名稱和視圖類(取自Pet診所示例):
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
...
welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
welcomeView.url=welcome (this is the name of a Tiles definition)
vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
vetsView.url=vetsView (again, this is the name of a Tiles definition)
findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView
findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
...
當您使用ResourceBundleViewResolver時,您可以輕鬆地混合不同的視圖技術。
注意,TilesView類支持JSTL (JSP標準標記庫)。
SimpleSpringPreparerFactory和SpringBeanPreparerFactory
作爲一個高級特性,Spring還支持兩個特殊的Tiles製備工廠實現。有關如何在Tiles定義文件中使用ViewPreparer引用的詳細信息,請參閱Tiles文檔。
您可以指定simplespringprepareerfactory來基於指定的preparer類自動裝配ViewPreparer實例,應用Spring的容器回調,以及應用配置好的Spring beanpostprocessor。如果Spring的上下文範圍的註釋配置已經激活,ViewPreparer類中的註釋將被自動檢測和應用。注意,這需要在Tiles定義文件中準備類,就像默認的prepareerfactory所做的那樣。
您可以指定springbeanprepareerfactory來操作指定的準備器名稱(而不是類),從而從DispatcherServlet的應用程序上下文中獲得相應的Spring bean。在本例中,完整的bean創建過程由Spring應用程序上下文控制,允許使用顯式依賴項注入配置、作用域bean等。注意,您需要爲每個準備器名稱定義一個Spring bean定義(正如在tile定義中使用的那樣)。下面的例子展示瞭如何在TilesConfigurer bean上定義springbeanprepareerfactory屬性:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
<!-- resolving preparer names as Spring bean definition names -->
<property name="preparerFactoryClass"
value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>
</bean>
1.10.7。RSS和Atom
AbstractAtomFeedView和AbstractRssFeedView都繼承自AbstractFeedView基類,分別用於提供Atom和RSS提要視圖。它們基於ROME項目,位於org.springframework.web.servlet.view.feed包中。
AbstractAtomFeedView要求您實現buildFeedEntries()方法,也可以覆蓋buildFeedMetadata()方法(默認實現爲空)。下面的例子演示瞭如何做到這一點:
public class SampleContentAtomView extends AbstractAtomFeedView {
@Override
protected void buildFeedMetadata(Map<String, Object> model,
Feed feed, HttpServletRequest request) {
// implementation omitted
}
@Override
protected List<Entry> buildFeedEntries(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// implementation omitted
}
}
實現AbstractRssFeedView也需要類似的要求,如下例所示:
public class SampleContentRssView extends AbstractRssFeedView {
@Override
protected void buildFeedMetadata(Map<String, Object> model,
Channel feed, HttpServletRequest request) {
// implementation omitted
}
@Override
protected List<Item> buildFeedItems(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// implementation omitted
}
}
如果需要訪問地區,buildFeedItems()和buildFeedEntries()方法會傳遞HTTP請求。HTTP響應僅在設置cookie或其他HTTP標頭時傳遞。提要在方法返回後自動寫入響應對象。
有關創建Atom視圖的示例,請參閱Alef Arendsen的Spring Team博客文章。
1.10.8。PDF和Excel
Spring提供了返回HTML以外的輸出的方法,包括PDF和Excel電子表格。本節描述如何使用這些特性。
文檔視圖簡介
HTML頁面並不總是用戶查看模型輸出的最佳方式,Spring使得從模型數據動態生成PDF文檔或Excel電子表格變得很簡單。文檔是視圖,從服務器中以正確的內容類型流媒體,以使客戶機PC能夠運行其電子表格或PDF查看器應用程序作爲響應。
爲了使用Excel視圖,您需要將Apache POI庫添加到類路徑中。對於PDF生成,您需要添加(最好)OpenPDF庫。
注意:如果可能,您應該使用底層文檔生成庫的最新版本。特別是,我們強烈建議使用OpenPDF(例如OpenPDF 1.2.12),而不是過時的iText 2.1.7,因爲OpenPDF是積極維護的,並修復了不可信PDF內容的一個重要漏洞。
PDF的視圖
一個簡單的單詞列表PDF視圖可以擴展org.springframework.web.servlet.view.document.AbstractPdfView並實現buildPdfDocument()方法,如下例所示:
public class PdfWordList extends AbstractPdfView {
protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer,
HttpServletRequest request, HttpServletResponse response) throws Exception {
List<String> words = (List<String>) model.get("wordList");
for (String word : words) {
doc.add(new Paragraph(word));
}
}
}
控制器可以從外部視圖定義(通過名稱引用)或從處理程序方法作爲視圖實例返回這樣的視圖。
Excel的視圖
從Spring Framework 4.2開始,org.springframework.web.servlet.view.document.AbstractXlsView是作爲Excel視圖的基類提供的。它基於Apache POI,具有專門的子類(AbstractXlsxView和AbstractXlsxStreamingView)來取代過時的AbstractExcelView類。
編程模型類似於AbstractPdfView,使用buildExcelDocument()作爲中心模板方法,控制器能夠從外部定義(通過名稱)或從處理程序方法作爲視圖實例返回此類視圖。
1.10.9。Jackson
Spring提供了對Jackson JSON庫的支持。
基於jacksen的JSON MVC視圖
MappingJackson2JsonView使用Jackson庫的ObjectMapper將響應內容呈現爲JSON。默認情況下,模型映射的所有內容(特定於框架的類除外)都被編碼爲JSON。對於需要過濾映射內容的情況,您可以使用modelKeys屬性指定要編碼的一組特定的模型屬性。您還可以使用extractValueFromSingleKeyModel屬性來直接提取和序列化單鍵模型中的值,而不是作爲模型屬性的映射。
您可以根據需要使用Jackson提供的註釋定製JSON映射。當您需要進一步的控制時,您可以通過ObjectMapper屬性注入一個定製的ObjectMapper,用於需要爲特定類型提供定製的JSON序列化器和反序列化器的情況。
Jackson-based XML視圖
MappingJackson2XmlView使用Jackson XML擴展的XmlMapper將響應內容呈現爲XML。如果模型包含多個條目,您應該使用modelKey bean屬性顯式地設置要序列化的對象。如果模型包含單個條目,它將自動序列化。
您可以根據需要使用JAXB或Jackson提供的註釋定製XML映射。當需要進一步控制時,可以通過ObjectMapper屬性注入定製的XmlMapper,以便在需要爲特定類型提供序列化器和反序列化器的情況下使用定製的XML。
1.10.10。XML編組
MarshallingView使用XML編組器(在org.springframework中定義)。以將響應內容呈現爲XML。可以使用MarshallingView實例的modelKey bean屬性顯式地設置要編組的對象。或者,視圖遍歷所有模型屬性並封送編組器支持的第一個類型。有關org.springframework功能的更多信息。oxm包,參見使用O/X映射器編組XML。
1.10.11。XSLT的視圖
XSLT是一種用於XML的轉換語言,在web應用程序中作爲視圖技術而流行。如果您的應用程序自然地處理XML,或者您的模型可以很容易地轉換成XML,那麼作爲一種視圖技術,XSLT可能是一個不錯的選擇。下一節將展示如何生成XML文檔作爲模型數據,並在Spring Web MVC應用程序中使用XSLT進行轉換。
這個例子是一個簡單的Spring應用程序,它在控制器中創建一個單詞列表,並將它們添加到模型映射中。返回映射和XSLT視圖的視圖名。有關Spring Web MVC控制器接口的詳細信息,請參閱帶註釋的控制器。XSLT控制器將單詞列表轉換爲一個簡單的XML文檔,以便進行轉換。
Beans
配置是簡單Spring web應用程序的標準配置:MVC配置必須定義XsltViewResolver bean和常規MVC註釋配置。下面的例子演示瞭如何做到這一點:
@EnableWebMvc
@ComponentScan
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public XsltViewResolver xsltViewResolver() {
XsltViewResolver viewResolver = new XsltViewResolver();
viewResolver.setPrefix("/WEB-INF/xsl/");
viewResolver.setSuffix(".xslt");
return viewResolver;
}
}
控制器
我們還需要一個封裝字生成邏輯的控制器。
控制器邏輯封裝在@Controller類中,處理程序方法定義如下:
@Controller
public class XsltController {
@RequestMapping("/")
public String home(Model model) throws Exception {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element root = document.createElement("wordList");
List<String> words = Arrays.asList("Hello", "Spring", "Framework");
for (String word : words) {
Element wordNode = document.createElement("word");
Text textNode = document.createTextNode(word);
wordNode.appendChild(textNode);
root.appendChild(wordNode);
}
model.addAttribute("wordList", root);
return "home";
}
}
到目前爲止,我們只創建了一個DOM文檔並將其添加到模型映射中。注意,您還可以將XML文件作爲資源加載並使用它而不是自定義DOM文檔。
有一些軟件包可以自動“domify”對象圖,但是在Spring中,您可以完全靈活地從模型中以任何方式創建DOM。這可以防止XML的轉換在模型數據結構中扮演太大的角色,這在使用工具管理DOMification過程時是很危險的。
轉換
最後,XsltViewResolver解析“主”XSLT模板文件,並將DOM文檔合併到其中以生成我們的視圖。如XsltViewResolver配置中所示,XSLT模板位於WEB-INF/xsl目錄中的war文件中,並以XSLT文件擴展名結束。
下面的示例顯示了XSLT轉換:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes"/>
<xsl:template match="/">
<html>
<head><title>Hello!</title></head>
<body>
<h1>My First Words</h1>
<ul>
<xsl:apply-templates/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="word">
<li><xsl:value-of select="."/></li>
</xsl:template>
</xsl:stylesheet>
前面的轉換被渲染爲如下的HTML:
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello!</title>
</head>
<body>
<h1>My First Words</h1>
<ul>
<li>Hello</li>
<li>Spring</li>
<li>Framework</li>
</ul>
</body>
</html>
1.11。MVC配置
MVC Java配置和MVC XML名稱空間提供了適用於大多數應用程序的默認配置和用於自定義的配置API。
對於配置API中沒有的更高級的定製,請參閱高級Java配置和高級XML配置。
您不需要了解MVC Java配置和MVC名稱空間創建的底層bean。如果您想了解更多,請參閱特殊的Bean類型和Web MVC配置。
1.11.1。使MVC配置
在Java配置中,可以使用@EnableWebMvc註釋來啓用MVC配置,如下例所示:
@Configuration
@EnableWebMvc
public class WebConfig {
}
在XML配置中,可以使用<mvc:annotation-driven>元素來啓用mvc配置,如下例所示:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
前面的示例註冊了大量Spring MVC基礎設施bean,並根據類路徑上可用的依賴項進行調整(例如,JSON、XML和其他類型的有效負載轉換器)。
1.11.2。MVC配置API
在Java配置中,可以實現WebMvcConfigurer接口,如下面的示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
在XML中,您可以檢查<mvc:annotation-driven/>的屬性和子元素。您可以查看Spring MVC XML模式,或者使用IDE的代碼完成特性來發現哪些屬性和子元素可用。
1.11.3。類型轉換
默認情況下,會安裝數字和日期類型的格式化器,包括對@NumberFormat和@DateTimeFormat註釋的支持。如果類路徑上存在Joda-Time,那麼還將安裝對Joda-Time格式庫的完全支持。
在Java配置中,可以註冊自定義格式器和轉換器,如下面的示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.example.MyConverter"/>
</set>
</property>
<property name="formatters">
<set>
<bean class="org.example.MyFormatter"/>
<bean class="org.example.MyAnnotationFormatterFactory"/>
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.example.MyFormatterRegistrar"/>
</set>
</property>
</bean>
</beans>
有關何時使用FormatterRegistrar實現的更多信息,請參見FormatterRegistrar SPI和FormattingConversionServiceFactoryBean。
1.11.4。驗證
默認情況下,如果類路徑上存在Bean驗證(例如Hibernate驗證器),LocalValidatorFactoryBean將註冊爲全局驗證器,與@Valid一起使用,並在控制器方法參數上驗證。
在Java配置中,可以自定義全局驗證器實例,如下例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public Validator getValidator() {
// ...
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven validator="globalValidator"/>
</beans>
注意,你也可以在本地註冊驗證器實現,如下面的例子所示:
@Controller
public class MyController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}
}
注意:如果您需要在某個地方注入LocalValidatorFactoryBean,那麼創建一個bean並用@Primary標記它,以避免與MVC配置中聲明的bean發生衝突。
1.11.5。攔截器
在Java配置中,您可以註冊攔截器來應用於傳入的請求,如下面的示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<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>
1.11.6。內容類型
您可以配置Spring MVC如何從請求中確定所請求的媒體類型(例如,Accept標頭、URL路徑擴展、查詢參數等)。
默認情況下,首先檢查URL路徑擴展—將json、xml、rss和atom註冊爲已知的擴展(取決於類路徑依賴項)。第二個檢查Accept標頭。
考慮將這些缺省值更改爲僅接受header,如果必須使用基於url的內容類型解析,則考慮在路徑擴展上使用查詢參數策略。詳細信息請參閱後綴匹配和後綴匹配和RFD。
在Java配置中,可以自定義請求的內容類型解析,如下例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON);
configurer.mediaType("xml", MediaType.APPLICATION_XML);
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<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>
1.11.7。消息轉換器
您可以通過覆蓋configureMessageConverters()(來替換Spring MVC創建的默認轉換器)或覆蓋extendMessageConverters()(來定製默認轉換器或向默認轉換器添加額外的轉換器)在Java配置中定製HttpMessageConverter。
下面的示例使用定製的ObjectMapper而不是默認的ObjectMapper添加XML和Jackson JSON轉換器:
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
@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.createXmlMapper(true).build()));
}
}
在前面的例子中,Jackson2ObjectMapperBuilder是用來創建一個通用配置MappingJackson2HttpMessageConverter和MappingJackson2XmlHttpMessageConverter啓用了縮進,一個定製的日期格式,和jackson-module-parameter-names登記,這增加了支持訪問參數名稱(功能添加到Java 8)。
該生成器自定義Jackson的默認屬性如下:
- DeserializationFeature。FAIL_ON_UNKNOWN_PROPERTIES是禁用的。
- MapperFeature。DEFAULT_VIEW_INCLUSION是禁用的。
如果在類路徑中檢測到以下知名模塊,它還會自動註冊它們:
- jackson-datatype-joda:支持Joda-Time類型。
- jackson-datatype-jsr310:支持Java 8日期和時間API類型。
- jackson-datatype-jdk8:支持其他Java 8類型,比如Optional。
jackson-module-kotlin:
支持Kotlin類和數據類。
注意:需要使用Jackson XML支持啓用,需要在jackson-dataformat-xml添加
woodstox-core-asl依賴。
其他有趣的Jackson 模塊是可用的:
- jackson-datatype-money:支持javax。貨幣類型(非官方模塊)。
- jackson-datatype-hibernate:支持特定於hibernate的類型和屬性(包括延遲加載方面)。
下面的例子展示瞭如何在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"/>
1.11.8。視圖控制器
這是定義一個ParameterizableViewController的快捷方式,它在調用時立即轉發到視圖。如果在視圖生成響應之前沒有要執行的Java控制器邏輯,您可以在靜態情況下使用它。
下面的Java配置示例將一個對/的請求轉發給一個名爲home的視圖:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
下面的示例實現了與前面的示例相同的功能,但是使用的是XML,使用的是<mvc:view-controller>元素:
<mvc:view-controller path="/" view-name="home"/>
如果@RequestMapping方法映射到任何HTTP方法的URL,那麼視圖控制器就不能處理相同的URL。這是因爲通過URL與帶註釋的控制器匹配被認爲是端點所有權的足夠強的指示,因此可以將405 (METHOD_NOT_ALLOWED)、415 (UNSUPPORTED_MEDIA_TYPE)或類似的響應發送到客戶端以幫助調試。因此,建議避免在帶註釋的控制器和視圖控制器之間分割URL處理。
1.11.9。視圖解析器
MVC配置簡化了視圖解析器的註冊。
下面的Java配置示例使用JSP和Jackson作爲JSON呈現的默認視圖來配置內容協商視圖解析:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
}
下面的例子展示瞭如何在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、Tiles、Groovy標記和腳本模板也需要配置底層視圖技術。
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>
在Java配置中,您可以添加各自的Configurer bean,如下面的示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.freeMarker().cache(false);
}
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/freemarker");
return configurer;
}
}
1.11.10。靜態資源
此選項提供了一種方便的方式來從基於資源的位置列表中提供靜態資源。
在下一個示例中,給定一個以/resources開始的請求,相對路徑用於在web應用程序根目錄下或在/static類路徑下查找和提供相對於/public的靜態資源。這些資源有一年的有效期,以確保最大限度地使用瀏覽器緩存並減少瀏覽器發出的HTTP請求。最後修改的標頭也會被計算,如果有,則返回304狀態碼。
下面的清單展示瞭如何使用Java配置做到這一點:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCachePeriod(31556926);
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<mvc:resources mapping="/resources/**"
location="/public, classpath:/static/"
cache-period="31556926" />
參見靜態資源的HTTP緩存支持。
資源處理程序還支持ResourceResolver實現和ResourceTransformer實現的鏈,您可以使用它們創建用於處理優化資源的工具鏈。
您可以使用VersionResourceResolver根據從內容、固定的應用程序版本或其他計算出的MD5散列,對經過版本控制的資源url進行版本控制。ContentVersionStrategy (MD5散列)是一個不錯的選擇——但也有一些明顯的例外,比如與模塊加載器一起使用的JavaScript資源。
下面的示例演示瞭如何在Java配置中使用VersionResourceResolver:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<mvc:resources mapping="/resources/**" location="/public/">
<mvc:resource-chain resource-cache="true">
<mvc:resolvers>
<mvc:version-resolver>
<mvc:content-version-strategy patterns="/**"/>
</mvc:version-resolver>
</mvc:resolvers>
</mvc:resource-chain>
</mvc:resources>
然後可以使用ResourceUrlProvider重寫url,並應用完整的解析器和轉換器鏈——例如,插入版本。MVC配置提供了ResourceUrlProvider bean,因此可以將它注入到其他bean中。您還可以使用ResourceUrlEncodingFilter對Thymeleaf、jsp、FreeMarker和其他使用URL標記(依賴於HttpServletResponse#encodeURL)的程序進行透明重寫。
請注意,在使用EncodedResourceResolver(例如,用於提供gzip壓縮或brotli編碼的資源)和VersionResourceResolver時,必須按此順序註冊它們。這確保了基於內容的版本總是基於未編碼的文件進行可靠的計算。
WebJars也可以通過WebJarsResourceResolver來支持,當組織被註冊時,WebJarsResourceResolver會自動註冊。webjars:webjar -locator-core庫位於類路徑中。解析器可以重寫url來包含jar的版本,也可以匹配沒有版本的傳入url——例如,從/jquery/jquery.min.js到/jquery/1.2.0/jquery.min.js。
1.11.11。默認Servlet
Spring MVC允許將DispatcherServlet映射到/(因此覆蓋了容器默認Servlet的映射),同時仍然允許容器默認Servlet處理靜態資源請求。它將DefaultServletHttpRequestHandler配置爲URL映射爲/**,並且相對於其他URL映射具有最低優先級。
此處理程序將所有請求轉發到缺省Servlet。因此,它必須保持在所有其他URL處理程序映射的最後。如果您使用<mvc:註釋驅動的>,就會出現這種情況。另外,如果您設置了自己的自定義HandlerMapping實例,請確保將其order屬性設置爲一個比DefaultServletHttpRequestHandler更低的值,該值是Integer.MAX_VALUE。
下面的例子演示瞭如何使用默認設置啓用該功能:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<mvc:default-servlet-handler/>
覆蓋/ Servlet映射的警告是,必須根據名稱而不是路徑檢索缺省Servlet的RequestDispatcher。DefaultServletHttpRequestHandler嘗試在啓動時自動檢測容器的默認Servlet,使用大多數主要Servlet容器(包括Tomcat、Jetty、GlassFish、JBoss、Resin、WebLogic和WebSphere)的已知名稱列表。如果默認的Servlet是用不同的名字定製配置的,或者在默認的Servlet名字未知的地方使用了不同的Servlet容器,那麼必須顯式地提供默認的Servlet名字,如下例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable("myCustomDefaultServlet");
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
1.11.12。路徑匹配
您可以自定義與路徑匹配和URL處理相關的選項。有關各個選項的詳細信息,請參見PathMatchConfigurer javadoc。
下面的例子展示瞭如何在Java配置中自定義路徑匹配:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper())
.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
@Bean
public UrlPathHelper urlPathHelper() {
//...
}
@Bean
public PathMatcher antPathMatcher() {
//...
}
}
下面的例子展示瞭如何在XML中實現相同的配置:
<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"/>
1.11.13。先進的Java配置
@EnableWebMvc導入委託webmvcconfiguration,其中:
爲Spring MVC應用程序提供默認的Spring配置
檢測並委託WebMvcConfigurer實現來定製配置。
對於高級模式,您可以刪除@EnableWebMvc並直接從委託webmvcconfiguration擴展,而不是實現WebMvcConfigurer,如下例所示:
@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {
// ...
}
您可以在WebConfig中保留現有的方法,但是您現在也可以覆蓋基類中的bean聲明,並且您仍然可以在類路徑上擁有任意數量的其他WebMvcConfigurer實現。
1.11.14。先進的XML配置
MVC命名空間沒有高級模式。如果你需要定製一個bean上的屬性,你不能改變它,你可以使用Spring ApplicationContext的BeanPostProcessor生命週期鉤子,如下面的例子所示:
@Component
public class MyPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
// ...
}
}
注意,您需要將MyPostProcessor聲明爲bean,可以顯式地使用XML,也可以通過<component-scan/>聲明來檢測它。
1.12。HTTP/2
Servlet 4容器需要支持HTTP/2,而Spring Framework 5與Servlet API 4兼容。從編程模型的角度來看,應用程序不需要做任何特定的事情。但是,有一些與服務器配置相關的考慮事項。有關更多細節,請參見 HTTP/2 wiki page。
Servlet API確實公開了一個與HTTP/2相關的構造。您可以使用javax.servlet.http。PushBuilder主動地將資源推給客戶端,它作爲@RequestMapping方法的method argument受到支持。