官网参考文档: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>