官網參考文檔:https://docs.spring.io/spring-framework/docs/5.3.10-SNAPSHOT/reference/html/web.html#mvc
Spring源碼下載下傳位址:https://github.com/spring-projects/spring-framework/tags?after=v5.2.13.RELEASE
【1】SpringMVC中一些核心基礎類
SpringMVC通過一些
"特殊的bean"
來處理請求、以合适的方式處理響應(如頁面或者json)。
① HandlerMapping
将請求與用于預處理和後處理的攔截器清單一起映射到處理程式 。映射基于某些标準,其細節因
HandlerMapping
實作而異。
HandlerMapping 叫做處理器映射器,它的作用就是根據目前 request 找到對應的 Handler 和 Interceptor,然後封裝成一個 HandlerExecutionChain 對象傳回。
兩個主要
HandlerMapping
實作是
RequestMappingHandlerMapping
(支援帶
@RequestMapping
注釋的方法)和
SimpleUrlHandlerMapping
(維護 URI 路徑模式到處理程式的顯式注冊)。
HandlerMapping家族結構圖示

② HandlerAdapter
對于一個請求,幫助
DispatcherServlet
尋找到其對應的Handler并進行處理。
DispatcherServlet
不用關心
handler
是如何被發現并調用的。
HandlerAdapter家族結構圖示
③ HandlerExceptionResolver
提供解決異常的政策,可能将它們映射到處理程式、HTML 錯誤視圖或其他目标。常見實作類有:
AbstractHandlerExceptionResolver、AbstractHandlerMethodExceptionResolver、DefaultHandlerExceptionResolver、ExceptionHandlerExceptionResolver、HandlerExceptionResolverComposite、ResponseStatusExceptionResolver、SimpleMappingExceptionResolver
。
HandlerExceptionResolver家族結構圖示
關于異常解析器更多異常處理參考博文:SpringMVC中異常處理與ControllerAdvice捕捉全局異常
④ ViewResolver
解析一個handler傳回的
String
格式的視圖名稱為一個實際的
View
去渲染響應結果。
ViewResolver家族結構圖示
⑤ LocaleResolver, LocaleContextResolver
解析用戶端正在使用的區域設定以及可能的時區,以便能夠提供國際化視圖。
LocaleResolver家族結構圖示
⑥ ThemeResolver
解析web應用程式可以使用的主題 — 例如,提供個性化布局。
ThemeResolver家族結構圖示
⑦ MultipartResolver
在
multipart parsing library
幫助下解決
a multi-part request
。例如,浏覽器的檔案上傳。
org.springframework.web.multipart
包中的
MultipartResolver
是一種解析
multipart request
(包括檔案上載)的政策。有一個實作基于
Commons FileUpload
,另一個實作基于Servlet 3.0
multipart request
解析。
要啟用
multipart
處理,需要在
DispatcherServlet Spring
配置中聲明一個名為
multipartResolver
的
MultipartResolver
bean。DispatcherServlet檢測它并将其應用于傳入請求。當接收到内容類型為
multipart/form-data
的POST請求時,解析程式解析内容并将目前
HttpServletRequest
包裝為
MultipartHttpServletRequest
,以提供對解析檔案的通路,以及将部分作為請求參數公開。
其有兩個預設實作:
CommonsMultipartResolver
和
StandardServletMultipartResolver
。
Apache Commons FileUpload
為了使用
Apache Commons FileUpload
,你需要引入
commons-fileupload
包,并配置一個名為
multipartResolver
的
CommonsMultipartResolver
類型的bean。
此解析器變體将委托給應用程式中的本地庫,提供跨Servlet容器的最大可移植性。作為另一種選擇,通過下面讨論的容器自己的解析器來考慮标準
Servlet multipart
解決方案。
Commons FileUpload
傳統上僅适用于POST請求,但接受任何
multipart/
内容類型。
Servlet 3.0
Servlet 3.0
multipart
解析需要通過Servlet容器配置啟用。為此:
- 在Java中,在Servlet注冊上設定
。MultipartConfigElement
- 在web.xml中,向servlet聲明添加
部分。<multipart config>
以下示例顯示如何在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"));
}
}
一旦Servlet3.0配置就緒,就可以添加一個名為
multipartResolver
的
StandardServletMultipartResolver
類型的bean。
⑧ FlashMapManager
存儲和檢索可用于将屬性從一個請求傳遞到另一個請求的“輸入”和“輸出”FlashMap,通常是通過重定向。也就是請求重定向時,不同請求間共享參數。
應用程式可以聲明上述接口的實作類。
DispatcherServlet
會從
WebApplicationContext
檢測這些類型的實作。如果沒有檢測到,則會使用如下預設實作配置。
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
【2】SpringMVC配置DispatcherServlet
① web.xml配置
傳統環境中,通常在
web.xml
中配置
SpringMVC
的
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>
② Java配置
在Servlet 3.0+環境下,可以選擇使用如下方式配置
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
是SpringMVC提供的一個接口,它確定檢測到你的實作并自動用于初始化任何Servlet 3容器。
WebApplicationInitializer
的抽象基類實作
AbstractDispatcherServletInitializer
通過重寫指定servlet映射和
DispatcherServlet
配置的方法,使注冊
DispatcherServlet
變得更加容易。
如果是基于Java(類|注解配置)的Spring配置,推薦如下方式注冊DispatcherServlet:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
如果是基于xml的spring 配置,推薦繼承于AbstractDispatcherServletInitializer。
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
AbstractDispatcherServletInitializer
同樣提供了一種周遊的方式,注入Filter執行個體并使其自動映射到
DispatcherServlet
。
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
類似于如下xml配置
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
每個過濾器都會根據其具體類型添加一個預設名稱,并自動映射到DispatcherServlet。
AbstractDispatcherServletInitializer
中有一個
protected
方法
isAsyncSupported
。該
isAsyncSupported
方法提供了一個單一位置,用于在
DispatcherServlet
和映射到它的所有
filter
上啟用異步支援。預設情況下,此标志設定為
true
。
如果你想要更進一步自定義
DispatcherServlet
,你可以覆寫
AbstractDispatcherServletInitializer
的
createDispatcherServlet
方法。
如上所示,并沒有指定Filter對應的url-pattern,那麼會攔截哪些請求呢?預設情況下,會過濾每一個請求!HiddenHttpMethodFilter和CharacterEncodingFilter都是抽象類OncePerRequestFilter的子類。而OncePerRequestFilter預設情況下,會作用于每一個請求(看類名也能看出來)。
【3】SpringMVC的處理過程
① 處理邏輯
主要步驟如下 :
① 擷取WebApplicationContext 并作為attribute 放到目前request中供controller或者其他元件使用。預設屬性key使用
WEB_APPLICATION_CONTEXT_ATTRIBUTE(=DispatcherServlet.class.getName() + ".CONTEXT)"
。
② 為目前請求綁定locale 解析器,以使在處理過程中(如渲染視圖、準備資料等等)解析locale。如果你不需要解析locale,則無需locale解析器。
③ 為目前請求綁定主題解析器,以使元件如view可以決定使用哪個主題。如果你沒有使用主題,則可以忽略。
④ 如果你指定了一個multipart file解析器,則目前請求将會檢測multipart部分。如果multipart被發現,則将目前請求包裝
MultipartHttpServletRequest
内,以是處理過程中其他元件進一步解析。
⑤ 尋找到一個合适的handler。如果一個handler被找到,将運作與該處理程式handler(預處理器、後處理器和控制器)關聯的執行鍊,以準備用于渲染的模型Model。或者,對于帶注解(如@ResponseBody)的控制器,可以呈現響應(在HandlerAdapter中),而不是傳回視圖。
⑥ 如果傳回Model,則渲染視圖View。如果沒有傳回任何Model(可能是由于預處理器或後處理器攔截了請求,可能是出于安全原因),則不會呈現任何視圖View,因為請求可能已經完成。(這裡的Model、View、Controller是按照MVC思想講述,Model指的就是響應結果資料)。
聲明在
WebApplicationContext
中的
HandlerExceptionResolver
用于解決請求處理期間引發的異常。這些異常解析器允許自定義邏輯來處理異常。
② Servlet初始化參數
通過将Servlet初始化參數(init param)添加到web.xml檔案中的Servlet聲明中,可以自定義各個DispatcherServlet執行個體。下表列出了支援的參數:
參數 | 解釋 |
contextClass | 實作 的類,該類将由此Servlet執行個體化和本地配置。預設為 |
contextConfigLocation | 傳遞給上下文執行個體(由contextClass指定)以訓示可以在何處找到上下文配置檔案的字元串。該字元串可能由多個字元串(使用逗号作為分隔符)組成,以支援多個上下文。如果多個上下文位置包含定義了兩次的bean,則以最新位置為準。 |
namespace | 的命名空間,預設是 |
throwExceptionIfNoHandlerFound | 當找不到請求的處理程式時,是否引發 。可以使用 捕獲異常(例如,通過使用 控制器方法),并将其作為任何其他方法處理。預設情況下,該值設定為false,在這種情況下, 将響應狀态設定為404(未找到),而不會引發異常。請注意,如果還配置了預設servlet處理,則未解析的請求總是轉發到預設servlet,并且從不引發404。 |
【4】路徑比對
Servlet API
将完整的請求路徑公開為
requestURI
,并進一步将其細分為
contextPath
、
servletPath
和
pathInfo
(這些對象的值根據
Servlet
的映射方式而不同)。根據這些輸入,
Spring MVC
需要确定用于處理程式映射的查找路徑,該路徑是
DispatcherServlet
本身映射中的路徑,不包括
contextPath
和任何
servletMapping
字首(如果存在)。
servletPath
和
pathInfo
被轉義(
decoded
),這使得它們不可能直接與完整的
requestURI
進行比較。為了獲得lookupPath,有必要對
requestURI
進行轉義(
decoded
)。但是,這也帶來了自身的問題,因為路徑可能包含編碼的保留字元,例如
“/”
或
;
.這反過來會在decoded後改變路徑的結構,這也會導緻安全問題。此外,Servlet容器可能會在不同程度上規範化Servlet路徑,這使得執行
startsWith
與
requestUri
進行比較變得更加不可能。
這就是為什麼最好避免依賴基于
字首的servletPath映射類型
的
servletPath
。 如果
DispatcherServlet
被映射為帶有
“/”
的預設Servlet,或者沒有帶有
“/*”
字首的預設Servlet。如果Servlet容器是4.0+,那麼Spring MVC能夠檢測Servlet映射類型并避免使用
servletPath
和
pathInfo
。在
3.1 Servlet
容器上,假設相同的Servlet映射類型,可以通過MVC配置中的Path Matching提供一個
alwaysUseFullPath=true
的
UrlPathHelper
來實作同樣效果。
幸運的是,預設的
Servlet
映射“/”是一個不錯的選擇。但是,仍然存在一個問題,即需要對
requestUri
進行
decoded
,以便能夠與控制器映射進行比較。這也是不可取的,因為可能會對改變路徑結構的保留字元進行decoded 。如果不需要這些字元,則可以拒絕它們(如Spring Security HTTP防火牆),或者可以使用
urlDecode=false
配置UrlPathHelper,但控制器映射将需要與
encoded
路徑比對,這可能并不總是很好地工作。此外,有時DispatcherServlet需要與另一個Servlet共享URL空間,并且可能需要通過字首進行映射。
通過從PathMatcher切換到5.3或更高版本中提供的解析PathPattern,可以更全面地解決上述問題,請參見 Pattern Comparison.。與AntPathMatcher不同,AntPathMatcher需要
decoded
的查找路徑或
encoded
的控制器映射,一個解析的PathPattern與一個名為RequestPath(l解析後的path)比對,一次一個路徑段。這允許單獨decoded和清理路徑段值,而不存在改變路徑結構的風險。解析的PathPattern還支援使用servletPath字首映射,隻要字首保持簡單且不包含任何需要
encoded
的字元。
【5】攔截器
所有
HandlerMapping
實作都支援處理程式攔截器。當您希望将特定功能應用于某些請求時,這些攔截器非常有用 — 例如,檢查principal。攔截器必須實作
org.springframework.web.servlet
包中的
HandlerInterceptor
接口(并覆寫其三個方法)。
這三種方法應提供足夠的靈活性來執行各種預處理和後處理:
- preHandle(…): Before the actual handler is run
- postHandle(…): After the handler is run
- afterCompletion(…): After the complete request has finished
preHandle
方法傳回一個布爾值。可以使用該方法中斷或繼續處理這個執行鍊。方法傳回true時,執行鍊繼續向下執行。當傳回false時,
DispatcherServlet
假定攔截器本身已經處理了請求(例如,呈現了适當的視圖),并且不繼續執行其他攔截器和執行鍊中的實際處理程式。
可以在MVC配置類中注冊攔截器,也可以通過在單個
HandlerMapping
實作類上使用setter直接注冊它們。
注意,
postHandle
對使用了
@ResponseBody
注解或傳回
ResponseEntity
方法不太使用。在
postHandle
方法觸發前,已經在
HandlerAdapter
中編寫并送出響應。這意味着對響應進行任何更改(例如添加額外的頭)為時已晚。對于這樣的場景,您可以實作
ResponseBodyAdvice
并将其聲明為
Controller Advice bean
,或者直接在
RequestMappingHandlerAdapter
上配置它。
ResponseBodyAdvice是什麼?
其允許在一個使用了
@ResponseBody
注解或傳回
ResponseEntity
方法執行後并且在body被寫入前(使用HttpMessageConverter)自定義response 。
實作類可以直接注冊到
RequestMappingHandlerAdapter
和
ExceptionHandlerExceptionResolver
中,或者更可能使用
@ControllerAdvice
進行注解(在這種情況下,它們都将被自動檢測到)。
【6】異常
如果在請求映射期間發生異常或從請求處理程式(如@Controller)抛出異常,
DispatcherServlet
将委托給
HandlerExceptionResolver
異常解析器鍊以解決異常并提供替代處理方案,這通常是錯誤響應。
下表列出了可用的HandlerExceptionResolver實作:
HandlerExceptionResolver | Description |
SimpleMappingExceptionResolver | 異常類與錯誤頁面的映射 |
DefaultHandlerExceptionResolver | 将SpringMVC的異常映射為HTTP狀态碼 |
ResponseStatusExceptionResolver | 解析使用了@ResponseStatus注解的異常,并根據其屬性CODE映射到對應的HTTP STAUS |
ExceptionHandlerExceptionResolver | 通過調用在 注解或者 注解的類中被 注解的方法處理異常 |
異常解析器鍊
可以通過在Spring配置中聲明多個
HandlerExceptionResolver
bean
并根據需要設定它們的
order
屬性來形成異常解析器鍊。order屬性越高,異常解析程式的定位就越晚。
HandlerExceptionResolver
的協定規定它可以傳回:
- 一個ModelAndView 指向一個錯誤視圖;
- 如果異常被resolver處理,則傳回一個空的ModelAndView;
- 如果異常仍然未解決,則為null,以便後續的解析程式嘗試;如果異常仍然在最後,則允許它冒泡到Servlet容器。
MVC配置自動為預設Spring MVC異常、
@ResponseStatus
注解異常和
@ExceptionHandler
方法的支援聲明内置解析器。您可以自定義或替換該清單。即
DefaultHandlerExceptionResolver
、
ResponseStatusExceptionResolver
和
ExceptionHandlerExceptionResolver
。
容器錯誤頁面
如果任何
HandlerExceptionResolver
都無法解決異常,是以隻能傳播。或者如果響應狀态設定為錯誤狀态(即4xx、5xx),Servlet容器可以在HTML中呈現預設錯誤頁面。要自定義容器的預設錯誤頁,可以在web.xml中聲明錯誤頁映射。以下示例顯示了如何執行此操作:
<error-page>
<location>/error</location>
</error-page>
在前面的示例中,當出現異常或響應具有錯誤狀态時,Servlet容器會在容器中向配置的URL發送ERROR請求轉發(例如,/error)。然後,DispatcherServlet會對其進行處理,可能會将其映射到@Controller,這可以實作為傳回一個錯誤視圖或者JSON response,如下例所示:
@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
更多異常處理參考博文:SpringMVC中異常處理與ControllerAdvice捕捉全局異常
【7】視圖解析
Spring MVC定義了
ViewResolver
和View接口,允許你在浏覽器中渲染模型(render the Model),而無需使用特定的視圖技術。
ViewResolver
提供視圖名稱和實際視圖之間的映射。View處理在移交給特定視圖技術之前的資料準備。
更多ViewResolver 細節如下表所示:
視圖解析器 | 描述 |
| 的子類緩存它們解析的視圖執行個體。緩存提高了某些視圖技術的性能。通過将緩存屬性設定為 可以關閉緩存。此外,如果必須在運作時重新整理某個視圖(例如,修改FreeMarker模闆時),則可以使用 方法。 |
| 接口的簡單實作,進行邏輯視圖名稱到URL的直接解析,而無需顯式映射定義。如果您的邏輯名稱與視圖資源的名稱以簡單的方式比對,而不需要任意映射,那麼這是合适的。如 |
| 的一個友好子類,支援 (實際上就是Servlets 和 JSPs)和其子類如JstlView 和TilesView。可以使用 為此解析器生成的所有View指定View Class。 |
| 的友善子類,支援 及其自定義子類。 |
| 接口的實作,該接口基于請求檔案名或Accept頭解析視圖。 |
| ViewResolver接口的實作,該接口将視圖名稱解釋為目前應用程式上下文中的bean名稱。這是一個非常靈活的變體,允許根據不同的視圖名稱混合和比對不同的視圖類型。每個這樣的視圖都可以定義為一個bean,例如在XML或配置類中。 |
視圖解析器整體繼承樹如下所示
PS:idea裡面F4可以直接檢視類繼承樹結構。ctrl+t隻能檢視子類非樹結構。
① 處理
你可以聲明多個視圖解析器組成解析器鍊,必要時可以通過設定
order
屬性來進行排序。order值越大,解析器在處理鍊的位置越靠後。
ViewResolver的協定規定它可以傳回
null
以标明找不到該視圖。但是,對于JSP和
InternalResourceViewResolver
,确定JSP是否存在的唯一方法是通過
RequestDispatcher
執行排程。是以,必須始終将
InternalResourceViewResolver
配置為視圖解析程式整體順序中的最後一個。
配置視圖解析隻需要簡單的将ViewResolver bean配置載Spring中即可。MVC Config為
View Resolvers
和添加
logic-less View Controllers
提供了專用的配置API,這對于無控制器邏輯的HTML模闆呈現非常有用。
② 重定向
視圖名稱中的特殊
redirect:
字首允許你執行重定向。
UrlBasedViewResolver
(及其子類)将其識别為需要重定向的指令。視圖名稱的其餘部分是重定向URL。
實際效果與控制器傳回一個RedirectView重定向視圖的效果相同,但現在控制器本身可以根據邏輯視圖名稱進行操作。邏輯視圖名稱(例如
redirect:/myapp/some/resource
)相對于目前Servlet上下文重定向,而名稱例如
redirect:https://myhost.com/some/arbitrary/path
重定向到絕對URL。
請注意,如果控制器方法使用
@ResponseStatus
進行注釋,則注釋值優先于
RedirectView
設定的響應狀态。
③ 轉發
你還可以為那些被UrlBasedViewResolver 和其子類解析的視圖名稱指定特殊字首
forward:
。這将建立一個
InternalResourceView
,它執行
RequestDispatcher.forward()
操作。
是以,這個字首對于
InternalResourceViewResolver
和
InternalResourceView
(對于JSP)來說并不有用,但是如果您使用另一種視圖技術,但仍然希望強制Servlet/JSP引擎處理資源的轉發,那麼它會很有幫助。注意,你可以使用視圖解析器鍊來替代。
轉發與重定向更多資訊參考博文:SpringMVC中轉發與重定向(redirect與forward)實踐執行個體
④ 内容判斷
ContentNegotingViewResolver
本身不解析視圖,而是委托給其他視圖解析程式,并選擇與用戶端請求的表示類似的視圖。可以從Accept标頭或查詢參數(例如,
“/path?format=pdf”
)确定表示形式。
ContentNegotiatingViewResolver
通過将請求媒體類型與每個ViewResolver關聯的View支援的媒體類型(也稱為
Content-Type
)進行比較,選擇适當的View來處理請求。清單中具有相容
Content-Type
内容類型的第一個視圖View将表示形式傳回給用戶端。如果ViewResolver鍊無法提供相容的視圖,将查閱通過DefaultViews屬性指定的視圖清單。後一個選項适用于可以呈現目前資源的适當表示形式的單例視圖,而不管邏輯視圖名稱如何。Accept标頭可以包含通配符(例如
text/*
),在這種情況下,内容類型
Content-Type
為
text/xml
的視圖是相容的比對項。
【8】Locale區域/本地化
Spring架構的大多數部分都支援國際化,就像Spring MVC架構一樣。DispatcherServlet允許您使用用戶端的區域設定自動解析消息。這是通過LocaleResolver對象完成的。
當請求傳入時,DispatcherServlet會查找區域設定解析程式,如果找到了,它會嘗試使用它來設定區域設定。通過使用
RequestContext.getLocale()
方法,可以檢索由區域設定解析程式解析的區域設定。
除了自動區域設定解析外,還可以将攔截器附加到HandlerMapping,以在特定情況下(例如,基于請求中的參數)更改區域設定。
區域設定解析器和攔截器在
org.springframework.web.servlet.i18n
包中定義,并以正常方式在應用程式上下文中配置。Spring中包括以下區域設定解析程式選擇。
- Time Zone
- Header Resolver
- Cookie Resolver
- Session Resolver
- Locale Interceptor
① Time Zone
除了擷取客戶機的區域設定外,了解其時區通常也很有用。LocaleContextResolver接口提供了LocalResolver的擴充,允許解析器提供更豐富的LocaleContext,其中可能包括時區資訊。
如果可用,可以使用
RequestContext.getTimeZone()
方法擷取使用者的時區。通過Spring的
ConversionService
注冊的任何
Date/Time Converter
和
Formatter
格式化程式對象都會自動使用時區資訊。
② Header Resolver
此區域設定解析器檢查用戶端(例如web浏覽器)發送的請求中的
accept-language
請求頭字段。通常,請求頭包含用戶端作業系統的區域設定。請注意,此解析器不支援時區資訊。
③ Cookie Resolver
此區域設定解析器檢查用戶端上可能存在的Cookie,以檢視是否指定了Locale(區域設定)或TimeZone(時區)。如果是,則使用指定的詳細資訊。可以通過該解析器的屬性,設定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的屬性。
屬性 | 預設值 | 描述 |
cookieName | classname + LOCALE | cookie的名字 |
cookieMaxAge | 由Servlet 容器預設指定 | cookie 在用戶端存活時限。如果設定為 ,則浏覽器關閉即銷毀 |
cookiePath | | 将cookie的可見性限制在站點的特定部分。指定cookiePath時,cookie僅對該路徑及其下的路徑可見。 |
④ Session Resolver
SessionLocalerResolver
允許你從可能與使用者請求關聯的會話中檢索區域設定Locale 和時區TimeZone 。與
CookieLocaleResolver
不同,此政策将本地選擇的locale 設定存儲在
Servlet
容器的
HttpSession
中。是以,這些設定對于每個會話都是臨時的,是以在每個會話結束時都會丢失。
請注意,與外部會話管理機制(如Spring Session項目)沒有直接關系。此
SessionLocalerResolver
根據目前
HttpServletRequest
評估和修改相應的HttpSession屬性。
⑤ Locale Interceptor
通過将
LocaleChangeInterceptor
添加到某個
HandlerMapping
執行個體中,可以啟用區域設定更改。它檢測請求中的一個參數并相應地更改區域設定(在dispatcher的應用程式上下文中調用
LocaleResolver
上的
setLocale
方法)。下一個示例顯示,對包含名為
siteLanguage
的參數的所有
*.view
資源的調用現在會更改區域設定。例如,請求URL為
https://www.sf.net/home.view?siteLanguage=nl
,将站點語言更改為荷蘭語。以下示例顯示了如何攔截區域設定:
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="siteLanguage"/>
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="localeChangeInterceptor"/>
</list>
</property>
<property name="mappings">
<value>/**/*.view=someController</value>
</property>
</bean>
【9】過濾器
Spring工程的
spring-web
子產品(也就是Spring MVC)提供了一些非常好用的過濾器:
- Form Data(表單資料)
- Forwarded Headers(轉發請求頭)
- Shallow ETag
- CORS(跨域相關)
① Form Data
浏覽器隻能通過HTTP的GET或者POST請求送出表單資料,但是非浏覽器用戶端可以使用HTTP的PUT、PATCH和DELETE請求。ServletAPI要求
ServletRequest.getParameter*()
方法僅支援HTTP POST的表單字段通路。
spring-web子產品提供了
FormContentFilter
過濾器攔截
Content-Type
為
application/x-www-form-urlencoded
的HTTP PUT、PATCH和DELETE請求,從請求體重讀取表單資料并且包裝ServletRequest 使其表單資料能夠被
ServletRequest.getParameter*()
方法家族讀取。
FormContentFilter類結構圖如下所示
② Forwarded Headers
當請求通過代理(如負載平衡器)時,主機、端口和方案可能會發生變化,這使得從用戶端角度建立指向正确主機、端口和方案的連結成為一項挑戰。
RFC 7239定義了
Forwarded
HTTP頭,代理可以使用該頭提供有關原始請求的資訊。還有其他非标準頭,包括
X-Forwarded-Host
、
X-Forwarded-Port
、
X-Forwarded-Proto
、
X-Forwarded-Ssl
和
X-Forwarded-Prefix
。
ForwardedHeaderFilter
是一個Servlet過濾器,它修改請求,以便:a)根據轉發的頭更改主機、端口和協定;b)删除這些頭以消除進一步的影響。
ForwardedHeaderFilter
通過包裝請求(和響應)實作功能,是以它必須排序在其他過濾器(如
RequestContextFilter
)之前,這些過濾器應處理修改後的請求,而不是原始請求。
如下圖所示,該過濾器的核心方法doFilterInternal會對請求和響應進行包裝,然後交給過濾器鍊。
對于轉發的頭,存在安全風險,因為應用程式無法知道頭是由代理添加的,還是由惡意用戶端添加的。這就是為什麼信任邊界處的代理應該配置為删除來自外部的不受信任的
Forwarded
頭。還可以将
ForwardedHeaderFilter
配置為
removeOnly=true
,在這種情況下,它會删除且不使用
headers
。
為了支援異步請求和錯誤排程,此過濾器應映射為
DispatcherType.ASYNC
和
DispatcherType.error
。如果使用Spring架構的
AbstractAnnotationConfigDispatcherServletInitializer
(請參閱Servlet配置),則會自動為所有轉發類型注冊所有過濾器。但是,如果通過web.xml或通過
FilterRegistrationBean
在Spring Boot中注冊過濾器,請確定除了
DispatcherType.REQUEST
之外還包括
DispatcherType.ASYNC和DispatcherType.ERROR
。
③ Shallow ETag
ShallowEtagHeaderFilter
過濾器通過緩存寫到響應的内容并根據内容計算一個MD5 哈希來建立一個
“shallow” ETag
。下次用戶端發送時,它會執行相同的操作,但它還會将計算值與
If-None-Match
請求頭進行比較,如果兩者相等,則傳回
304(NOT_MODIFIED)
。
此政策節省網絡帶寬,但不節省CPU,因為必須為每個請求計算完整響應。前面描述的控制器級别的其他政策可以避免計算。
ShallowEtagHeaderFilter
過濾器有一個
writeWeakETag
參數可以配置過濾器寫入
weak ETags
内容類似如下:
W/"02a2d595e6ed9a0b24f027f2b63b134d6"
(如在RFC 7232 Section 2.3定義一樣)。
為了支援異步請求,必須使用
DispatcherType.ASYNC
映射此過濾器,以便過濾器可以延遲并成功生成ETag,直到最後一次異步排程結束。如果使用Spring架構的
AbstractAnnotationConfigDispatcherServletInitializer
(請參閱Servlet配置),則會自動為所有轉發類型注冊所有過濾器。但是,如果通過web.xml或通過
FilterRegistrationBean
在Spring Boot中注冊過濾器,請確定包括
DispatcherType.ASYNC
。
④ CORS
SpringMVC通過控制器上的注解為CORS配置提供細粒度支援。然而,當與Spring Security一起使用時,我們建議依賴内置的
CorsFilter
,它必須在Spring Security的過濾器鍊之前生效。
出于安全原因,浏覽器禁止對目前來源之外的資源進行AJAX調用。例如,您可以在一個頁籤中設定銀行帳戶,在另一個頁籤中設定evil.com。來自evil.com的腳本不能使用您的憑據向您的銀行API發出AJAX請求 — 例如,從您的帳戶中提款!
Cross-Origin Resource Sharing (CORS)
是由大多數浏覽器實作的W3C規範,它允許您指定授權的跨域請求類型,而不是使用基于IFRAME或JSONP的不太安全和功能不太強大的解決方案。
CORS規範區分預檢、簡單和實際請求。要了解CORS的工作原理,您可以閱讀該篇文章以及其他許多文章,或者檢視規範以了解更多詳細資訊。
Spring MVC 中的HandlerMapping實作提供了對CORS的内置支援。再成功地将請求映射到處理程式後,HandlerMapping實作将檢查給定請求和處理程式handler的CORS配置,并采取進一步的操作。
Preflight
請求直接處理,而簡單和實際的CORS請求被截獲、驗證,并設定了所需的CORS響應頭。
為了啟用
cross-origin
跨來源請求(即,來源标頭存在并且與請求的主機不同),您需要有一些顯式聲明的CORS配置。如果沒有找到比對的CORS配置,
preflight
請求将被拒絕。簡單和實際CORS請求的響應中沒有添加CORS頭,是以浏覽器會拒絕它們。
每個HandlerMapping都可以使用基于URL模式的
CorsConfiguration
映射單獨配置。在大多數情況下,應用程式使用MVC Java配置或XML名稱空間來聲明此類映射,進而将單個全局映射傳遞給所有HandlerMapping執行個體。
你可以将
HandlerMapping
級别的全局CORS配置與更細粒度、處理程式級别的CORS配置相結合。例如,帶注解的控制器可以使用類級或方法級
@CrossOrigin
注解(其他處理程式可以實作
CorsConfigurationSource
)。
組合全局和本地配置的規則通常是相加的 — 例如,所有全局和所有本地配置。對于隻能接受單個值的屬性,例如
allowCredentials
和
maxAge
,本地值将覆寫全局值。
@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
注解支援:
- 所有來源
- 所有請求頭
- 所有controller method比對的HTTP方法
預設情況下不啟用
allowCredentials
,因為它建立了一個信任級别,該級别公開敏感的使用者特定資訊(如cookie和CSRF令牌),并且隻應在适當的情況下使用。啟用時,必須将
allowOrigins
設定為一個或多個特定域(而不是特殊值
“*”
),或者可以使用
allowOriginPatterns
屬性來比對一組動态來源請求。
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) {
// ...
}
}
Global Configuration
除了細粒度的控制器方法級配置之外,您可能還想定義一些全局CORS配置。您可以在任何HandlerMapping上單獨設定基于URL的CorsConfiguration映射。然而,大多數應用程式都使用MVC Java配置或MVC XML名稱空間來實作這一點。
預設情況下,全局配置啟用如下功能:
- 所有的請求來源
- 所有的請求頭
- GET,HEAD和POST方法
預設情況下不啟用
allowCredentials
,因為它建立了一個信任級别,該級别公開敏感的使用者特定資訊(如cookie和CSRF令牌),并且隻應在适當的情況下使用。啟用時,必須将
allowOrigins
設定為一個或多個特定域(而不是特殊值
“*”
),或者可以使用
allowOriginPatterns
屬性來比對一組動态來源請求。
maxAge
被設定為30分鐘。
可以通過Java配置定義跨域設定。
@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...
}
}
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").
allowedOrigins("*"). //允許跨域的域名,可以用*表示允許任何域名使用
allowedMethods("*"). //允許任何方法(post、get等)
allowedHeaders("*"). //允許任何請求頭
allowCredentials(true). //帶上cookie資訊
exposedHeaders(HttpHeaders.SET_COOKIE)
.maxAge(3600L);
//maxAge(3600)表明在3600秒内,不需要再發送預檢驗請求,可以緩存該結果
}
//...
}
要在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>
CORS Filter
還可以通過内置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);
⑤ HiddenHttpMethodFilter
浏覽器form表單隻支援GET與POST請求,Spring3.0增加了一個過濾器,可以将這些HTTP請求轉換為标準的HTTP方法,使得支援
GET、POST、PUT
與
DELETE、PATCH
請求。
該Filter它将釋出的方法參數轉換為HTTP方法,可通過
HttpServletRequest#getMethod()
檢索。由于浏覽器目前隻支援GET和POST,原型庫使用的一種常見技術是使用普通POST和一個額外的隐藏表單字段
(_method)
來傳遞“
real
”HTTP方法。此filter讀取該參數并相應地更改
HttpServletRequestWrapper#getMethod()
傳回值。
該過濾器隻支援
PUT、DELETE
以及
PATCH
HTTP方法。請求參數名稱預設是
_method
,但是可以通過
setMethodParam(String)
方法改變
methodParam
屬性。
在multipart POST請求的情況下,此過濾器需要在
multipart
處理後運作,因為它固有地需要檢查POST body參數。通常,在你的web.xml中需要将
org.springframework.web.multipart.support.MultipartFilte
配置在
HiddenHttpMethodFilter
之前。
過濾器源碼如下:
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS =
Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return this.method;
}
}
}
⑥ CharacterEncodingFilter
Servlet Filter 允許為請求指定一個字元編碼。這很有用,因為目前浏覽器通常不設定字元編碼,即使在HTML頁面或表單中指定了字元編碼。
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>