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
提供了用于请求处理的共享算法,而实际工作是由可配置的委托组件执行的。 该模型非常灵活,并支持多种工作流程。
DispatcherServlet
与任何Servlet一样,都需要使用 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下的示例)。
org.springframework.web.SpringServletContainerInitializer
以下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 Boot遵循不同的初始化顺序。Spring Boot并没有陷入Servlet容器的生命周期,而是使用Spring配置来引导自身和嵌入式Servlet容器。
Filter
和Servlet
声明在Spring配置中检测到并注册到Servlet容器中。有关更多详细信息,请参见 Spring Boot文档。
1.1.1。上下文层次
DispatcherServlet
需要 WebApplicationContext
(ApplicationContext
的扩展)为其自身的配置。 WebApplicationContext
具有指向ServletContext
和与其关联的Servlet
的链接。 它还绑定到ServletContext
,以便应用程序可以在RequestContextUtils
上使用静态方法来查找WebApplicationContext
(如果需要获取它们)。
对于许多应用来说,拥有单个WebApplicationContext
简单且足够。也可能有一个上下文层次结构,其中一个根WebApplicationContext
在多个DispatcherServlet
(或其他Servlet
)实例之间共享,每个实例都有其自己的子WebApplicationContext
配置。有关上下文层次结构功能的更多信息,请参见的 ApplicationContext
的其他功能。
根WebApplicationContext
通常包含需要在多个Servlet实例之间共享的基础结构Bean,例如数据存储库和业务服务。 这些Bean是有效继承的,并且可以在Servlet
特定的子WebApplicationContext
中重写(即重新声明),该子WebApplicationContext
通常包含给定Servlet本地的Bean。 下图显示了这种关系:
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup
以下示例配置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管理Object
实例。 这些通常带有内置约定,但是您可以自定义其属性并扩展或替换它们。
下表列出了DispatcherServlet
会检测的特殊bean:
Bean 类型 | 说明 |
---|---|
HandlerMapping |
将请求与拦截器列表一起映射到处理程序,以进行预处理和后期处理。映射基于某些标准,具体标准因HandlerMapping 实现而异。两个主要HandlerMapping 实现是RequestMappingHandlerMapping (支持带@RequestMapping 注释的方法)和SimpleUrlHandlerMapping (将URI路径模式显式注册到处理程序)。 |
HandlerAdapter |
帮助DispatcherServlet 调用映射到请求的处理程序,而不管实际如何调用该处理程序。例如,调用带注释的控制器需要解析注释。HandlerAdapter 的主要目的是保护DispatcherServlet 这些细节。 |
HandlerExceptionResolver |
解决异常的策略,可能将它们映射到处理程序,HTML错误视图或其他目标。请参阅例外。 |
ViewResolver |
解析从处理程序返回的实际基于字符串的基于逻辑的视图名称,以实际的视图呈现给响应。 请参阅 View Resolution 和 View Technologies。 |
LocaleResolver ,LocaleContextResolver |
解决一个客户正在使用的Locale 并且可能是其时区的问题,以便能够提供国际化的视图。请参阅语言环境。 |
ThemeResolver |
解决Web应用程序可以使用的主题,例如,提供个性化的布局。请参阅主题。 |
MultipartResolver |
借助一些多部分解析库来解析 multi-part 请求的抽象(例如,浏览器表单文件上传)。请参见Multipart Resolver。 |
FlashMapManager |
存储和检索“输入”和“输出” FlashMap ,它们通常用于通过重定向将属性从一个请求传递到另一个请求。请参见Flash属性。 |
默认配置在
DispatcherServlet.properties
中调用:
org.springframework.web.servlet.DispatcherServlet#doDispatch
1.1.3。Web MVC配置
应用程序可以声明处理请求所需的特殊Bean类型中列出的基础结构Bean。 DispatcherServlet
检查每个特殊bean的WebApplicationContext
。 如果没有匹配的bean类型,它将使用DispatcherServlet.properties
中列出的默认类型。
C:/Users/80953/.ideaLibSources/spring-webmvc-5.2.3.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:283
在大多数情况下,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 容器。WebApplicationInitializer
的抽象基类实现名为AbstractDispatcherServletInitializer
,它通过覆盖指定Servlet映射和DispatcherServlet
配置位置的方法,使注册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
还提供了一种添加Filter
实例并将其自动映射到DispatcherServlet
的便捷方法,如以下示例所示:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
每个过滤器都会根据其具体类型添加默认名称,并自动映射到DispatcherServlet
。
AbstractDispatcherServletInitializer
的protected
方法isAsyncSupported
提供了一个位置,以在DispatcherServlet
和映射到它的所有过滤器上启用异步支持。默认情况下,此标志设置为true
。
最后,如果您需要进一步自定义DispatcherServlet
自身,则可以覆盖该createDispatcherServlet
方法。
1.1.5. 请求处理过程
DispatcherServlet
处理请求过程如下:
- 搜索
WebApplicationContext
并将其绑定为请求中的属性,控制器和流程中的其他元素可以使用该属性。 默认情况下,它绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
键下。 - 语言环境解析器绑定到请求,以使流程中的元素解析在处理请求(呈现视图,准备数据等)时要使用的语言环境。 如果不需要语言环境解析,则不需要语言环境解析器。
- 主题解析器绑定到请求,以使诸如视图之类的元素确定要使用的主题。如果不使用主题,则可以将其忽略。
- 如果指定多部分文件解析器,则将检查请求中是否有多部分。如果找到多部分,则将请求包装在
MultipartHttpServletRequest
中,以供流程中的其他元素进一步处理。有关多部分处理的更多信息,请参见Multipart Resolver。 - 搜索适当的处理程序。 如果找到处理程序,则执行与处理程序(预处理器,后处理器和控制器)关联的执行链,以准备模型或渲染。 另外,对于带注释的控制器,可以呈现响应(在
HandlerAdapter
中),而不是返回视图。 - 如果返回模型,则呈现视图。如果没有返回任何模型(可能是由于预处理器或后处理器拦截了该请求,可能出于安全原因),则不会呈现任何视图,因为该请求可能已经被满足。
org.springframework.web.servlet.DispatcherServlet#doService
WebApplicationContext
中声明的HandlerExceptionResolver
Bean用于解决在请求处理期间引发的异常。 这些异常解析器允许定制逻辑以解决异常。 有关更多详细信息,请参见 Exceptions。
Spring DispatcherServlet
还支持Servlet API所指定的last-modification-date
的返回。 确定特定请求的最后修改日期的过程很简单:DispatcherServlet
查找适当的处理程序映射,并测试找到的处理程序是否实现了LastModified
接口。 如果是这样,则将LastModified
接口的long getLastModified(request)
方法的值返回给客户端。
您可以通过将Servlet初始化参数(init-param
元素)添加到web.xml
文件中的Servlet声明中,来定制各个DispatcherServlet
实例。 下表列出了受支持的参数:
表1. DispatcherServlet
初始化参数
参数 | 说明 |
---|---|
contextClass |
实现ConfigurableWebApplicationContext 的类,由该Servlet实例化并在本地配置。默认情况下,使用XmlWebApplicationContext 。 |
contextConfigLocation |
传递给上下文实例的字符串(由contextClass 指定),以指示可以在哪里找到上下文。该字符串可能包含多个字符串(使用逗号作为分隔符)以支持多个上下文。对于具有两次定义的bean的多个上下文位置,以最新位置为准。 |
namespace |
WebApplicationContext 的命名空间。默认为[servlet-name]-servlet 。 |
throwExceptionIfNoHandlerFound |
在找不到请求的处理程序时是否抛出NoHandlerFoundException 。然后可以使用HandlerExceptionResolver (例如,使用 @ExceptionHandler 控制器方法)捕获该异常并将其作为其他任何异常进行处理。默认情况下,它设置为false ,在这种情况下,DispatcherServlet 将响应状态设置为404 (NOT_FOUND ),而不会引发异常。请注意,如果还配置了默认servlet处理,则始终将未解决的请求转发到默认servlet,并且永远不会引发404 。 |
org.springframework.web.servlet.HttpServletBean#init
1.1.6。拦截
所有HandlerMapping
实现都支持处理程序拦截器,当您要将特定功能应用于某些请求(例如,检查主体)时,这些拦截器将非常有用。拦截器必须使用三种方法从org.springframework.web.servlet
程序包中实现HandlerInterceptor
,这 三种方法应提供足够的灵活性以执行各种预处理和后处理:
preHandle(..)
:在执行实际的处理程序之前postHandle(..)
:执行处理程序后afterCompletion(..)
:完成完整的请求后
preHandle(..)
方法返回一个布尔值。您可以使用此方法来中断或继续执行链的处理。当此方法返回true
时,处理程序执行链继续。当它返回false
时,DispatcherServlet
假定拦截器本身已经处理了请求(例如,渲染了适当的视图),并且不会继续执行执行链中的其他拦截器和实际处理程序。
有关如何配置拦截器的示例,请参见MVC配置部分中的拦截器。 您还可以通过使用各个HandlerMapping
实现上的设置器直接注册它们。
请注意,对于@ResponseBody
和ResponseEntity
方法,postHandle
的用处不大,在HandlerAdapter
内和postHandle
之前,将其写入并提交响应。 这意味着对响应进行任何更改为时已晚,例如添加额外的标头。 对于这种情况,您可以实现ResponseBodyAdvice
并将其声明为Controller Advice Bean或直接在RequestMappingHandlerAdapter
上对其进行配置。
1.1.7。异常
如果在请求映射期间发生异常或从请求处理程序(例如@Controller
)抛出异常,则将DispatcherServlet
委托委托给HandlerExceptionResolver
Bean 链以解决异常并提供替代处理,通常是错误响应。
下表列出了可用的HandlerExceptionResolver
实现:
HandlerExceptionResolver |
描述 |
---|---|
SimpleMappingExceptionResolver |
异常类名称和错误视图名称之间的映射。对于在浏览器应用程序中呈现错误页面很有用。 |
DefaultHandlerExceptionResolver |
解决Spring MVC引发的异常,并将其映射到HTTP状态代码。另请参见替代ResponseEntityExceptionHandler 和REST API异常。 |
ResponseStatusExceptionResolver |
使用@ResponseStatus 注释解决异常,并根据注释中的值将其映射到HTTP状态代码。 |
ExceptionHandlerExceptionResolver |
通过调用@Controller 或@ControllerAdvice 类中的@ExceptionHandler 方法来解决异常。 请参见@ExceptionHandler 方法。 |
解析器链
您可以通过在Spring配置中声明多个HandlerExceptionResolver
bean并根据需要设置它们的order
属性来形成异常解析器链。order
属性越高,异常解析器的定位就越晚。
org.springframework.web.servlet.DispatcherServlet#initHandlerExceptionResolvers
如果没有自定义
HandlerExceptionResolver
bean,则异常解析器链中是在DispatcherServlet.properties
中定义的: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
HandlerExceptionResolver
的约定指定它可以返回:
-
指向错误视图的
ModelAndView
。 -
如果在解析程序中处理了异常,则为空的
ModelAndView
。 -
如果该异常仍未解决,则为
null
,以供后续解析器尝试;如果该异常仍在末尾,则允许将其冒泡到Servlet容器。
MVC Config自动为默认的Spring MVC异常,@ResponseStatus
注释的异常以及对@ExceptionHandler
方法的支持声明内置的解析器。 您可以自定义该列表或替换它。
容器错误页面
如果所有HandlerExceptionResolver
仍然无法解决异常,该异常可以传播,或者如果响应状态设置为错误状态(即4xx
,5xx
),则Servlet容器可以在HTML中呈现默认错误页面。要自定义容器的默认错误页面,可以在web.xml
中声明错误页面映射。以下示例显示了如何执行此操作:
<error-page>
<location>/error</location>
</error-page>
给定前面的示例,当异常冒出气泡或响应具有错误状态时,Servlet容器在容器内向配置的URL(例如/error
)进行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
提供视图名称和实际视图之间的映射。View
在移交给特定的视图技术之前,先解决数据准备问题。
下表提供了有关ViewResolver
层次结构的更多详细信息:
表3. ViewResolver
实现
ViewResolver | 描述 |
---|---|
AbstractCachingViewResolver |
AbstractCachingViewResolver 的子类缓存它们解析的视图实例。 缓存可以提高某些视图技术的性能。 您可以通过将cache 属性设置为false 来关闭缓存。 此外,如果必须在运行时刷新某个视图(例如,当修改FreeMarker模板时),则可以使用removeFromCache(String viewName,Locale loc) 方法。 |
XmlViewResolver |
ViewResolver 的实现接受一个用XML编写的配置文件,该配置文件的DTD与Spring的XML bean工厂相同。默认配置文件是 /WEB-INF/views.xml 。 |
ResourceBundleViewResolver |
ViewResolver 的实现,该实现使用ResourceBundle 中的bean定义(由包基本名称指定)。对于应该解析的每个视图,它将属性的值[viewname].(class) 用作视图类,并将属性的值[viewname].url 用作视图URL。您可以在View Technologies一章中找到示例 。 |
UrlBasedViewResolver |
ViewResolver 接口的简单实现会影响将逻辑视图名称直接解析为URL而没有显式映射定义。如果您的逻辑名称以直接的方式与视图资源的名称匹配,而无需任意映射,则这是适当的。 |
InternalResourceViewResolver |
UrlBasedViewResolver 的方便子类,支持InternalResourceView (实际上,Servlet和JSP)和子类,如JstlView 和TilesView 。您可以使用setViewClass(..) 来为此解析器生成的所有视图指定视图类。有关UrlBasedViewResolver 详细信息,请参见javadoc。 |
FreeMarkerViewResolver |
UrlBasedViewResolver 的方便的子类支持FreeMarkerView 和他们的自定义子类。 |
ContentNegotiatingViewResolver |
基于请求文件名或Accept 头解析视图的ViewResolver 接口的实现。请参阅内容协商。 |
初始化:
org.springframework.web.servlet.DispatcherServlet#initViewResolvers
DispatcherServlet.properties
中定义的:org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
调用:
org.springframework.web.servlet.DispatcherServlet#resolveViewName
处理方式(Handling)
您可以通过声明多个解析器bean,并在必要时通过设置order
属性以指定顺序来链接视图解析器。请记住,order
属性越高,视图解析器在链中的定位就越晚。
ViewResolver
的协定指定它可以返回null
来指示找不到该视图。 但是,对于JSP和InternalResourceViewResolver
,找出JSP是否存在的唯一方法是通过RequestDispatcher
进行调度。 因此,您必须始终将InternalResourceViewResolver
配置为在视图解析器的总体顺序中排在最后。
配置视图解析就像将ViewResolver
bean添加到Spring配置中一样简单。 MVC Config为View解析器和添加无逻辑的View Controller提供了专用的配置API,这对于无控制器逻辑的HTML模板呈现非常有用。
重定向
视图名称中的特殊 redirect:
前缀使您可以执行重定向。 UrlBasedViewResolver
(及其子类)将其识别为需要重定向的指令。 视图名称的其余部分是重定向URL。
org.springframework.web.servlet.view.UrlBasedViewResolver#createView
最终效果与控制器返回RedirectView
的效果相同,但是现在控制器本身可以根据逻辑视图名称进行操作。 逻辑视图名称(如redirect:/myapp/some/resource
)相对于当前Servlet上下文进行重定向,而名称如redirect:https://myhost.com/some/arbitrary/path
则重定向至绝对URL。
请注意,如果使用@ResponseStatus
注释控制器方法,则注释值优先于RedirectView
设置的响应状态。
转发
您还可以对视图名称使用特殊的forward:
前缀,这些视图名称最终由UrlBasedViewResolver
和子类解析。这将创建一个 InternalResourceView
,并执行一个RequestDispatcher.forward()
。因此,此前缀在InternalResourceViewResolver
和InternalResourceView
中(对于JSP)没有用,但是如果您使用另一种视图技术,但仍然希望强制转发由Servlet/JSP引擎处理的资源,则该前缀很有用。请注意,您也可以链接多个视图解析器。
内容协商
ContentNegotiatingViewResolver
不会解析视图本身,而是委托其他视图解析器,并选择类似于客户端请求的表示形式的视图。可以从Accept
标头或查询参数(例如"/path?format=pdf"
)中确定表示形式。
ContentNegotiatingViewResolver
通过将请求媒体类型与与其每个ViewResolver
关联的View
支持的媒体类型(也称为Content-Type
)进行比较,从而选择合适的View
处理该请求。 列表中具有兼容Content-Type
的第一个View
将表示形式返回给客户端。 如果ViewResolver
链无法提供兼容的视图,请查阅通过DefaultViews
属性指定的视图列表。 后一个选项适用于单例Views
,无论逻辑视图名称如何,该视图都可以呈现当前资源的适当表示形式。 Accept
标头可以包含通配符(例如text/*
),在这种情况下,其Content-Type
为text/xml
的View
是兼容的匹配。
有关配置详细信息,请参见MVC Config下的查看解析器。
1.1.9. Locale
正如Spring Web MVC框架所做的那样,Spring体系结构的大多数部分都支持国际化。DispatcherServlet
使您可以使用客户端的语言环境自动解析消息。这是通过LocaleResolver
对象完成的。
收到请求时,DispatcherServlet
将查找一个语言环境解析器,如果找到一个,它将尝试使用它来设置语言环境。通过使用RequestContext.getLocale()
方法,您始终可以检索由语言环境解析器解析的语言环境。
除了自动的语言环境解析之外,您还可以在处理程序映射上附加一个拦截器(有关处理程序映射拦截器的更多信息,请参见拦截),以在特定情况下(例如,基于请求中的参数)更改语言环境。
语言环境解析器和拦截器在org.springframework.web.servlet.i18n
程序包中定义, 并以常规方式在应用程序上下文中配置。Spring包含以下选择的语言环境解析器。
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
时区
除了获取客户的语言环境外,了解其时区通常也很有用。 LocaleContextResolver
接口提供了LocaleResolver
的扩展,该扩展使解析程序可以提供更丰富的LocaleContext
,其中可能包含时区信息。
如果可用,则可以使用RequestContext.getTimeZone()
方法获取用户的TimeZone
。 通过Spring的ConversionService
注册的任何日期/时间转换器和格式器对象都会自动使用时区信息。
标头解析器
此语言环境解析器检查accept-language
客户端(例如,Web浏览器)发送的请求中的标头。通常,此头字段包含客户端操作系统的语言环境。请注意,此解析器不支持时区信息。
Cookie解析器
该语言环境解析器检查客户端上可能存在的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
的属性:
表4. CookieLocaleResolver
属性
属性 | 默认 | 描述 |
---|---|---|
cookieName |
类名+ LOCALE | Cookie的名称 |
cookieMaxAge |
Servlet容器默认 | Cookie在客户端上保留的最长时间。如果指定-1 ,则cookie将不会保留。它仅在客户端关闭浏览器之前可用。 |
cookiePath |
/ | 将Cookie的可见性限制在您网站的特定部分。当cookiePath 被指定,cookie是仅对于该路径和它下面的路径可见。 |
会话解析器
SessionLocaleResolver
允许您从可能与用户的请求相关的会话中检索Locale
和TimeZone
。不同于CookieLocaleResolver
,此策略将本地选择的语言环境设置存储在Servlet容器的HttpSession
中。结果,这些设置对于每个会话都是临时的,因此在每个会话终止时会丢失。
请注意,与外部会话管理机制(例如Spring Session项目)没有直接关系。 该SessionLocaleResolver
针对当前HttpServletRequest
评估并修改相应的HttpSession
属性。
区域拦截器
您可以通过将LocaleChangeInterceptor
添加到HandlerMapping
定义之一来启用语言环境更改。 它在请求中检测到一个参数,并相应地更改语言环境,在调度程序的应用程序上下文中在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>
1.1.10. Themes
您可以应用Spring Web MVC框架主题来设置应用程序的整体外观,从而增强用户体验。主题是静态资源(通常是样式表和图像)的集合,这些资源会影响应用程序的视觉样式。
定义主题
要在Web应用程序中使用主题,您必须设置org.springframework.ui.context.ThemeSource
接口的实现。 WebApplicationContext
接口扩展了ThemeSource
,但将其职责委托给专用的实现。 默认情况下,委托是org.springframework.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
使用一个空的基本名称前缀。 结果,从类路径的根加载属性文件。 因此,您可以将cool.properties
主题定义放在类路径的根目录中(例如,在/WEB-INF/classes
中)。 ResourceBundleThemeSource
使用标准的Java资源束加载机制,允许主题的完全国际化。 例如,我们可以有一个/WEB-INF/classes/cool_nl.properties
,它引用带有荷兰文字的特殊背景图像。
解析主题
定义主题后,如上一节所述,您可以决定要使用哪个主题。 DispatcherServlet
寻找一个名为themeResolver
的bean,以找出要使用的ThemeResolver
实现。 主题解析器的工作方式与LocaleResolver
几乎相同。 它可以检测用于特定请求的主题,还可以更改请求的主题。 下表描述了Spring提供的主题解析器:
表5. ThemeResolver
实现
类 | 描述 |
---|---|
FixedThemeResolver |
选择通过使用defaultThemeName 属性设置的固定主题。 |
SessionThemeResolver |
主题在用户的HTTP会话中维护。每个会话只需设置一次,但在会话之间不会保留。 |
CookieThemeResolver |
所选主题存储在客户端的cookie中。 |
Spring还提供了一个ThemeChangeInterceptor
允许使用简单的请求参数对每个请求进行主题更改的功能。
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
1.1.11。Multipart 解析器
org.springframework.web.multipart
包中的MultipartResolver
是一种用于解析包括文件上传在内的 Multipart 请求的策略。 有一种基于Commons FileUpload的实现,另一种基于Servlet 3.0多部分请求解析。
要启用 Multipart 处理,您需要在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容器配置启用Servlet 3.0 multipart 解析。 为此:
-
在Java中,在Servlet注册上设置
MultipartConfigElement
。 -
在
web.xml
中,将<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配置到位后,您可以添加名称为multipartResolver
的StandardServletMultipartResolver
类型的Bean。
1.1.12. Logging
Spring MVC中的DEBUG
级别的日志被设计为紧凑,最少且人性化的。 它侧重于一遍又一遍有用的高价值信息,而其他信息仅在调试特定问题时才有用。
TRACE
级别的日志记录通常遵循与DEBUG
相同的原理(例如,也不应是消防水带),但可用于调试任何问题。 此外,某些日志消息在TRACE
和DEBUG
上可能显示不同级别的详细信息。
良好的日志记录来自使用日志的经验。如果您发现任何不符合既定目标的东西,请告诉我们。
敏感数据
调试和跟踪日志记录可能会记录敏感信息。 这就是默认情况下屏蔽请求参数和标头,并且必须通过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
来拦截内容类型为application/x-www-form-urlencoded
的HTTP PUT
,PATCH
和DELETE
请求,从请求主体中读取表单数据,并包装ServletRequest
以使表单数据可通过ServletRequest.getParameter *()
方法族获得。
1.2.2。转发的表头
当请求通过代理(例如负载平衡器)进行处理时,主机,端口和 scheme 可能会更改,这使得从客户端角度创建指向正确的主机,端口和 scheme 的链接带来了挑战。
RFC 7239定义了代理可以用来提供有关原始请求的信息的Forwarded
HTTP标头。还有其他一些非标准标头,也包括X-Forwarded-Host
,X-Forwarded-Port
, X-Forwarded-Proto
,X-Forwarded-Ssl
,和X-Forwarded-Prefix
。
ForwardedHeaderFilter
是一个Servlet过滤器,它根据Forwarded
标头修改请求的主机,端口和方案,然后删除这些标头。
对于转发的标头,出于安全方面的考虑,因为应用程序无法知道标头是由代理添加的,还是由恶意客户端添加的。这就是为什么应配置位于信任边界的代理以删除来自外部的不受信任的Forwarded
标头的原因。您也可以用removeOnly=true
配置ForwardedHeaderFilter
,在这种情况下,它会删除但不使用标题。
1.2.3. Shallow ETag
ShallowEtagHeaderFilter
过滤器通过缓存写入响应的内容并从中计算MD5哈希值来创建“浅” ETag。 客户端下次发送时,将执行相同的操作,但还会将计算值与If-None-Match
请求标头进行比较,如果两者相等,则返回304
(NOT_MODIFIED)。
此策略可节省网络带宽,但不会节省CPU,因为必须为每个请求计算完整响应。 如前所述,控制器级别的其他策略可以避免计算。 请参阅HTTP缓存。
该过滤器具有writeWeakETag
参数,该参数将过滤器配置为写入弱ETag,类似于以下内容:W/"02a2d595e6ed9a0b24f027f2b63b134d6"
(在RFC 7232第2.3节中定义)。
1.2.4。CORS
Spring MVC通过控制器上的注释为CORS配置提供了细粒度的支持。 但是,当与Spring Security一起使用时,我们建议您依赖内置的CorsFilter
,该CorsFilter
排序必须在Spring Security的过滤器链之前。
有关更多详细信息,请参见有关CORS和CORS过滤器的部分。
1.3。带注释的控制器
Spring MVC提供了一个基于注释的编程模型,其中@Controller
和@RestController
组件使用注释来表达请求映射,请求输入,异常处理等。 带注释的控制器具有灵活的方法签名,无需扩展基类或实现特定的接口。 以下示例显示了由注释定义的控制器:
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
在前面的示例中,该方法接受Model
并以String
的形式返回视图名称,但是还存在许多其他选项,本章稍后将对其进行说明。
spring.io 上的指南和教程使用本节中描述的基于注释的编程模型。
1.3.1。声明
您可以使用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
是一个组合的注解,其本身使用@Controller
和@ResponseBody
进行了元注释,以指示其每个方法都继承类型级别@ResponseBody
注解的控制器,因此,将其直接写入响应主体(与视图解析相对应)并使用 HTML模板。
AOP代理
在某些情况下,您可能需要在运行时用AOP代理装饰控制器。 一个示例是,如果您选择直接在控制器上具有@Transactional
注解。 在这种情况下,特别是对于控制器,我们建议使用基于类的代理。 这通常是控制器的默认选择。 但是,如果控制器必须实现不是Spring Context回调的接口(例如InitializingBean
,*Aware
等),则可能需要显式配置基于类的代理。 例如,使用<tx:annotation-driven />
可以更改为<tx:annotation-driven proxy-target-class="true"/>
,使用@EnableTransactionManagement
可以更改为@EnableTransactionManagement(proxyTargetClass = true)
。
1.3.2。请求映射
您可以使用@RequestMapping
注解将请求映射到控制器方法。 它具有各种属性,可以通过URL,HTTP方法,请求参数,标头和媒体类型进行匹配。 您可以在类级别使用它来表示共享的映射,也可以在方法级别使用它来缩小到特定的端点映射。
@RequestMapping
还有HTTP方法特定的快捷方式:
@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
,等)默认支持,你可以注册任何其它数据类型的支持。请参阅类型转换和DataBinder
。
您可以显式命名URI变量(例如@PathVariable("customId")
),但是如果名称相同并且您的代码是通过调试信息或-parameters
Java 8上的编译器标志进行编译的,则可以省去该细节。
语法{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使用spring-core中的PathMatcher协定和AntPathMatcher实现来进行URI路径匹配。
模式比较
当多个模式与URL匹配时,必须将它们进行比较以找到最佳匹配。这是通过使用AntPathMatcher.getPatternComparator(String path)
来完成的,该工具查找更具体的模式。
如果模式的URI变量(计数为1),单通配符(计数为1)和双通配符(计数为2)的数量较少,则模式的含义不太明确。 给定相等的分数,则选择更长的模式。 给定相同的分数和长度,将选择URI变量比通配符更多的模式。
默认映射模式(/**
)从评分中排除,并且始终排在最后。另外,前缀模式(例如/public/**
)被认为比没有双通配符的其他模式更具体。
有关完整的详细信息,请参见AntPatternComparator
中AntPathMatcher
,也请记住,你可以自定义PathMatcher
的实现。请参阅配置部分中的路径匹配。
后缀匹配
默认情况下,Spring MVC执行.*
后缀模式匹配,以便映射到/person
的控制器也隐式映射到/person.*
。 然后,文件扩展名用于解释请求的内容类型以用于响应(即,代替Accept
标头),例如/person.pdf
、/person.xml
等。
当浏览器用来发送难以一致解释的Accept
标头时,以这种方式使用文件扩展名是必要的。 目前,这已不再是必须的,使用Accept
标头应该是首选。
随着时间的流逝,文件扩展名的使用已经以各种方式证明是有问题的。 当使用URI变量,路径参数和URI编码进行覆盖时,可能会引起歧义。 关于基于URL的授权和安全性的推理(请参阅下一部分以了解更多详细信息)也变得更加困难。
若要完全禁用文件扩展名,必须设置以下两项:
useSuffixPatternMatching(false)
,请参阅PathMatchConfigurerfavorPathExtension(false)
,请参阅ContentNegotiationConfigurer
基于URL的内容协商仍然有用(例如,在浏览器中键入URL时)。 为此,我们建议使用基于查询参数的策略,以避免文件扩展名附带的大多数问题。 或者,如果必须使用文件扩展名,请考虑通过 ContentNegotiationConfigurer
的mediaTypes
属性将它们限制为显式注册的扩展名列表。
后缀匹配和RFD
反射文件下载(RFD)攻击与XSS相似,因为它依赖反映在响应中的请求输入(例如,查询参数和URI变量)。 但是,RFD攻击不是将JavaScript插入HTML,而是依靠浏览器切换来执行下载,并在以后双击时将响应视为可执行脚本。
在Spring MVC中,@ResponseBody
和ResponseEntity
方法是有风险的,因为它们可以呈现不同的内容类型,客户端可以通过URL路径扩展要求。禁用后缀模式匹配并使用路径扩展进行内容协商可以降低风险,但不足以防止RFD攻击。
为了防止RFD攻击,Spring MVC在呈现响应主体之前添加了 Content-Disposition:inline;filename=f.txt
标头,以建议固定和安全的下载文件。仅当URL路径包含既未列入白名单也未明确注册用于内容协商的文件扩展名时,才执行此操作。但是,当直接在浏览器中键入URL时,它可能会产生副作用。
默认情况下,许多常见路径扩展名都列入了白名单。具有自定义HttpMessageConverter
实现的应用程序 可以显式注册文件扩展名以进行内容协商,以避免为这些扩展名添加Content-Disposition
头。请参阅内容类型。
有关RFD的其他建议,请参见CVE-2015-5211。
Consumable Media Types
您可以根据请求的Content-Type
来缩小请求映射,如以下示例所示:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
使用consumes
属性按内容类型缩小映射。
consumes
属性还支持否定表达式-例如,!text/plain
表示text/plain
以外的任何内容类型。
您可以在类级别声明共享consumes
属性。但是,与大多数其他请求映射属性不同,在类级别使用时,方法级别的consumes
属性将覆盖而不是扩展类级别的声明。
MediaType
提供常用媒体类型(例如APPLICATION_JSON_VALUE
和APPLICATION_XML_VALUE
)的常量 。
Producible Media Types
您可以根据Accept
请求标头和控制器方法生成的内容类型列表来缩小请求映射,如以下示例所示:
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
使用produces
属性按内容类型缩小映射。
媒体类型可以指定字符集。支持否定表达式-例如, !text/plain
表示除 text/plain
之外的任何内容类型。
您可以在类级别声明共享produces
属性。但是,与大多数其他请求映射属性不同,在类级别使用时,方法级别的produces
属性将覆盖而不是扩展类级别的声明。
MediaType
提供常用媒体类型(例如APPLICATION_JSON_VALUE
和APPLICATION_XML_VALUE
)的常量 。
Parameters, headers
您可以根据请求参数条件来缩小请求映射。 您可以测试是否存在请求参数(myParam
),是否存在一个请求参数(!myParam
)或特定值(myParam = myValue
)。 以下示例显示如何测试特定值:
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
测试myParam
是否相等myValue
。
您还可以将其与请求标头条件一起使用,如以下示例所示:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
您可以匹配Content-Type
并Accept
与标头条件匹配,但最好使用 消耗和生产 。
HTTP HEAD, OPTIONS
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)透明支持HTTP HEAD以进行请求映射。控制器方法不需要更改。应用于javax.servlet.http.HttpServlet
的响应包装器确保将Content-Length
标头设置为写入的字节数(实际上未写入响应)。
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)被隐式映射到并支持HTTP HEAD。像处理HTTP GET一样处理HTTP HEAD请求,不同的是,不是写入正文,而是计算字节数并设置Content-Length
标头。
默认情况下,通过将Allow
响应标头设置为所有具有匹配URL模式的@RequestMapping
方法中列出的HTTP方法列表来处理HTTP OPTIONS
。
对于@RequestMapping
不使用HTTP方法声明的情况,Allow
标头设置为 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
。控制器方法应该总是声明支持HTTP方法(例如,通过使用HTTP方法具体变体: @GetMapping
,@PostMapping
,及其他)。
您可以将@RequestMapping
方法显式映射到HTTP HEAD
和HTTP OPTIONS
,但这在通常情况下不是必需的。
自定义注释
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) throws NoSuchMethodException {
// 准备请求映射元数据。
RequestMappingInfo info = RequestMappingInfo .paths("/user/{id}").methods(RequestMethod.GET).build();
// 获取处理程序方法。
Method method = UserHandler.class.getMethod("getUser", Long.class);
// 添加注册。
mapping.registerMapping(info, handler, method);
}
}
1.3.3。处理程序方法(Handler Methods)
@RequestMapping
处理程序方法具有灵活的签名,可以从支持的控制器方法参数和返回值的范围中进行选择。
方法参数
下表描述了受支持的控制器方法参数。 任何参数均不支持响应式类型。
支持JDK 8的java.util.Optional
作为方法参数,并与具有必需属性(例如,@RequestParam
,@RequestHeader
等)的注释结合在一起,等效于required = false
。
控制器方法参数 | 描述 |
---|---|
WebRequest , NativeWebRequest |
通用访问请求参数以及请求和会话属性,而无需直接使用Servlet API。 |
javax.servlet.ServletRequest , javax.servlet.ServletResponse |
选择任何特定的请求或响应类型-例如ServletRequest ,HttpServletRequest 或Spring的MultipartRequest ,MultipartHttpServletRequest 。 |
javax.servlet.http.HttpSession |
强制会话的存在。 结果,这样的参数永远不会为空。 请注意,会话访问不是线程安全的。 如果允许多个请求并发访问会话,请考虑将RequestMappingHandlerAdapter 实例的syncnizeOnSession 标志设置为true 。 |
javax.servlet.http.PushBuilder |
用于程序化 HTTP/2 资源推送的Servlet 4.0 推送构建器API。请注意,根据Servlet规范,如果客户端不支持HTTP/2 功能,则注入的PushBuilder 实例可以为null 。 |
java.security.Principal |
当前经过身份验证的用户-可能是特定的Principal 实现类(如果已知)。 |
HttpMethod |
请求的HTTP方法。 |
java.util.Locale |
当前的请求语言环境,由最可用的特定LocaleResolver (实际上是配置的LocaleResolver 或LocaleContextResolver )确定。 |
java.util.TimeZone + java.time.ZoneId |
与当前请求关联的时区,由LocaleContextResolver 决定。 |
java.io.InputStream , java.io.Reader |
用于访问Servlet API公开的原始请求正文。 |
java.io.OutputStream , java.io.Writer |
用于访问Servlet API公开的原始响应正文。 |
@PathVariable |
用于访问URI模板变量。请参阅URI模式。 |
@MatrixVariable |
用于访问URI路径段中的名称/值对。请参阅矩阵变量。 |
@RequestParam |
用于访问Servlet请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。参见@RequestParam 以及Multipart。请注意,对于简单参数值,使用 @RequestParam 是可选的。请参阅此表末尾的“其他任何参数”。 |
@RequestHeader |
用于访问请求标头。标头值将转换为声明的方法参数类型。请参阅@RequestHeader 。 |
@CookieValue |
用于访问cookie。Cookies值将转换为声明的方法参数类型。请参阅@CookieValue 。 |
@RequestBody |
用于访问HTTP请求正文。正文内容通过使用HttpMessageConverter 实现转换为声明的方法参数类型。请参阅@RequestBody 。 |
HttpEntity |
用于访问请求标头和正文。正文用HttpMessageConverter 转换。参见HttpEntity。 |
@RequestPart |
要访问multipart/form-data 请求中的文件,使用HttpMessageConverter 来转换文件的主体。参见多部分。 |
java.util.Map ,org.springframework.ui.Model ,org.springframework.ui.ModelMap |
用于访问HTML控制器中使用的模型,并作为视图渲染的一部分公开给模板。 |
RedirectAttributes |
指定在重定向的情况下使用的属性(即追加到查询字符串中),并指定要临时存储的属性,直到重定向后的请求为止。请参阅重定向属性和Flash属性。 |
@ModelAttribute |
用于访问应用了数据绑定和验证的模型中的现有属性(如果不存在,则进行实例化)。 请参见@ModelAttribute 以及Model和DataBinder 。请注意, @ModelAttribute 的使用是可选的(例如,设置其属性)。 请参阅此表末尾的“其他任何参数”。 |
Errors , BindingResult |
用于访问来自命令对象的验证和数据绑定(即@ModelAttribute 参数)的错误或来自@RequestBody 或@RequestPart 参数的验证的错误。 您必须在经过验证的方法参数之后立即声明一个Errors 或BindingResult 参数。 |
SessionStatus + 类级别 @SessionAttributes |
为了标记表单处理完成,将触发清除通过类级别@SessionAttributes 注释声明的会话属性。 有关更多详细信息,请参见@SessionAttributes 。 |
UriComponentsBuilder |
用于准备相对于当前请求的主机,端口,方案,上下文路径以及servlet映射的文字部分的URL。请参阅URI链接。 |
@SessionAttribute |
对于访问任何会话属性,与通过类级别@SessionAttributes 声明存储在会话中的模型属性相反。 有关更多详细信息,请参见@SessionAttribute 。 |
@RequestAttribute |
用于访问请求属性。请参阅@RequestAttribute 以获取更多详细信息。 |
任何其他论点 | 如果方法参数不与此表中的任何较前的值匹配,并且是简单类型(由BeanUtils#isSimpleProperty确定 ,则将其解析为@RequestParam 。否则,将其解析为@ModelAttribute 。 |
为控制器上的参数赋值
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
默认支持的参数类型解析器:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers
返回值
下表描述了受支持的控制器方法返回值。所有返回值都支持反应性类型。
控制器方法返回值 | 描述 |
---|---|
@ResponseBody |
返回值通过HttpMessageConverter 实现进行转换并写入响应。请参阅@ResponseBody 。 |
HttpEntity<B> , ResponseEntity<B> |
指定完整响应(包括HTTP标头和正文)的返回值将通过HttpMessageConverter 实现进行转换,并写入响应中。参见ResponseEntity。 |
HttpHeaders |
用于返回带标头没有主体内容的响应。 |
String |
一个视图名称,将通过ViewResolver 实现来解析,并与隐式模型一起使用-通过命令对象和@ModelAttribute 方法确定。处理程序方法还可以通过声明Model 参数来以编程方式丰富模型(请参见Explicit Registrations)。 |
View |
用于与隐式模型一起渲染的View 实例,该隐式模型是通过命令对象和@ModelAttribute 方法确定的。 处理程序方法还可以通过声明Model 参数来以编程方式丰富模型(请参阅Explicit Registrations)。 |
java.util.Map , org.springframework.ui.Model |
要添加到隐式模型的属性,视图名称通过RequestToViewNameTranslator 隐式确定。 |
@ModelAttribute |
要添加到模型的属性,视图名称通过RequestToViewNameTranslator 隐式确定。请注意, @ModelAttribute 是可选的。请参阅此表末尾的“其他任何返回值”。 |
ModelAndView 对象 |
要使用的视图和模型属性,以及响应状态(可选)。 |
void |
如果返回类型为void (或返回值为null )的方法还具有ServletResponse ,OutputStream 参数或@ResponseStatus 批注,则认为该方法已完全处理了响应。 如果控制器进行了肯定的ETag 或lastModified 时间戳检查,也是如此(请参阅控制器以获取详细信息)。如果以上条件都不成立,则 void 返回类型还可以为REST控制器指示“无响应正文”,或者为HTML控制器指示默认视图名称选择。 |
DeferredResult<V> |
从任何线程异步生成任何上述返回值-例如,由于某些事件或回调的结果。请参见异步请求和DeferredResult 。 |
Callable<V> |
在Spring MVC管理的线程中异步产生上述任何返回值。请参见异步请求和Callable 。 |
ListenableFuture<V> , java.util.concurrent.CompletionStage<V> , java.util.concurrent.CompletableFuture<V> |
为方便起见,替代DeferredResult ,(例如,当基础服务返回其中之一时)。 |
ResponseBodyEmitter , SseEmitter |
异步发出对象流,以将其写入 HttpMessageConverter 实现中。也支持作为ResponseEntity 的主体。请参阅异步请求和HTTP流。 |
StreamingResponseBody |
异步写入OutputStream 响应。也支持作为ResponseEntity 的主体 。请参阅异步请求和HTTP流。 |
反应类型-Reactor,RxJava或其他类型 ReactiveAdapterRegistry |
DeferredResult 的替代方法,其中包含收集到List 的多值流(例如Flux ,Observable )。对于流场景(例如, text/event-stream , application/json+stream ),使用SseEmitter 和ResponseBodyEmitter 代替,其中在Spring MVC管理的线程上执行ServletOutputStream 阻塞I/O,并在完成时施加反压每个写入。请参阅异步请求和响应类型。 |
任何其他返回值 | 如果返回值不是由BeanUtils#isSimpleProperty 确定的简单类型,且它与该表中的任何较早值都不匹配且为String 或void 的任何返回值均被视为视图名称(通过RequestToViewNameTranslator 选择默认视图名称) 。 简单类型的值仍然无法解析。 |
类型转换
如果参数声明为String
以外的形式,则某些表示基于String
的请求输入的带注释的控制器方法参数(例如@RequestParam
,@RequestHeader
,@PathVariable
,@MatrixVariable
和@CookieValue
)可能需要类型转换。
在这种情况下,将根据配置的转换器自动应用类型转换。 默认情况下,支持简单类型(int
,long
,Date
和其他)。 您可以通过WebDataBinder
(请参见DataBinder
)或通过在FormattingConversionService
中注册Formatter
来自定义类型转换。 参见Spring字段格式。
矩阵变量
RFC 3986讨论了路径段中的名称/值对。在Spring MVC中,我们根据Tim Berners-Lee 的“old post”将其称为“矩阵变量” ,但它们也可以称为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
}
可以将矩阵变量定义为可选变量,并指定默认值,如以下示例所示:
// 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" />
。
代码示例:
@Configuration
@EnableWebMvc
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUrlPathHelper(urlPathHelper());
}
@Bean
public UrlPathHelper urlPathHelper() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
return urlPathHelper;
}
}
@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.Optional
包装器声明参数来指定方法参数是可选的。
如果目标方法参数类型不是String
,则类型转换将自动应用。请参阅类型转换。
将参数类型声明为数组或List
可允许解析同一参数名称的多个参数值。
如果将@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) {
//...
}
如果目标方法的参数类型不是String
,则将 自动应用类型转换。请参阅类型转换。
当@RequestHeader
注解上的使用Map
, MultiValueMap
或HttpHeaders
参数,则Map
被填充有所有标头值。
内置支持可用于将逗号分隔的字符串转换为数组或字符串集合或类型转换系统已知的其他类型。 例如,用@RequestHeader("Accept") 注释的方法参数可以是
String
类型,也可以是String []
或List <String>
。
@CookieValue
您可以使用@CookieValue
注释将HTTP cookie的值绑定到控制器中的方法参数。
考虑带有以下cookie的请求:
JSESSIONID = 415A4AC178C59DACE0B2C9CA727CDD84
以下示例显示如何获取cookie值:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
如果目标方法的参数类型不是String
,则类型转换将自动应用。请参阅类型转换。
@ModelAttribute
您可以在方法参数上使用@ModelAttribute
注释,以从模型访问属性,或将其实例化(如果不存在)。model属性还覆盖了名称与字段名称匹配的HTTP Servlet请求参数中的值。这被称为数据绑定,它使您不必处理解析和转换单个查询参数和表单字段的工作。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
上面的Pet
实例解析如下:
- 来自模型(如果已使用Model添加)。
- 来自HTTP会话,通过使用
@SessionAttributes
。 - 来自URI路径变量,通过
Converter
传递(请参见下一个示例)。 - 来自默认构造函数的调用。
- 通过调用具有与Servlet请求参数匹配的参数的“主要构造函数”。 参数名称是通过JavaBeans
@ConstructorProperties
或字节码中运行时保留的参数名称确定的。
尽管通常使用模型来用属性填充模型,但另一种替代方法是依赖于Converter <String,T>
与URI路径变量约定结合使用。 在以下示例中,模型属性名称account
与URI路径变量account
匹配,并且通过将String
帐号传递给已注册的Converter <String,Account>
来加载Account:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
获取模型属性实例后,将应用数据绑定。 WebDataBinder
类将Servlet请求参数名称(查询参数和表单字段)与目标Object
上的字段名称进行匹配。 必要时在应用类型转换后填充匹配字段。 有关数据绑定(和验证)的更多信息,请参见验证。 有关自定义数据绑定的更多信息,请参见DataBinder
。
数据绑定可能会导致错误。 默认情况下,引发BindException
。 但是,要检查控制器方法中的此类错误,可以在@ModelAttribute
旁边立即添加BindingResult
参数,如以下示例所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
在某些情况下,您可能希望访问没有数据绑定的模型属性。对于这种情况,您可以将Model
注入到控制器中并直接访问它,或者设置@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.Valid
注释或Spring的@Validated
注释( Bean验证和 Spring验证)在数据绑定之后自动应用验证 。以下示例显示了如何执行此操作:
@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")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete();
// ...
}
}
代码示例:
@SessionAttributes("myIdx")
@RestController
public class MyController {
@ModelAttribute("myIdx")
public String myId() {
return UUID.randomUUID().toString();
}
@GetMapping("myId2")
public void myId2(HttpSession session, SessionStatus status) {
System.out.println("1:" + session.getAttribute("myIdx"));
}
}
@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 Filter
或HandlerInterceptor
):
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
重定向属性
默认情况下,所有模型属性都被视为在重定向URL中作为URI模板变量公开。 在其余属性中,那些属于原始类型或原始类型的集合或数组的属性会自动附加为查询参数。
如果专门为重定向准备了模型实例,则将原始类型属性作为查询参数附加可能是理想的结果。 但是,在带注释的控制器中,模型可以包含为渲染目的添加的其他属性(例如,下拉字段值)。 为了避免此类属性出现在URL中的可能性,@RequestMapping
方法可以声明RedirectAttributes
类型的参数,并使用它来指定可用于RedirectView
的确切属性。 如果该方法确实重定向,则使用RedirectAttributes
的内容。 否则,将使用模型的内容。
RequestMappingHandlerAdapter
提供了一个名为ignoreDefaultModelOnRedirect
的标志,您可以使用该标志指示如果控制器方法重定向,则绝不要使用默认Model
的内容。 相反,控制器方法应声明一个RedirectAttributes
类型的属性,或者,如果没有声明,则不应将任何属性传递给RedirectView
。 MVC名称空间和MVC Java配置都将此标志设置为false
,以保持向后兼容性。 但是,对于新应用程序,我们建议将其设置为true
。
请注意,展开重定向URL时,当前请求中的URI模板变量会自动变为可用,而您无需通过Model
或RedirectAttributes
显式添加它们。 以下示例显示了如何定义重定向:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
将数据传递到重定向目标的另一种方法是使用 flash 属性。与其他重定向属性不同,flash 属性保存在HTTP会话中(因此不会出现在URL中)。有关更多信息,请参见Flash属性。
代码示例
@Controller
public class MyController {
@PostMapping("/files/{path}")
public String upload(@PathVariable("path")String path) {
// ...
System.out.println("study.hwj.myspringmvc.MyController.upload");
return "redirect:/files/my/{path}";
}
@RequestMapping("/files/my/{path}")
public void mupload(@PathVariable("path") String path) {
// ...
System.out.println("study.hwj.myspringmvc.MyController.mupload");
System.out.println(path);
return ;
}
}
Flash属性
Flash属性为一个请求提供了一种存储要在另一个请求中使用的属性的方式。重定向时最常需要此方法,例如Post-Redirect-Get模式。Flash属性在重定向之前(通常在会话中)被临时保存,以便在重定向之后可供请求使用,并立即被删除。
Spring MVC有两个主要的抽象来支持Flash属性。FlashMap
用于保存Flash属性,而FlashMapManager
用于存储,检索和管理 FlashMap
实例。
Flash属性支持始终处于“打开”状态,无需显式启用。但是,如果不使用它,则永远不会导致HTTP会话创建。在每个请求上,都有一个“输入” FlashMap
,该属性具有从前一个请求(如果有)传递过来的属性,而“输出”FlashMap
则具有为后续请求保存的属性。FlashMap
可以通过Spring中的静态方法RequestContextUtils
从Spring MVC中的任何位置访问这两个实例 。
带注释的控制器通常不需要直接使用FlashMap
。而是, @RequestMapping
方法可以接受RedirectAttributes
类型的参数,并使用它为重定向方案添加Flash属性。通过添加的Flash属性将 RedirectAttributes
自动传播到“输出” FlashMap
。同样,重定向后,来自“输入”FlashMap
的属性会自动添加到 服务于目标URL的控制器的Model
。
将请求与Flash属性匹配
Flash属性的概念存在于许多其他Web框架中,并已证明有时会遇到并发问题。这是因为根据定义,Flash 属性将存储到下一个请求。但是,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,过早删除了闪存属性。
为了减少此类问题的可能性,RedirectView
会自动使用目标重定向URL的路径和查询参数“标记” FlashMap
实例。 反过来,默认FlashMapManager
在查找“输入” FlashMap
时会将信息与传入请求匹配。
这不能完全消除并发问题的可能性,但是可以通过重定向URL中已经可用的信息大大减少并发问题。因此,我们建议您主要将Flash属性用于重定向方案。
Multipart
启用MultipartResolver
后,将解析具有multipart/form-data
的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";
}
}
将参数类型声明为List <MultipartFile>
允许解析同一参数名的多个文件。
如果将@RequestParam
注解声明为Map <String, MultipartFile>
或MultiValueMap <String, MultipartFile>
,但未在注解中指定参数名称,则将使用每个给定参数名称的多部分文件来填充Map
。
使用Servlet 3.0多部分解析时,您还可以声明
javax.servlet.http.Part
而不是Spring的MultipartFile
作为方法参数或集合值类型。
您还可以将多部分内容用作绑定到命令对象的数据的一部分。 例如,前面示例中的表单字段和文件可以是表单对象上的字段,如以下示例所示:
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
)。 在使用HttpMessageConverter
进行转换之后,使用@RequestPart
注解来访问多部分:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
您可以将@RequestPart
与javax.validation.Valid
结合使用,也可以使用Spring的@Validated
注释,这两种注释都会导致应用标准Bean验证。 默认情况下,验证错误会导致MethodArgumentNotValidException
,该异常将转换为400
(BAD_REQUEST
)响应。 或者,您可以通过Errors
或BindingResult
参数在控制器内本地处理验证错误,如以下示例所示:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
@RequestBody
您可以使用@RequestBody
注解来使请求正文通过HttpMessageConverter
读取并反序列化为Object。 以下示例使用@RequestBody
参数:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
您可以使用MVC Config的“ 消息转换器”选项来配置或自定义消息转换。
您可以将@RequestBody
与javax.validation.Valid
或Spring的@Validated
注释结合使用,这两种注释都会导致应用标准Bean验证。 默认情况下,验证错误会导致MethodArgumentNotValidException
,该异常将转换为400
(BAD_REQUEST
)响应。 或者,您可以通过Errors
或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 Config的“消息转换器”选项来配置或自定义消息转换。
您可以将@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库的支持。
JSON视图
Spring MVC为Jackson的序列化视图提供了内置支持 ,该视图仅可呈现Object
中所有字段的子集。要将其与 @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. Model
您可以使用@ModelAttribute
注释:
- 在
@RequestMapping
方法中的方法参数上,可从模型创建或访问对象,并将其通过WebDataBinder
绑定到请求。 - 作为
@Controller
或@ControllerAdvice
类中的方法级注释,可在任何@RequestMapping
方法调用之前帮助初始化模型。 - 在
@RequestMapping
方法上标记其返回值的是模型属性。
本节讨论@ModelAttribute
方法-前面列表中的第二项。 控制器可以具有任意数量的@ModelAttribute
方法。 所有此类方法均在同一控制器中的@RequestMapping
方法之前调用。 @ModelAttribute
方法也可以通过@ControllerAdvice
在控制器之间共享。 有关更多详细信息,请参见 Controller Advice 部分。
@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);
}
如果未明确指定名称,则根据Object
类型选择默认名称,如javadoc中针对Conventions
的解释。您始终可以使用重载addAttribute
方法或通过name
on 的属性@ModelAttribute
(用于返回值)来分配显式名称。
如果未明确指定名称,则根据
Object
类型选择默认名称,如约定的javadoc中所述。 您始终可以使用重载的addAttribute
方法或通过@ModelAttribute
上的name
属性(用于返回值)来分配显式名称。
您也可以将@ModelAttribute
用作@RequestMapping
方法上的方法级注释,在这种情况下,@RequestMapping
方法的返回值将解释为模型属性。 通常不需要这样做,因为这是HTML控制器的默认行为,除非返回值是一个String
,否则它将被解释为视图名称。 @ModelAttribute
还可以自定义模型属性名称,如以下示例所示:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
先调用 modelFactory.initModel(webRequest, mavContainer, invocableMethod);
后调用 invocableMethod.invokeAndHandle(webRequest, mavContainer);
@ModelAttribute方法的调用栈:
pet:16, MyController (study.hwj.mymvc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:190, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeModelAttributeMethods:137, ModelFactory (org.springframework.web.method.annotation)
initModel:106, ModelFactory (org.springframework.web.method.annotation)
invokeHandlerMethod:865, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:793, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1040, DispatcherServlet (org.springframework.web.servlet)
doService:943, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:634, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:741, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:688, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:367, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:868, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1639, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
1.3.5。 DataBinder
@Controller
或@ControllerAdvice
类可以具有用于初始化WebDataBinder
实例的@InitBinder
方法,而这些方法又可以:
- 将请求参数(即表单或查询数据)绑定到模型对象。
- 将基于字符串的请求值(例如请求参数,路径变量,标头,Cookie等)转换为控制器方法参数的目标类型。
- 呈现HTML表单时,将模型对象值格式化为
String
值。
@InitBinder
方法可以注册特定于控制器的java.bean.PropertyEditor
或Spring Converter
和Formatter
组件。此外,您可以使用 MVC配置 在全局共享FormattingConversionService
中注册Converter
和Formatter
键入。
@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
使用基于Formatter
的设置时,可以重新使用相同的方法并注册特定于控制器的Formatter
实现,如以下示例所示:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
1.3.6. Exceptions
@Controller
和@ControllerAdvice类可以具有处理控制器方法异常的@ExceptionHandler
方法,如以下示例所示:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
该异常可以与正在传播的顶级异常(即,直接抛出的IOException
异常)匹配,也可以与顶级包装异常(例如,IOException
包装在IllegalStateException
内)的直接原因匹配 。
对于匹配的异常类型,最好将目标异常声明为方法参数,如前面的示例所示。当多个异常方法匹配时,根源异常匹配通常比原因异常匹配更可取。更具体地,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
的包装器异常中传播,则传入的异常实例就是该包装器异常。在
handle(Exception)
变体中,行为甚至更简单。 在包装方案中,总是使用包装程序异常来调用此方法,在这种情况下,实际匹配的异常可以通过ex.getCause()
找到。 仅当将它们作为顶级异常抛出时,传入的异常才是实际的FileSystemException
或RemoteException
实例。
通常,我们建议您在参数签名中尽可能具体,以减少根类型和原因异常类型之间不匹配的可能性。考虑将多重匹配方法分解为单个@ExceptionHandler
方法,每个方法都通过其签名匹配单个特定的异常类型。
在多@ControllerAdvice
安排中,我们建议在以相应顺序优先的@ControllerAdvice
上声明您的主根异常映射。 尽管根异常匹配优先于原因,但它是在给定控制器或@ControllerAdvice
类的方法之间定义的。 这意味着,优先级较高的@ControllerAdvice
Bean上的原因匹配优先于优先级较低的@ControllerAdvice
Bean上的任何匹配(例如,根)。
最后但并非最不重要的一点是,@ExceptionHandler
方法实现可以选择以不处理给定异常实例的方式将其以原始形式重新抛出。在仅对根级别匹配或无法静态确定的特定上下文中的匹配感兴趣的情况下,这很有用。重新抛出的异常会在其余的解决方案链中传播,就像给定的@ExceptionHandler
方法最初不会匹配一样。
Spring MVC中对@ExceptionHandler
方法的支持建立在DispatcherServlet
级别 HandlerExceptionResolver 机制上。
方法参数
@ExceptionHandler
方法支持以下参数:
方法参数 | 描述 |
---|---|
异常类型 | 用于访问引发的异常。 |
HandlerMethod |
用于访问引发异常的控制器方法。 |
WebRequest , NativeWebRequest |
对请求参数以及请求和会话属性的常规访问,而无需直接使用Servlet API。 |
javax.servlet.ServletRequest , javax.servlet.ServletResponse |
选择任何特定的请求或响应类型(例如ServletRequest 或 HttpServletRequest 或Spring的MultipartRequest 或MultipartHttpServletRequest )。 |
javax.servlet.http.HttpSession |
强制会话的存在。结果,这个参数永远不会为null 。请注意,会话访问不是线程安全的。 如果允许多个请求同时访问会话,请考虑将 RequestMappingHandlerAdapter 实例的syncnizeOnSession 标志设置为true 。 |
java.security.Principal |
当前经过身份验证的用户-可能是特定的Principal 实现类(如果已知)。 |
HttpMethod |
请求的HTTP方法。 |
java.util.Locale |
当前的请求语言设置,由最具体的可用LocaleResolver 确定,实际上是配置的LocaleResolver 或LocaleContextResolver 。 |
java.util.TimeZone , java.time.ZoneId |
与当前请求关联的时区,由LocaleContextResolver 决定。 |
java.io.OutputStream , java.io.Writer |
用于访问原始响应主体(由Servlet API公开)。 |
java.util.Map ,org.springframework.ui.Model ,org.springframework.ui.ModelMap |
用于访问模型以进行错误响应。永远是空的。 |
RedirectAttributes |
指定在重定向的情况下要使用的属性(要附加到查询字符串中),并指定要临时存储的属性,直到重定向后的请求为止。请参阅重定向属性和Flash属性。 |
@SessionAttribute |
对于访问任何会话属性,与通过类级别@SessionAttributes 声明存储在会话中的模型属性不同。 有关更多详细信息,请参见@SessionAttribute 。 |
@RequestAttribute |
用于访问请求属性。请参阅@RequestAttribute 以获取更多详细信息。 |
返回值
@ExceptionHandler
方法支持以下返回值:
返回值 | 描述 |
---|---|
@ResponseBody |
返回值通过HttpMessageConverter 实例转换并写入响应。请参阅@ResponseBody 。 |
HttpEntity , ResponseEntity |
返回值指定完整的响应(包括HTTP标头和正文)将通过HttpMessageConverter 实例转换并写入响应。参见ResponseEntity。 |
String |
一个视图名称,将通过实现来解析,并与隐式模型一起使用-通过命令对象和@ModelAttribute 方法确定。该处理程序方法还可以通过声明一个Model 变量(如前所述)以编程方式丰富模型。 |
View |
用于与隐式模型一起渲染的View 实例,该隐式模型是通过命令对象和@ModelAttribute 方法确定的。 该处理程序方法还可以通过声明模型变量(如前所述)以编程方式丰富模型。 |
java.util.Map , org.springframework.ui.Model |
要添加到隐式模型的属性,其视图名称是通过RequestToViewNameTranslator 隐式确定的。 |
@ModelAttribute |
要添加到模型的属性,其视图名称通过RequestToViewNameTranslator 隐式确定。请注意,@ModelAttribute 是可选的。请参见表末尾的“其他任何返回值”。 |
ModelAndView 对象 |
要使用的视图和模型属性,以及响应状态(可选)。 |
void |
如果返回类型为void (或者返回值为null )的方法还具有ServletResponse ,OutputStream 参数或@ResponseStatus 批注,则认为该方法已完全处理了响应。 如果控制器进行了肯定的ETag 或lastModified 时间戳检查,也是如此(请参阅 Controllers 以获取详细信息)。如果以上所有条件都不成立,则对于REST控制器, void 返回类型也可以指示“无响应正文”,对于HTML控制器,则默认选择视图名称。 |
任何其他返回值 | 如果返回值与上述任何一个都不匹配并且不是简单类型(由BeanUtils#isSimpleProperty确定 ),则默认情况下会将其视为要添加到模型的模型属性。如果它是简单类型,则仍无法解决。 |
REST API 异常
REST服务的常见要求是在响应正文中包含错误详细信息。Spring框架不会自动执行此操作,因为响应主体中错误详细信息的表示是特定于应用程序的。但是,@RestController
可以使用带有ResponseEntity
返回值的@ExceptionHandler
方法来设置响应的状态和主体。也可以在@ControllerAdvice
类中声明此类方法以将其全局应用。
在响应主体中实现具有错误详细信息的全局异常处理的应用应考虑继承 ResponseEntityExceptionHandler
,它提供对Spring MVC引发的异常的处理,并提供用于自定义响应主体的钩子。要使用此功能,请创建ResponseEntityExceptionHandler
的子类 ,用@ControllerAdvice
进行注释,覆盖必要的方法,然后将其声明为Spring bean。
代码示例
@ControllerAdvice
public class MyResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleMyException(Exception ex, WebRequest request) throws Exception {
System.out.println("study.hwj.mymvc.MyResponseEntityExceptionHandler.handleMyException");
return new ResponseEntity<>(ex.getMessage(), HttpStatus.OK);
}
}
继承ResponseEntityExceptionHandler
时,异常类型如果是org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler#handleException
上指定的具体类型时,调用父类的方法;如果不继承,则都调用实现类方法。
1.3.7. Controller Advice
通常,@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. Functional Endpoints
Spring Web MVC包括WebMvc.fn,这是一个轻量级的函数编程模型,其中的函数用于路由和处理请求,而契约则是为不变性而设计的。它是基于注释的编程模型的替代方案,但可以在同一DispatcherServlet上运行。
1.4.1。总览
在WebMvc.fn中,HTTP请求使用HandlerFunction
来处理:一个接受ServerRequest
并返回ServerResponse
的函数 。作为请求对象的请求都具有不可变的协定,这些协定为JDK 8提供了对HTTP请求和响应的友好访问。 HandlerFunction
等效于基于注释的编程模型中@RequestMapping
方法的主体。
传入的请求通过RouterFunction
路由到处理函数:该函数接受ServerRequest
并返回可选的HandlerFunction
(即Optional <HandlerFunction>
)。 当路由器功能匹配时,返回处理程序功能。 否则为空的Optional
。 RouterFunction
等效于@RequestMapping
注解,但主要区别在于路由器功能不仅提供数据,而且还提供行为。
RouterFunctions.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
是不可变的接口,它们提供JDK 8友好的HTTP请求和响应访问,包括标头,正文,方法和状态代码。
ServerRequest
ServerRequest
提供对HTTP方法,URI,标头和查询参数的访问,而通过body
方法提供对主体的访问。
下面的示例将请求正文提取为String:
String string = request.body(String.class);
以下示例将主体提取到List
,其中Person
对象从序列化形式(例如JSON或XML)解码:
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
以下示例显示如何访问参数:
MultiValueMap<String, String> params = request.params();
ServerResponse
ServerResponse
提供对HTTP响应的访问,并且由于它是不可变的,因此可以使用一种build
方法来创建它。您可以使用构建器来设置响应状态,添加响应标题或提供正文。以下示例使用JSON内容创建200
(OK
)响应:
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
下面的示例演示如何构建一个具有Location
标头且没有正文的201
(CREATED
)响应:
URI location = ...
ServerResponse.created(location).build();
Handler Classes
我们可以将处理程序函数编写为lambda,如以下示例所示:
HandlerFunction<ServerResponse> helloWorld = request -> ServerResponse.ok().body("Hello World");
这很方便,但是在应用程序中我们需要多个功能,并且多个内联lambda可能会变得凌乱。因此,将相关的处理程序功能分组到一个处理程序类中很有用,该类具有与基于@Controller
注释的应用程序相似的作用。例如,以下类公开了反应式Person
存储库:
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;
}
// listPeople是一个处理程序函数,以JSON格式返回存储库中找到的所有Person对象。
public ServerResponse listPeople(ServerRequest request) {
List<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people);
}
// createPerson是一个处理程序函数,用于存储请求正文中包含的新Person。
public ServerResponse createPerson(ServerRequest request) throws Exception {
Person person = request.body(Person.class);
repository.savePerson(person);
return ok().build();
}
// getPerson是一个处理程序函数,该函数返回由path变量id标识的一个人。我们从存储库中检索到该Person,并创建一个JSON响应(如果找到)。如果找不到,我们将返回404 Not Found响应。
public ServerResponse getPerson(ServerRequest request) {
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();
}
}
}
Validation
函数式端点(functional endpoint)可以使用Spring的验证工具将验证应用于请求主体。例如,给定Person
的定制的Spring Validator实现:
public class PersonHandler {
// 创建Validator实例。
private final Validator validator = new PersonValidator();
// ...
public ServerResponse createPerson(ServerRequest request) {
Person person = request.body(Person.class);
// 应用验证。
validate(person);
repository.savePerson(person);
return ok().build();
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
// 引发400响应的异常。
throw new ServerWebInputException(errors.toString());
}
}
}
处理程序还可以通过创建和注入基于LocalValidatorFactoryBean
的全局Validator
实例来使用标准bean验证API(JSR-303)。请参阅Spring Validation。
1.4.3。 RouterFunction
RouterFunction
用于将请求路由到相应的HandlerFunction
。通常,您不是自己编写路由器功能,而是在RouterFunctions
实用工具类上使用一种方法来创建一个。 RouterFunctions.route()
(无参数)为您提供了流畅的生成器来创建路由器功能,而RouterFunctions.route(RequestPredicate, HandlerFunction)
提供了直接的方式来创建路由器。
通常,建议使用route()
构建器,因为它为典型的映射方案提供了便捷的捷径,而无需发现静态导入。例如,路由器功能构建器提供了GET(String, HandlerFunction)
为GET请求创建映射的方法,POST请求对应POST(String, HandlerFunction)
。
除了基于HTTP方法的映射外,路由构建器还提供了一种在映射到请求时引入其他谓词的方法。 对于每个HTTP方法,都有一个重载的变体,它以RequestPredicate
作为参数,但是可以表示其他约束。
Predicates
您可以编写自己的RequestPredicate
,但是RequestPredicates
实用程序类根据请求路径,HTTP方法,内容类型等提供常用的实现。以下示例使用请求断言基于Accept
标头创建约束:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World"));
您可以使用以下命令组合多个请求谓词:
RequestPredicate.and(RequestPredicate)
-两者都必须匹配。RequestPredicate.or(RequestPredicate)
-两者任一匹配即可。
RequestPredicates
中的许多断言都是组成的。 例如,RequestPredicates.GET(String)
由RequestPredicates.method(HttpMethod)
和RequestPredicates.path(String)
组成。 上面显示的示例还使用了两个请求断言,因为构建器在内部使用RequestPredicates.GET
并将其与accept
断言组合在一起。
路由
路由器功能按顺序评估:如果第一个路由不匹配,则评估第二个路由,依此类推。因此,在通用路由之前声明更具体的路由是有意义的。请注意,此行为不同于基于注释的编程模型,在该模型中,将自动选择“最特定”的控制器方法。
使用路由器功能构建器时,所有定义的路由都组成一个从build()
中返回的RouterFunction
。还有其他方法可以将多个路由器功能组合在一起:
- 在
RouterFunctions.route()
构建器上add(RouterFunction)
RouterFunction.and(RouterFunction)
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
— 带有嵌套RouterFunctions.route()
的RouterFunction.and()
的快捷方式。
以下示例显示了四种路由的组成:
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()
// 带有与JSON匹配的Accept标头的 GET /person/{id} 被路由到PersonHandler.getPerson
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
// 具有与JSON匹配的Accept标头的 GET /person 路由到PersonHandler.listPeople
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
// 没有其他断言的 POST /person 映射到PersonHandler.createPerson
.POST("/person", handler::createPerson)
// otherRoute 是在其他位置创建的路由器功能,并将其添加到构建的路由中。
.add(otherRoute)
.build();
嵌套路由
一组路由器功能通常具有共享断言,例如共享路径。 在上面的示例中,共享断言将是与/person
匹配的路径断言,其中三个路径使用该断言。 使用注释时,您可以通过使用映射到/person
的类型级别的@RequestMapping
注释来删除此重复项。 在WebMvc.fn中,可以通过路由器功能构建器上的path
方法共享路径谓词。 例如,可以通过以下方式使用嵌套路由来改进上面示例的最后几行:
RouterFunction<ServerResponse> route = route()
// 请注意,path的第二个参数是使用路由器构建器的消费者
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
尽管基于路径的嵌套是最常见的,但是您可以通过使用构建器上的nest
方法来嵌套在任何种类的断言上。 上面的内容仍然包含一些以共享Accept
标头断言形式出现的重复项。 通过将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 Config在基于DispatcherHandler
设置的路由器中运行路由器功能,该配置使用Spring配置来声明处理请求所需的组件。MVC Java配置声明以下基础结构组件以支持函数式端点:
RouterFunctionMapping
:在Spring配置中检测一个或多个RouterFunction <?>
bean,通过RouterFunction.andOther
组合它们,并将请求路由到生成的组合RouterFunction
。HandlerFunctionAdapter
:一个简单的适配器,它使DispatcherHandler
调用映射到请求的HandlerFunction
。
前面的组件使函数式端点适合于DispatcherServlet
请求处理生命周期,并且(可能)与带注释的控制器(如果已声明)并排运行。 这也是Spring Boot Web启动程序如何启用函数式端点的方式。
以下示例显示了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。过滤处理程序功能(Filtering Handler Functions)
您可以通过使用路由功能构建器上的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过滤器仅应用于两个GET路由。
.before(request -> ServerRequest.from(request)
.header("X-RequestHeader", "Value")
.build()))
.POST("/person", handler::createPerson))
// 记录响应的after过滤器将应用于所有路由,包括嵌套路由。
.after((request, response) -> logResponse(response))
.build();
路由器构建器上的filter
方法采用HandlerFilterFunction
:该函数采用ServerRequest
和HandlerFunction
并返回ServerResponse
。 handler函数参数代表链中的下一个元素。 这通常是路由到的处理程序,但是如果应用了多个,它也可以是另一个过滤器。
现在,我们可以为路由添加一个简单的安全过滤器,假设我们有一个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)
将过滤器应用于现有路由器功能。
通过专用的
CorsWebFilter
提供对功能端点的CORS支持。
1.5。URI链接
本节描述了Spring框架中可用于URI的各种选项。
1.5.1。UriComponents
UriComponentsBuilder
有助于从带有变量的URI模板构建URI,如以下示例所示:
UriComponents uriComponents = UriComponentsBuilder
// 带有URI模板的静态工厂方法。
.fromUriString("https://example.com/hotels/{hotel}")
// 添加或替换URI组件。
.queryParam("q", "{q}")
// 请求对URI模板和URI变量进行编码。
.encode()
// 建立一个UriComponents。
.build();
// 展开变量并获得URI。
URI uri = uriComponents.expand("Westin", "123").toUri();
可以将前面的示例合并为一个链,并通过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
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模板进行编码,而是在将其扩展到模板之前通过UriUtils#encodeUriUriVariables
对URI变量进行严格编码。URI_COMPONENTS
:使用UriComponents#encode()
,对应于先前列表中的第二个选项,在扩展URI变量后使用 URI 组件值进行编码。NONE
:未应用编码。
由于历史原因和向后兼容性,将RestTemplate
设置为EncodingMode.URI_COMPONENTS
。 WebClient
依赖于DefaultUriBuilderFactory
中的默认值,该默认值已从5.0.x
中的EncodingMode.URI_COMPONENTS
更改为5.1
中的EncodingMode.TEMPLATE_AND_VALUES
。
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相关的URI(例如/main/*
),如以下示例所示:
// Re-uses host, port, context path, and Servlet prefix...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
从5.1开始,
ServletUriComponentsBuilder
忽略来自Forwarded
和X-Forwarded-*
标头的信息,该标头指定了客户端起源的地址。考虑使用ForwardedHeaderFilter
来提取和使用或丢弃此类标头。
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
的链接创建时,其设计受到限制。 除了需要适当的参数签名外,返回类型还存在技术限制(即,为链接生成器调用生成运行时代理),因此返回类型不得为最终值。 特别是,视图名称的通用String
返回类型在这里不起作用。 您应该改用ModelAndView
甚至普通对象(具有String
返回值)。
前面的示例在MvcUriComponentsBuilder
中使用静态方法。 在内部,它们依靠ServletUriComponentsBuilder
从当前请求的方案,主机,端口,上下文路径和Servlet路径准备基本URL。 在大多数情况下,此方法效果很好。 但是,有时可能不足。 例如,您可能不在请求的上下文之内(例如,准备链接的批处理过程),或者您可能需要插入路径前缀(例如从请求路径中删除并需要重新插入链接的语言环境前缀)。
在这种情况下,可以使用静态的fromXxx
重载方法,这些方法接受UriComponentsBuilder
以使用基本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
忽略来自Forwarded
和X-Forwarded-*
标头的信息,该标头指定了客户端起源的地址。考虑使用 ForwardedHeaderFilter提取和使用或丢弃此类标头。
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
),但是很容易定义您自己的函数或为其他模板技术准备类似的函数。
工作机制是这样的。启动时,将通过HandlerMethodMappingNamingStrategy
为每个@RequestMapping
分配一个默认名称,该名称的默认实现使用该类的大写字母和方法名称(例如,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
控制器可以使用java.util.concurrent.Callable
来包装任何受支持的返回值,如以下示例所示:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
然后,可以通过配置的TaskExecutor
运行给定任务来获取返回值。
1.6.3. Processing
这是Servlet异步请求处理的非常简洁的概述:
- 可以通过调用
request.startAsync()
将ServletRequest
置于异步模式。 这样做的主要效果是Servlet(以及所有过滤器)可以退出,但是响应保持打开状态,以便以后完成处理。 - 对
request.startAsync()
的调用返回AsyncContext
,可将其用于进一步控制异步处理。 例如,它提供了dispatch
方法,该方法与Servlet API中的转发方法类似,不同之处在于,它使应用程序可以恢复对Servlet容器线程的请求处理。 ServletRequest
提供对当前DispatcherType
的访问,您可以使用它来区分处理初始请求,异步调度,转发和其他调度程序类型。
DeferredResult
处理工作如下:
- 控制器返回
DeferredResult
并将其保存在一些可访问它的内存队列或列表中。 - Spring MVC调用
request.startAsync()
。 - 同时,
DispatcherServlet
和所有配置的过滤器退出请求处理线程,但响应保持打开状态。 - 应用程序从某个线程设置
DeferredResult
,Spring MVC将请求分派回Servlet容器。 - 再次调用
DispatcherServlet
,并使用异步产生的返回值恢复处理。
Callable
处理工作如下:
- 控制器返回
Callable
。 - Spring MVC调用
request.startAsync()
并将Callable
提交到TaskExecutor
一个单独的线程中进行处理。 - 同时,
DispatcherServlet
和所有过滤器退出Servlet容器线程,但响应保持打开状态。 - 最终
Callable
产生一个结果,Spring MVC将请求分派回Servlet容器以完成处理。 - 再次调用
DispatcherServlet
,并使用Callable
中异步产生的返回值恢复处理。
有关更多背景知识,您还可以阅读在Spring MVC 3.2中引入了异步请求处理支持的博客文章。
异常处理
使用DeferredResult
时,使用DeferredResult
时,可以选择是调用setResult
还是带有异常的setErrorResult
。在这两种情况下,Spring MVC都将请求分派回Servlet容器以完成处理。然后将其视为是控制器方法返回了给定值,还是好像它产生了给定的异常。然后,异常将通过常规的异常处理机制(例如,调用 @ExceptionHandler
方法)进行处理。
当您使用时Callable
,会发生类似的处理逻辑,主要区别是从Callable
中返回了结果,或者引发了异常。
拦截
HandlerInterceptor
实例的类型可以为AsyncHandlerInterceptor
,以接收启动异步处理的初始请求(而不是postHandle
和afterCompletion
)上的afterConcurrentHandlingStarted
回调。
HandlerInterceptor
实现也可以注册CallableProcessingInterceptor
或DeferredResultProcessingInterceptor
,以与异步请求的生命周期更深入地集成(例如,处理超时事件)。请参阅 AsyncHandlerInterceptor
以获取更多详细信息。
DeferredResult
提供onTimeout(Runnable)
和onCompletion(Runnable)
回调。 有关更多详细信息,请参见DeferredResult
的javadoc。 可以用Callable
代替WebAsyncTask
,它公开了超时和完成回调的其他方法。
与WebFlux相比
Servlet API最初是为通过Filter-Servlet链进行一次传递而构建的。Servlet 3.0中添加了异步请求处理,使应用程序可以退出Filter-Servlet链,但保留响应以进行进一步处理。Spring MVC异步支持围绕该机制构建。当控制器返回DeferredResult
时,退出Filter-Servlet链,并释放Servlet容器线程。稍后,当设置DeferredResult
时,将进行一次ASYNC
调度(到相同的URL),在此期间再次映射控制器,但是使用DeferredResult
值(就像控制器返回了它一样)而不是调用它来恢复处理。
相比之下,Spring WebFlux既不是基于Servlet API构建的,也不需要这种异步请求处理功能,因为它在设计上是异步的。异步处理已内置在所有框架协定中,并在请求处理的所有阶段得到内在支持。
从编程模型的角度来看,Spring MVC和Spring WebFlux都支持异步和响应类型作为控制器方法中的返回值。Spring MVC甚至支持流式传输,包括反应性背压。但是,与WebFlux不同,WebFlux依赖于非阻塞I/O,并且每次写入都不需要额外的线程,因此对响应的单个写入仍然处于阻塞状态(并在单独的线程上执行)。
另一个根本的区别是,Spring MVC在控制器方法参数中不支持异步或响应类型(例如,@RequestBody
,@RequestPart
等),也没有对异步和响应类型作为模型属性的任何显式支持。 Spring WebFlux确实支持所有这些。
1.6.4. HTTP Streaming
您可以将DeferredResult
和Callable
用于单个异步返回值。如果要产生多个异步值并将那些值写入响应中怎么办?本节介绍如何执行此操作。
Objects
您可以使用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();
您还可以在ResponseEntity
中将ResponseBodyEmitter
用作主体,以自定义响应的状态和标头。
当emitter
引发IOException
异常时(例如,如果远程客户端消失了),应用程序不负责清理连接,因此不应调用emitter.complete
或emitter.completeWithError
。取而代之的是,Servlet容器自动启动 AsyncListener
错误通知,Spring MVC在其中进行completeWithError
调用。反过来,此调用对应用程序执行最后一次ASYNC
调度,在此期间,Spring MVC调用已配置的异常解析器并完成请求。
SSE(Server Sent Events)
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
(Reactor)或Single
(RxJava)。 - 与使用
ResponseBodyEmitter
或SseEmitter
相似,适用于具有流媒体类型(例如application/stream+json
或text/event-stream
)的多值流。 示例包括Flux
(Reactor)或Observable
(RxJava)。 应用程序还可以返回Flux<ServerSentEvent>
或Observable<ServerSentEvent>
。 - 与使用
DeferredResult<List<?>>
相似,适用于具有其他任何媒体类型(例如application/json
)的多值流。
Spring MVC通过
spring-core
的ReactiveAdapterRegistry
支持Reactor和RxJava,这使其可以适应多个反应式库。
为了流式传输到响应,支持响应式背压,但响应的写仍处于阻塞状态,并通过配置的TaskExecutor
在单独的线程上执行,以避免阻塞上游源(例如,从WebClient
返回的Flux
)。 默认情况下,SimpleAsyncTaskExecutor
用于阻止写操作,但是在负载下不适合。 如果计划使用响应类型进行流传输,则应使用MVC配置来配置任务执行程序。
1.6.6。断开连接
当远程客户端离开时,Servlet API不提供任何通知。因此,在通过SseEmitter 或反应性类型流式传输到响应时,定期发送数据非常重要,因为如果客户端断开连接,写入将失败。发送可以采取空(comment-only)SSE事件或另一端必须将其解释为心跳和忽略的任何其他数据的形式。
或者,考虑使用具有内置心跳机制的Web消息传递解决方案(例如,基于 WebSocket的STOMP或具有SockJS的 WebSocket )。
1.6.7. Configuration
必须在Servlet容器级别启用异步请求处理功能。MVC配置还为异步请求提供了多个选项。
Servlet容器
Filter和Servlet声明具有一个asyncSupported
标志,需要将其设置为true
启用异步请求处理。此外,应声明过滤器映射以处理ASYNC
javax.servlet.DispatchType
。
在Java配置中,当您用AbstractAnnotationConfigDispatcherServletInitializer
初始化Servlet容器时,这是自动完成的。
在web.xml
配置中,可以将<async-supported>true</async-supported>
添加到DispatcherServlet
和Filter
声明中,并添加<dispatcher>ASYNC</dispatcher>
来过滤映射。
Spring MVC
MVC配置公开了以下与异步请求处理相关的选项:
- Java配置:在
WebMvcConfigurer
上使用configureAsyncSupport
回调。 - XML名称空间:使用
<mvc:annotation-driven>
下的<async-support>
元素。
您可以配置以下内容:
- 异步请求的默认超时值(如果未设置)取决于底层的Servlet容器(例如,在Tomcat上为10秒)。
AsyncTaskExecutor
用于在使用响应类型进行流式传输时阻止写操作以及用于执行从控制器方法返回的Callable
实例。我们强烈建议您配置此属性,如果您使用反应性类型进行流式处理或具有Callable
返回的控制器方法,则默认情况下为SimpleAsyncTaskExecutor
。DeferredResultProcessingInterceptor
实现和CallableProcessingInterceptor
实现。
请注意,您还可以在DeferredResult
,ResponseBodyEmitter
和SseEmitter
上设置默认超时值。 对于Callable
,可以使用WebAsyncTask
提供超时值。
1.7。CORS
Spring MVC使您可以处理CORS(跨域资源共享)。本节介绍如何执行此操作。
1.7.1。介绍
出于安全原因,浏览器禁止AJAX调用当前来源以外的资源。例如,您可以将您的银行帐户放在一个标签中,将evil.com放在另一个标签中。来自evil.com的脚本不应使用您的凭据向您的银行API发出AJAX请求,例如,从您的帐户中提取资金!
跨域资源共享(CORS)是由大多数浏览器实现的W3C规范,可让您指定授权哪种类型的跨域请求,而不是使用基于IFRAME
或JSONP
的安全性较低且功能较弱的变通办法。
1.7.2. Processing
CORS规范区分飞行前(preflight),简单和实际请求。要了解CORS的工作原理,您可以阅读本文以及其他内容,或者参阅规范以获取更多详细信息。
Spring MVC HandlerMapping
实现为CORS提供内置支持。成功将请求映射到处理程序后,HandlerMapping
实现会检查给定请求和处理程序的CORS配置,并采取进一步的措施。飞行前请求直接处理,而简单和实际的CORS请求被拦截,验证并设置了必需的CORS响应标头。
为了启用跨域请求(即,Origin
标头存在并且与请求的主机不同),您需要具有一些显式声明的CORS配置。如果找不到匹配的CORS配置,则飞行前请求将被拒绝。没有将CORS标头添加到简单和实际CORS请求的响应中,因此,浏览器拒绝了它们。
每个HandlerMapping
都可以使用基于URL模式的CorsConfiguration
映射进行单独配置。在大多数情况下,应用程序使用MVC Java配置或XML名称空间声明此类映射,从而导致将单个全局映射传递给所有HandlerMappping
实例。
您可以将HandlerMapping
级别的全局CORS配置与更细粒度的处理程序级别的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支持。
如果您尝试在Spring Security中使用
CorsFilter
,请记住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缓存
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
指令进行缓存。 n > 0
值通过使用Cache-Control: max-age=n
指令将给定响应缓存n
秒。
1.9.2. Controllers
控制器可以添加对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 = ...
if (request.checkNotModified(eTag)) {
// 响应已设置为304(NOT_MODIFIED)-无需进一步处理。
return null;
}
// 继续进行请求处理。
model.addAttribute(...);
return "myViewName";
}
可以使用三种变体来根据eTag
值,lastModified
值或两者一起检查条件请求。对于条件GET
和HEAD
请求,您可以将响应设置为304
(NOT_MODIFIED
)。对于有条件的POST
,PUT
和DELETE
,可以改为将响应设置为409
(PRECONDITION_FAILED
),以防止并发修改。
1.9.3。静态资源
您应使用Cache-Control
和条件响应标头来提供静态资源,以实现最佳性能。请参阅“配置静态资源 ”部分。
1.9.4。ETag
过滤器
您可以使用ShallowEtagHeaderFilter
来添加根据响应内容计算出的“shallow” eTag
值,从而节省带宽,但不节省CPU时间。请参阅Shallow ETag。
1.10. View Technologies
Spring MVC中视图技术的使用是可插入的,无论您决定使用Thymeleaf,Groovy标记模板,JSP还是其他技术,主要取决于配置更改。本章介绍与Spring MVC集成的视图技术。我们假设您已经熟悉View Resolution。
1.10.1. Thymeleaf
Thymeleaf是一种现代的服务器端Java模板引擎,它强调可以通过双击在浏览器中预览的自然HTML模板,这对于独立处理UI模板(例如,由设计人员)而无需使用非常有用。正在运行的服务器。如果要替换JSP,Thymeleaf提供了最广泛的功能集之一,以使这种过渡更加容易。Thymeleaf是积极开发和维护的。有关更完整的介绍,请参见 Thymeleaf项目主页。
Thymeleaf与Spring MVC的集成由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.Properties
对象,而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"/>
有关设置和变量应用于Configuration
对象的详细信息,请参见FreeMarker文档。
表单处理
Spring提供了一个在JSP中使用的标签库,其中包含一个<spring:bind />
元素。 该元素主要允许表单显示来自表单支持对象的值,并显示来自Web或业务层中Validator
的验证失败的结果。 Spring还支持FreeMarker中的相同功能,并带有用于生成表单输入元素本身的附加便利宏。
绑定宏
简单绑定
输入宏
HTML转义
略
1.10.3。Groovy标记
Groovy的标记模板引擎的主要目的是生成XML类标记(XML,XHTML,HTML5等),但你可以用它来生成任何基于文本的内容。Spring框架具有内置的集成,可以将Spring MVC与Groovy标记一起使用。
Groovy标记模板引擎需要Groovy 2.3.1+。
Configuration
以下示例显示了如何配置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/"/>
Example
与传统的模板引擎不同,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. Script Views
Spring框架具有一个内置的集成,可以将Spring MVC与可以在JSR-223 Java脚本引擎之上运行的任何模板库一起使用 。我们已经在不同的脚本引擎上测试了以下模板库:
脚本库 | 脚本引擎 |
---|---|
Handlebars | Nashorn |
Mustache | Nashorn |
React | Nashorn |
EJS | Nashorn |
ERB | JRuby |
String templates | Jython |
Kotlin Script templating | Kotlin |
集成任何其他脚本引擎的基本规则是,它必须实现
ScriptEngine
和Invocable
接口。
要求
脚本模板
略
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>
JSP与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代码段,其中某些标记需要进一步的注释。
Configuration
表单标签库捆绑在spring-webmvc.jar
中。库描述符称为spring-form.tld
。
要使用该库中的标记,请在JSP页面顶部添加以下指令:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
其中form
是要用于此库中标签的标签名称前缀。
表单标签
该标签呈现HTML 'form' 元素,并向内部标签公开绑定路径以进行绑定。它将命令对象放入PageContext
中,以便内部标签可以访问该命令对象。该库中的所有其他标签都是form
标签的嵌套标签 。
假设我们有一个名为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>
页面控制器从放置在PageContext中的命令对象中检索firstName和lastName值。 继续阅读以了解如何将内部标签与表单标签一起使用的更复杂的示例。
下面的清单显示了生成的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>
input
标签
默认情况下,此标记呈现具有绑定值的HTML input
type='text'
元素。有关此标签的示例,请参见Form标签。您还可以使用特定的HTML5类型,如email
,tel
,date
,等。
checkbox
标签
checkboxes
标签
radiobutton
标签
radiobuttons
标签
password
标签
select
标签
option
标签
options
标签
textarea
标签
hidden
标签
errors
标签
略
spring-form.tld
标签库描述符(TLD)包含在spring-webmvc.jar
。有关单个标签的全面参考,请浏览 API参考 或查看标签库说明。
HTTP方法转换
REST的一个关键原则是使用“统一接口”。这意味着可以使用相同的四种HTTP方法(GET,PUT,POST和DELETE)来操纵所有资源(URL)。对于每种方法,HTTP规范都定义了确切的语义。例如,GET应该始终是安全的操作,这意味着它没有副作用,而PUT或DELETE应该是幂等的,这意味着您可以一遍又一遍地重复这些操作,但是最终结果应该相同。虽然HTTP定义了这四种方法,但是HTML仅支持两种:GET和POST。幸运的是,有两种可能的解决方法:您可以使用JavaScript进行PUT或DELETE,或者可以使用“real”方法作为附加参数(在HTML表单中建模为隐藏的输入字段)进行POST。 Spring的HiddenHttpMethodFilter
使用了后一种技巧。该过滤器是一个普通的Servlet过滤器,因此,它可以与任何Web框架(不仅仅是Spring MVC)结合使用。将此过滤器添加到web.xml
,然后将带有隐藏method
参数的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.xml中定义的HiddenHttpMethodFilter
拾取,如以下示例所示:
<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特定的属性。
表单input
标签支持输入text
以外的类型属性。这是为了让渲染新的HTML5特定的输入类型,如email
,date
, range
等。注意type='text'
,由于text
是默认类型,因此不需要输入。
1.10.6. Tiles
您可以像使用其他视图技术一样,将Tiles集成到使用Spring的Web应用程序中。本节将广泛介绍如何执行此操作。
本节重点介绍Spring对
org.springframework.web.servlet.view.tiles3
软件包中Tiles版本3的支持 。
Dependencies
Configuration
略
1.10.7。RSS和Atom
AbstractAtomFeedView
和AbstractRssFeedView
都继承自AbstractFeedView
基类,分别用于提供Atom和RSS Feed视图。 它们基于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
}
}
如果需要访问Locale
,则buildFeedItems()
和buildFeedEntries()
方法会传递HTTP请求。 仅针对Cookie或其他HTTP标头的设置传入HTTP响应。 方法返回后,feed 将自动写入响应对象。
有关创建Atom视图的示例,请参见Alef Arendsen的Spring Team Blog条目。
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));
}
}
}
控制器可以从外部视图定义(按名称引用)或作为处理程序方法的View
实例返回这种视图。
Excel视图
从Spring Framework 4.2开始, org.springframework.web.servlet.view.document.AbstractXlsView
作为Excel视图的基类提供。它基于Apache POI,具有取代过时类AbstractExcelView
的专用子类(AbstractXlsxView
和AbstractXlsxStreamingView
)。
编程模型类似于AbstractPdfView
,buildExcelDocument()
作为核心模板方法,控制器能够从外部定义(按名称)或从处理程序方法作为View
实例返回这种视图。
代码示例
@Slf4j
@Controller
public class MyController {
@GetMapping("/pdf")
public MyPdfView pdf() {
return new MyPdfView();
}
@GetMapping("/excel")
public ModelAndView excel() {
return new ModelAndView(new MyExcelView());
}
}
class MyPdfView extends AbstractPdfView {
@Autowired
protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 输出中文需要设置字体
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Font font = new Font(bfChinese, Font.BOLD);
doc.add(new Paragraph("11"));
doc.add(new Paragraph("12"));
doc.add(new Paragraph("13我", font));
}
}
class MyExcelView extends AbstractXlsxView {
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
row.createCell(0).setCellValue("00");
row.createCell(1).setCellValue("01");
row.createCell(2).setCellValue("02我");
}
}
1.10.9. Jackson
Spring提供了对Jackson JSON库的支持。
基于Jackson的JSON MVC视图
MappingJackson2JsonView
使用Jackson库的ObjectMapper
将响应内容呈现为JSON。 默认情况下,模型映射的所有内容(特定于框架的类除外)均编码为JSON。 对于需要过滤map内容的情况,可以使用modelKeys
属性指定一组特定的模型属性进行编码。 您还可以使用extractValueFromSingleKeyModel
属性,以将单键模型中的值直接提取并序列化,而不是作为模型属性的映射。
您可以根据需要使用Jackson提供的注释来自定义JSON映射。 当需要进一步控制时,可以在需要为特定类型提供自定义JSON序列化器和反序列化器的情况下,通过ObjectMapper
属性注入自定义ObjectMapper
。
基于Jackson的XML视图
MappingJackson2XmlView
使用Jackson XML扩展的XmlMapper
将响应内容呈现为XML。 如果模型包含多个条目,则应使用modelKey
bean属性显式设置要序列化的对象。 如果模型包含单个条目,那么它将自动序列化。
您可以根据需要使用JAXB或Jackson提供的注释自定义XML映射。 当需要进一步控制时,可以通过ObjectMapper
属性注入自定义XmlMapper
,对于自定义XML,需要为特定类型提供序列化器和反序列化器的情况。
1.10.10。XML编组(XML Marshalling)
MarshallingView
使用XML Marshaller
(在org.springframework.oxm
包中定义)将响应内容呈现为XML。 您可以使用MarshallingView
实例的modelKey
bean属性显式设置要编组的对象。 或者,视图遍历所有模型属性,并封送Marshaller
支持的第一个类型。 有关org.springframework.oxm
包中功能的更多信息,请参见使用O/X映射器编组XML。
1.10.11。XSLT视图
XSLT是XML的一种转换语言,在Web应用程序中作为一种视图技术而流行。如果您的应用程序自然处理XML,或者您的模型可以轻松转换为XML,那么XSLT可以作为视图技术的不错选择。下一节说明如何将XML文档生成为模型数据,以及如何在Spring Web MVC应用程序中使用XSLT对其进行转换。
这个示例是一个简单的Spring应用程序,它在Controller
中创建一个单词列表并将其添加到模型map。返回该map以及XSLT视图的视图名称。有关Spring Web MVC Controller
接口的详细信息, 请参见带注释的控制器。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
类中,其中handler方法的定义如下:
@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文档并将其添加到Model映射中。请注意,您还可以将XML文件作为Resource
加载并使用它代替自定义DOM文档。
有可用的软件包自动“对象化”对象图,但是在Spring中,您可以完全灵活地以任何你选择的方式从模型中创建DOM。这样可以防止XML转换在模型数据的结构中扮演过重要的角色,这在使用工具管理DOM化流程时是一种危险。
Transformation
最后,XsltViewResolver
解析 “home” 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中不可用的更多高级定制,请参阅Advanced Java Config和Advanced XML Config。
您不需要了解由MVC Java配置和MVC名称空间创建的基础bean。如果要了解更多信息,请参见特殊Bean类型 和Web MVC Config。
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 Config API
在Java配置中,您可以实现WebMvcConfigurer
接口,如以下示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
在XML中,您可以检查<mvc:annotation-driven>
的属性和子元素。您可以查看Spring MVC XML模式或使用IDE的代码完成功能来发现可用的属性和子元素。
1.11.3。类型转换
默认情况下,将安装Number
和Date
类型的格式化程序,包括对@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. Validation
默认情况下,如果Bean验证存在于类路径中(例如,Hibernate Validator),则LocalValidatorFactoryBean
将注册为全局验证器,以与@Valid
和Validated
在控制器方法参数上一起使用。
在Java配置中,您可以自定义全局Validator
实例,如以下示例所示:
@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>
请注意,您还可以在本地注册Validator
实现,如以下示例所示:
如果需要在某个地方注入
LocalValidatorFactoryBean
,请创建一个bean并用@Primary
进行标记,以避免与MVC配置中声明的那个冲突。
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. Content Types
您可以配置Spring MVC如何根据请求确定请求的媒体类型(例如,Accept
标头,URL路径扩展,查询参数等)。
默认情况下,将首先检查URL路径扩展名-将json
,xml
,rss
和atom
注册为已知扩展名(取决于类路径依赖项)。其次检查 Accept
标头。
考虑将这些默认值更改为 Accept
标头,并且,如果必须使用基于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. Message Converters
您可以通过覆盖configureMessageConverters()
(以替换Spring MVC创建的默认转换器)或覆盖extendMessageConverters()
(以自定义默认转换器或向默认转换器添加其他转换器)来以Java配置自定义HttpMessageConverter
。
以下示例使用自定义的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的默认属性,如下所示:
如果在类路径中检测到以下知名模块,它还将自动注册以下知名模块:
- jackson-datatype-jdk7:支持Java 7类型,例如
java.nio.file.Path
。 - jackson-datatype-joda:支持Joda-Time类型。
- jackson-datatype-jsr310:支持Java 8日期和时间API类型。
- jackson-datatype-jdk8:支持其他Java 8类型,例如
Optional
。
使用jackson XML支持启用缩进,除了jackson-dataformat-xml一个之外,还需要woodstox-core-asl依赖性。
其他有趣的Jackson模块也可用:
- jackson-datatype-money:支持
javax.money
类型(非官方模块)。 - 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"/>
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHttpMessageConverters
1.11.8. View Controllers
这是定义ParameterizableViewController
的快捷方式,该参数可在调用时立即转发到视图。 在视图生成响应之前没有Java控制器逻辑要执行的静态情况下,可以使用它。
以下Java配置示例将对 /
的请求转发到名为home
的视图:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
以下示例通过使用<mvc:view-controller>
元素,实现了与上一示例相同的操作,但使用XML :
<mvc:view-controller path="/" view-name="home"/>
1.11.9. View Resolvers
MVC配置简化了视图解析器的注册。
以下Java配置示例通过使用JSP和Jackson作为JSON呈现的默认View
来配置内容协商视图解析:
@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。静态资源
此选项提供了一种方便的方法来从基于 Resource
的位置列表中提供静态资源。
在下一个示例中,给定一个以/resources
开头的请求,相对路径用于查找和提供静态资源,在Web应用程序根目录下/public
或类路径下/static
。 这些资源的使用期限为一年,以确保最大程度地利用浏览器缓存并减少浏览器发出的HTTP请求。 还评估Last-Modified
标头,如果存在,则返回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,以便可以将其注入其他对象。您也可以使用ResourceUrlEncodingFilter
对Thymeleaf,JSP,FreeMarker以及其他依赖于HttpServletResponse#encodeURL
的URL标签的重写透明化。
请注意,在同时使用EncodedResourceResolver
(例如,用于提供压缩或brotli编码的资源)和VersionResourceResolver
时,必须按此顺序注册。这样可以确保始终基于未编码文件可靠地计算基于内容的版本。
WebJars还通过WebJarsResourceResolver
支持,当org.webjars:webjars-locator-core
库存在于类路径中时,WebJars将自动注册。解析器可以重写URL以包括jar的版本,并且还可以与不带版本的传入URL进行匹配,例如,从/jquery/jquery.min.js
到/jquery/1.2.0/jquery.min.js
。
1.11.11. Default Servlet
Spring MVC允许映射DispatcherServlet
到/
(从而覆盖了容器默认Servlet的映射),同时仍然允许容器的默认Servlet处理静态资源请求。它配置的 DefaultServletHttpRequestHandler
URL映射为/**
,相对于其他URL映射具有最低的优先级。
该处理程序将所有请求转发到默认Servlet。 因此,它必须按所有其他URL HandlerMappings
的顺序保留在最后。 如果使用<mvc:annotation-driven>
,就是这种情况。 另外,如果您设置自己的自定义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容器(包括Tomcat,Jetty,GlassFish,JBoss,Resin,WebLogic和WebSphere)的已知名称列表,在启动时自动检测容器的默认Servlet。 如果默认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。路径匹配(Path Matching)
您可以自定义与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
引入 DelegatingWebMvcConfiguration
,其中:
- 为Spring MVC应用程序提供默认的Spring配置
- 检测并委托给
WebMvcConfigurer
实现以自定义配置。
对于高级模式,可以删除@EnableWebMvc
并直接从DelegatingWebMvcConfiguration
扩展而不是实现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页面。
Servlet API确实公开了一种与HTTP/2相关的构造。 您可以使用javax.servlet.http.PushBuilder
主动将资源推送到客户端,并且它作为@RequestMapping
方法的方法参数而受支持。