Servlet Stack 上的 Web
SpringWebMVC
Spring Web MVC 是在 Servlet API 上构建的原始 web framework,并且从一开始就包含在 Spring Framework 中。形式 name,“ Spring Web MVC”来自其源模块(spring-webmvc)的 name,但它通常被称为“Spring MVC”。
Parallel 到 Spring Web MVC,Spring Framework 5.0 引入了一个 reactive-stack web framework,其 name,“ Spring WebFlux”也是基于它的源模块(spring-webflux)。本节介绍 Spring Web MVC。 下一节涵盖 Spring WebFlux。
有关基线信息以及与 Servlet 容器和 Java EE version 范围的兼容性,请参阅 Spring Framework 维基。
DispatcherServlet
Spring MVC 和许多其他 web 框架一样,是围绕前端控制器 pattern 设计的,其中中心Servlet
,DispatcherServlet
为请求处理提供了共享算法,而实际工作则由可配置的委托组件执行。这个 model 非常灵活,支持各种工作流程。
DispatcherServlet
,与任何Servlet
一样,需要使用 Java configuration 或web.xml
根据 Servlet 规范进行声明和映射。反过来,DispatcherServlet
使用 Spring configuration 来发现请求映射,视图解析,异常处理,和更多所需的委托组件。
下面的 Java configuration 示例注册并初始化DispatcherServlet
,
1 | public class MyWebApplicationInitializer implements WebApplicationInitializer { |
除了直接使用 ServletContext API 之外,您还可以扩展
AbstractAnnotationConfigDispatcherServletInitializer
并覆盖特定方法(请参阅Context 层次结构下的 example)。
以下web.xml
configuration 的 example 注册并初始化DispatcherServlet
:
1 | <web-app> |
Spring Boot 遵循不同的初始化顺序。 Spring Boot 使用 Spring configuration 来引导自身和嵌入的 Servlet 容器,而不是挂钩 Servlet 容器的生命周期。在 Spring configuration 中检测到
Filter
和Servlet
声明,并在 Servlet 容器中注册。有关更多详细信息,请参阅Spring Boot 文档。
Context层次结构
DispatcherServlet
期望WebApplicationContext
(普通ApplicationContext
的扩展名)用于自己的 configuration。 WebApplicationContext
有一个指向ServletContext
和Servlet
的链接。它也绑定到ServletContext
,这样 applications 可以使用RequestContextUtils
上的静态方法查找WebApplicationContext
,如果它们需要访问它。
对于许多 applications 来说,拥有一个WebApplicationContext
很简单而且足够了。也可以有一个 context 层次结构,其中一个根WebApplicationContext
在多个DispatcherServlet
(或其他Servlet
)实例之间共享,每个实例都有自己的 child WebApplicationContext
configuration。有关 context 层次结构 feature 的更多信息,请参阅ApplicationContext 的附加功能。
根WebApplicationContext
通常包含基础结构 beans,例如需要跨多个Servlet
实例共享的数据存储库和业务服务。那些 beans 是有效继承的,可以在 Servlet-specific child WebApplicationContext
中覆盖(即 re-declared),它通常包含给定Servlet
本地的 beans。下图显示了这种关系:
以下 example 配置WebApplicationContext
层次结构:
1 | public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { |
如果不需要 application context 层次结构,applications 可以
以下 example 显示web.xml
等效项:
1 | <web-app> |
如果不需要 application context 层次结构,则 applications 可以仅配置“root”context 并将
contextConfigLocation
Servlet 参数保留为空。
特殊的Bean类型
DispatcherServlet
委托特殊的 beans 来处理请求并呈现适当的响应。 “special beans”是指实现 WebFlux framework contracts 的 Spring-managed Object
实例。那些通常带有 built-in contracts,但您可以自定义它们的 properties 并扩展或替换它们。
以下 table lists 列出DispatcherHandler
检测到的特殊 beans:
Bean 类型 | 说明 |
---|---|
HandlerMapping |
将一个请求映射到一个处理程序以及一个拦截器列表,用于 pre-和 post-processing。映射基于某些标准,其详细信息因HandlerMapping implementation 而异。 两个主要的HandlerMapping implementations 是RequestMappingHandlerMapping (它支持@RequestMapping 带注释的方法)和SimpleUrlHandlerMapping (它维护 URI 路径模式到处理程序的显式注册)。 |
HandlerAdapter |
无论实际调用处理程序如何,都帮助DispatcherServlet 调用映射到请求的处理程序。例如,调用带注释的控制器需要解析 annotations。 HandlerAdapter 的主要目的是保护DispatcherServlet 免受这些细节的影响。 |
HandlerExceptionResolver | 解决Spring MVC引发的异常,并将其映射到HTTP状态码。请参阅备用的ResponseEntityExceptionHandler和REST API异常。 |
视图解析器 | 将从处理程序返回的逻辑String -based 视图名称解析为实际View ,以便将其呈现给响应。见视图解析和View技术。 |
LocaleResolver, LocaleContextResolver | 在 order 中解析Locale a client 正在使用并且可能是 time zone,以便能够提供国际化视图。见locale。 |
ThemeResolver | 解析 web application 可以使用的主题 - 例如,提供个性化布局。见主题。 |
MultipartResolver | 在一些 multipart 解析 library 的帮助下,解析 multi-part 请求(用于 example,浏览器表单文件上传)的抽象。见Multipart Resolver。 |
FlashMapManager | Store 并检索“输入”和“输出”FlashMap ,它们可用于将属性从一个请求传递到另一个请求,通常是通过重定向。见Flash 属性。 |
WebMVC配置
应用程序可以声明在处理请求所需的特殊Bean类型中列出的基础设施Bean。DispatcherServlet检查每个特殊 bean 的
WebApplicationContext`。如果没有匹配的 bean 类型,则它会回退到DispatcherServlet.properties中列出的默认类型。
在大多数情况下,MVC 配置是最好的起点。它在 Java 或 XML 中声明了所需的 beans,并提供了 higher-level configuration 回调 API 来自定义它。
Spring Boot 依赖于 MVC Java configuration 来配置 Spring MVC 并提供许多额外的方便选项。
Servlet配置
在 Servlet 3.0 环境中,您可以选择以编程方式配置 Servlet 容器作为替代方法或与web.xml
文件组合使用。以下 example 注册DispatcherServlet
:
1 | import org.springframework.web.WebApplicationInitializer; |
WebApplicationInitializer
是 Spring MVC 提供的接口,可确保检测到 implementation 并自动用于初始化任何 Servlet 3 容器。名为AbstractDispatcherServletInitializer
的WebApplicationInitializer
的抽象 base class implementation 使得通过重写方法注册DispatcherServlet
更容易,以指定 servlet 映射和DispatcherServlet
configuration 的位置。
对于使用 Java-based Spring configuration 的 applications,建议使用此方法,如下面的 example 所示:
1 | public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { |
如果使用 XML-based Spring configuration,则应直接从AbstractDispatcherServletInitializer
扩展,如下面的 example 所示:
1 | public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { |
AbstractDispatcherServletInitializer
还提供了添加Filter
实例的便捷方式,并将它们自动映射到DispatcherServlet
,如下面的 example 所示:
1 | public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { |
每个过滤器根据其具体类型添加默认 name 并自动映射到DispatcherServlet
。
AbstractDispatcherServletInitializer
的isAsyncSupported
protected 方法提供了一个单独的位置来启用DispatcherServlet
上的异步支持以及映射到它的所有过滤器。默认情况下,此 flag 设置为true
。
最后,如果您需要进一步自定义DispatcherServlet
本身,则可以覆盖createDispatcherServlet
方法。
Processing
DispatcherServlet
处理请求如下:
- 在请求中搜索并绑定
WebApplicationContext
作为控制器和 process 中的其他元素可以使用的属性。它默认绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
key 下。 - locale 解析器绑定到请求,以便让 process 中的元素解析处理请求时使用的 locale(呈现视图,准备数据等)。如果您不需要 locale 解析,则不需要 locale 解析器。
- 主题解析器绑定到请求,以允许视图等元素确定要使用的主题。如果您不使用主题,则可以忽略它。
- 如果指定 multipart 文件解析程序,则会检查请求的多部分。如果找到多部分,请求将包装在
MultipartHttpServletRequest
中,以供 process 中的其他元素进一步处理。有关 multipart 处理的更多信息,请参见Multipart Resolver。 - 搜索适当的处理程序。如果找到了处理程序,则在 order 中执行与处理程序关联的执行链(预处理程序,后处理程序和控制器)以准备 model 或呈现。或者,对于带注释的控制器,可以呈现响应(在
HandlerAdapter
内)而不是返回视图。 - 如果返回 model,则呈现视图。如果没有返回 model(可能是由于预处理器或后处理器拦截请求,可能是出于安全原因),则不会呈现任何视图,因为该请求可能已经完成。
在WebApplicationContext
中声明的HandlerExceptionResolver
beans 用于解决在请求处理期间抛出的 exceptions。那些 exception 解析器允许自定义逻辑来解决 exceptions。有关详细信息,请参阅Exceptions。
Spring DispatcherServlet
还支持last-modification-date
的 return,由 Servlet API 指定。确定特定请求的最后修改 date 的 process 很简单:DispatcherServlet
查找适当的处理程序映射并测试找到的处理程序是否实现了LastModified
接口。如果是这样,LastModified
接口的long getLastModified(request)
方法的 value 将返回到 client。
您可以通过将 Servlet 初始化参数(init-param
元素)添加到web.xml
文件中的 Servlet 声明来自定义单个DispatcherServlet
实例。以下 table lists 支持的参数:
参数 | 说明 |
---|---|
contextClass |
实现ConfigurableWebApplicationContext 的 Class,由此 Servlet 实例化并本地配置。默认情况下,使用XmlWebApplicationContext 。 |
contextConfigLocation |
String 传递给 context 实例(由contextClass 指定)以指示可以找到上下文的位置。 string 可能包含多个 strings(使用逗号作为分隔符)以支持多个上下文。如果多个 context 位置的 beans 定义了两次,则最新位置优先。 |
namespace |
WebApplicationContext 的命名空间。默认为[servlet-name]-servlet 。 |
throwExceptionIfNoHandlerFound |
当没有找到请求的处理程序时是否抛出NoHandlerFoundException 。然后可以使用HandlerExceptionResolver (对于 example,使用@ExceptionHandler 控制器方法)捕获 exception,并像处理其他任何方法一样处理。 默认情况下,它设置为false ,在这种情况下DispatcherServlet 将响应状态设置为 404(NOT_FOUND)而不引发 exception。 请注意,如果还配置了默认 servlet 处理,则始终将未解析的请求转发到默认的 servlet,并且永远不会引发 404。 |
Interception
所有HandlerMapping
implementations 都支持处理程序拦截器,当您想要将特定功能应用于某些请求时,这些拦截器非常有用 - 例如,检查主体。拦截器必须使用三种方法从org.springframework.web.servlet
包中实现HandlerInterceptor
,这些方法应该提供足够的灵活性来执行各种 pre-processing 和 post-processing:
preHandle(..)
:在执行实际处理程序之前postHandle(..)
:处理程序执行后afterCompletion(..)
:完成请求完成后
preHandle(..)
方法返回 boolean value。您可以使用此方法 break 或继续处理执行链。当此方法返回true
时,处理程序执行链继续。当它返回 false 时,DispatcherServlet
假定拦截器本身已处理请求(并且,对于 example,呈现了适当的视图)并且不继续执行其他拦截器和执行链中的实际处理程序。
有关如何配置拦截器的示例,请参阅 MVC configuration 一节中的拦截器。您也可以使用单个HandlerMapping
实现上的 setter 直接注册它们。
请注意,对于@ResponseBody
和ResponseEntity
方法,postHandle
不太有用,在HandlerAdapter
和postHandle
之前写入并提交响应。这意味着对响应进行任何更改都为时已晚,例如添加额外的标头。对于这种情况,您可以实现ResponseBodyAdvice
并将其声明为Controller Advice bean 或直接在RequestMappingHandlerAdapter
上进行配置。
Exceptions
如果在请求映射期间发生异常或从请求处理程序(例如@Controller
)抛出,则DispatcherServlet
委托给HandlerExceptionResolver
beans 链来解析异常并提供替代处理,这通常是错误响应。
以下 table lists 可用HandlerExceptionResolver
实现:
HandlerExceptionResolver |
描述 |
---|---|
SimpleMappingExceptionResolver |
exception class 名称和错误视图名称之间的映射。用于在浏览器 application 中呈现错误页面。 |
DefaultHandlerExceptionResolver | 解析 Spring MVC 引发的 exceptions,并将它们 maps 转换为 HTTP 状态代码。另请参阅替代ResponseEntityExceptionHandler 和REST API exceptions。 |
ResponseStatusExceptionResolver |
使用@ResponseStatus annotation 解析 exceptions,并根据 annotation 中的 value 将它们 maps 到 HTTP 状态代码。 |
ExceptionHandlerExceptionResolver |
通过在@Controller 或@ControllerAdvice class 中调用@ExceptionHandler 方法来解析 exceptions。见@ExceptionHandler 方法。 |
解析器链
您可以通过在 Spring configuration 中声明多个HandlerExceptionResolver
beans 并根据需要设置其order
properties 来形成 exception 解析程序链。 order property 越高,exception 解析器的位置越晚。
的 contract 指定它可以 return:
- a
ModelAndView
指向错误视图。 - 如果在解析器中处理了 exception,则返回空
ModelAndView
。 null
如果 exception 仍未解析,则后续解析器尝试,如果 exception 仍然在结尾,则允许冒泡到 Servlet 容器。
MVC 配置自动声明 built-in 解析器用于默认的 Spring MVC exceptions,用于@ResponseStatus
annotated exceptions,以及支持@ExceptionHandler
方法。您可以自定义该列表或替换它。
容器错误页面
如果任何HandlerExceptionResolver
仍然无法解析 exception,并且因此将其传播或者如果响应状态设置为错误状态(即 4xx,5xx),则 Servlet 容器可以呈现 HTML 中的默认错误页面。要自定义容器的默认错误页面,可以在web.xml
中声明错误页面映射。以下 example 显示了如何执行此操作:
1 | <error-page> |
给定前面的 example,当 exception 冒泡或响应具有错误状态时,Servlet 容器会在容器内对配置的 URL 进行 ERROR 调度(对于 example,/error
)。然后由DispatcherServlet
处理,可能将其映射到@Controller
,可以使用 model 实现 return 错误视图 name 或呈现 JSON 响应,如下面的 example 所示:
1 |
|
Servlet API 不提供在 Java 中创建错误页面映射的方法。但是,您可以使用
WebApplicationInitializer
和最小web.xml
。
View解析器
Spring MVC 定义了ViewResolver
和View
接口,使您可以在浏览器中呈现模型,而无需将您与特定的视图技术联系起来。 ViewResolver
提供视图名称和实际视图之间的映射。 View
解决了在移交给特定视图技术之前的数据准备工作。
以下 table 提供了有关ViewResolver
层次结构的更多详细信息:
视图解析器 | 描述 |
---|---|
AbstractCachingViewResolver |
他们解析的AbstractCachingViewResolver 个AbstractCachingViewResolver 缓存视图实例。缓存提高了某些视图技术的性能。您可以通过将cache property 设置为false 来关闭缓存。此外,如果必须在运行时刷新某个视图(对于 example,当修改 FreeMarker 模板时),可以使用removeFromCache(String viewName, Locale loc) 方法。 |
XmlViewResolver |
实现ViewResolver 接受用 XML 编写的 configuration 文件,其中包含与 Spring 的 XML bean 工厂相同的 DTD。默认的 configuration 文件是/WEB-INF/views.xml 。 |
ResourceBundleViewResolver |
的实现,在ResourceBundle 中使用 bean 定义,由 bundle base name 指定。对于它应该解析的每个视图,它使用 property [viewname].(class) 的 value 作为 view class,使用 property [viewname].url 的 value 作为视图 URL。您可以在View技术章节中找到示例。 |
UrlBasedViewResolver |
ViewResolver 接口的简单 implementation,它影响逻辑视图名称直接解析为没有显式映射定义的 URL。如果您的逻辑名称以直接的方式匹配视图资源的名称,而不需要任意映射,这是合适的。 |
InternalResourceViewResolver |
方便的UrlBasedViewResolver 子类,支持InternalResourceView (实际上是 Servlets 和 JSP)和子类,如JstlView 和TilesView 。您可以使用setViewClass(..) 为此解析程序生成的所有视图指定视图 class。有关详细信息,请参阅作为 UrlBasedViewResolver javadoc。 |
FreeMarkerViewResolver |
方便的UrlBasedViewResolver 子类,支持FreeMarkerView 及其自定义子类。 |
ContentNegotiatingViewResolver |
根据请求文件 name 或Accept 标头解析视图的ViewResolver 接口的实现。见内容协商。 |
Handling
您可以通过声明多个解析程序 bean 来链接视图解析器,并在必要时通过设置order
property 来指定 ordering。请记住,order property 越高,视图解析器在链中的位置越晚。
的 contract 指定它可以 return null 以指示无法找到该视图。但是,对于 JSP 和InternalResourceViewResolver
,确定 JSP 是否存在的唯一方法是通过RequestDispatcher
执行调度。因此,您必须始终将InternalResourceViewResolver
配置为视图解析器的整个 order 中的最后一个。
配置视图解析器就像在 Spring configuration 中添加ViewResolver
beans 一样简单。 MVC 配置为查看解析器提供专用的 configuration API,并添加 logic-less 查看控制器,这对于没有控制器逻辑的 HTML 模板渲染非常有用。
重定向
视图 name 中的特殊redirect:
前缀允许您执行重定向。 UrlBasedViewResolver
(及其子类)将此识别为需要重定向的指令。视图 name 的 rest 是重定向 URL。
净效果与控制器返回RedirectView
的效果相同,但现在控制器本身可以根据逻辑视图名称进行操作。逻辑视图 name(例如redirect:/myapp/some/resource
)相对于当前 Servlet context 重定向,而 name 等 name 重定向到绝对 URL。
请注意,如果使用@ResponseStatus
注释控制器方法,则 annotation value 优先于RedirectView
设置的响应状态。
转发
您还可以为最终由UrlBasedViewResolver
和子类解析的视图名称使用特殊的forward:
前缀。这会创建一个InternalResourceView
,它会执行RequestDispatcher.forward()
。因此,此前缀对于InternalResourceViewResolver
和InternalResourceView
(对于 JSP)没有用,但如果您使用其他视图技术但仍希望强制由 Servlet/JSP 引擎处理资源的转发,则它可能会有所帮助。请注意,您也可以链接多个视图解析器。
内容协商
ContentNegotiatingViewResolver不解析视图本身,而是委托给其他视图解析器,并选择类似于 client 请求的表示的视图。可以从Accept
标头或查询参数(对于 example,"/path?format=pdf"
)确定表示。
ContentNegotiatingViewResolver
通过将请求媒体类型与所支持的媒体类型(也称为Content-Type
)进行比较来选择适当的View
来处理请求。列表中具有兼容Content-Type
的第一个View
将表示返回给 client。如果ViewResolver
链不能提供兼容的视图,则会查阅通过DefaultViews
property 指定的视图列表。后一个选项适用于 singleton Views
,它可以呈现当前资源的适当表示,而不管逻辑视图 name。 Accept
标头可以包含通配符(对于 example text/*
),在这种情况下,View
Accept
是text/xml
是兼容的 match。
有关 configuration 详细信息,请参阅MVC 配置下的视图解析器。
Locale
Spring 的 architecture 的大多数部分都支持国际化,就像 Spring web MVC framework 那样。 DispatcherServlet
允许您使用 client 的 locale 自动解析消息。这是通过LocaleResolver
objects 完成的。
当一个请求进来时,DispatcherServlet
会查找一个 locale 解析器,如果找到一个,它会尝试使用它来设置 locale。通过使用RequestContext.getLocale()
方法,您始终可以检索 locale 解析程序解析的 locale。
除了自动 locale 解析之外,您还可以将拦截器附加到处理程序映射(有关处理程序映射拦截器的更多信息,请参阅截击)以在特定情况下更改 locale(对于 example,基于请求中的参数)。
Locale 解析器和拦截器在org.springframework.web.servlet.i18n
包中定义,并以正常方式在 application context 中配置。 _Spoc 包含以下选择的 locale 解析器。
时区
除了获取 client 的 locale 之外,知道它的 time zone 通常很有用。 LocaleContextResolver
接口提供了LocaleResolver
的扩展,允许解析器提供更丰富的LocaleContext
,其中可能包含 time zone 信息。
可用时,可以使用RequestContext.getTimeZone()
方法获取用户的TimeZone
。 Time zone 信息由 Spring 的ConversionService
注册的任何 Date/Time Converter
和Formatter
objects 自动使用。
Header解析器
这个 locale 解析器检查 client 发送的请求中的accept-language
标头(对于 example,一个 web 浏览器)。通常,此头字段包含 client 操作系统的 locale。请注意,此解析程序不支持 time zone 信息。
Cookie解析器
此 locale 解析程序检查 client 上可能存在的Cookie
,以查看是否指定了Locale
或TimeZone
。如果是,则使用指定的详细信息。通过使用此 locale 解析程序的 properties,您可以指定 cookie 的 name 以及最大年龄。以下 example 定义CookieLocaleResolver
:
1 | <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"> |
以下 table 描述 properties CookieLocaleResolver
:
属性 | 默认 | 描述 |
---|---|---|
cookieName |
classname LOCALE | cookie 的 name |
cookieMaxAge |
Servlet 容器默认 | client 上_chokie 持续的最大 time 时间。如果指定了-1 ,则不会保留 cookie。它仅在 client 关闭浏览器之前可用。 |
cookiePath |
/ | 限制 cookie 对您网站某个部分的可见性。指定cookiePath 时,cookie 仅对该路径及其下方的_path 可见。 |
Session解析器
SessionLocaleResolver
允许您从 session 中检索可能与用户请求相关联的Locale
和TimeZone
。与CookieLocaleResolver
相反,此策略存储 Servlet 容器的HttpSession
中本地选择的 locale 设置。因此,这些设置对于每个 session 都是临时的,因此在每个 session 终止时都会丢失。
请注意,与外部 session management 机制没有直接关系,例如 Spring Session 项目。此SessionLocaleResolver
针对当前HttpServletRequest
评估和修改相应的HttpSession
属性。
Locale拦截器
您可以通过将LocaleChangeInterceptor
添加到HandlerMapping
定义之一来启用区域设置的更改。它检测请求中的参数并相应地更改 locale,在调度程序的 application context 中调用LocaleResolver
上的setLocale
方法。下一个 example 显示 calls 到包含名为siteLanguage
的参数的所有*.view
资源现在更改 locale。因此,对于 example,对 URL 的请求http://www.sf.net/home.view?siteLanguage=nl
会将网站语言更改为荷兰语。以下 example 显示了如何拦截 locale:
1 | <bean id="localeChangeInterceptor" |
主题
您可以应用 Spring Web MVC framework 主题来设置 application 的整体 look-and-feel,从而增强用户体验。主题是静态资源的集合,通常是样式表和图像,它们会影响 application 的视觉样式。
定义主题
要在 web application 中使用主题,必须设置org.springframework.ui.context.ThemeSource
接口的 implementation。 WebApplicationContext
接口扩展ThemeSource
但将其职责委托给专用的 implementation。默认情况下,委托是一个org.springframework.ui.context.support.ResourceBundleThemeSource
implementation,它从 classpath 的根目录加载 properties files。要使用自定义ThemeSource
implementation 或配置ResourceBundleThemeSource
的基本 name 前缀,可以在 application context 中使用 reserved name,themeSource
注册 bean。 web application context 会自动检测具有该 name 的 bean 并使用它。
使用ResourceBundleThemeSource
时,主题在简单的 properties 文件中定义。 properties 文件列出构成主题的资源,如下面的 example 所示:
1 | styleSheet=/themes/cool/style.css |
properties 的键是引用 view code 中的主题元素的名称。对于 JSP,通常使用spring:theme
自定义标记执行此操作,该标记与spring:message
标记非常相似。以下 JSP 片段使用前一个 example 中定义的主题来自定义外观:
1 | <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> |
默认情况下,ResourceBundleThemeSource
使用空基 name 前缀。因此,properties files 从 classpath 的根加载。因此,您可以将cool.properties
主题定义放在 classpath 根目录的目录中(对于 example,在/WEB-INF/classes
中)。 ResourceBundleThemeSource
使用标准的 Java 资源包 loading 机制,允许主题的完全国际化。对于 example,我们可以使用引用带有荷兰文本的特殊背景图像。
解决主题
定义主题后,如前一节中所述,您可以决定使用哪个主题。 DispatcherServlet
查找名为themeResolver
的 bean,以找出要使用的ThemeResolver
implementation。主题解析器的工作方式与LocaleResolver
非常相似。它检测用于特定请求的主题,还可以更改请求的主题。以下 table 描述了 Spring 提供的主题解析器:
类 | 描述 |
---|---|
FixedThemeResolver |
选择使用defaultThemeName property 设置的固定主题。 |
SessionThemeResolver |
主题在用户的 HTTP session 中维护。它只需要为每个 session 设置一次,但不会在会话之间保留。 |
CookieThemeResolver |
所选主题存储在 client 上的 cookie 中。 |
Spring 还提供了一个ThemeChangeInterceptor
,它允许使用简单的请求参数对每个请求进行主题更改。
Multipart解析器
来自org.springframework.web.multipart
包的MultipartResolver
是一种解析 multipart 请求(包括文件上载)的策略。有一个基于Commons _FileUpload的 实现和另一个基于 Servlet 3.0 multipart 请求解析的实现。
要启用 multipart 处理,您需要在DispatcherServlet
Spring configuration 中使用 name multipartResolver
声明MultipartResolver
bean。 DispatcherServlet
检测到它并将其应用于传入请求。当收到 content-type 为multipart/form-data
的 POST 时,解析器会解析内容并将当前HttpServletRequest
包装为MultipartHttpServletRequest
,以提供对已解析部分的访问,并将其作为请求参数公开。
Apache常规FileUpload
要使用 Apache Commons FileUpload
,您可以使用 name multipartResolver
配置类型的 bean。您还需要将commons-fileupload
作为 classpath 的依赖项。
Servlet3.0
需要通过 Servlet 容器 configuration 启用 Servlet 3.0 multipart 解析。为此:
- 在 Java 中,在 Servlet 注册上设置
MultipartConfigElement
。 - 在
web.xml
中,将"<multipart-config>"
部分添加到 servlet 声明中。
以下 example 显示了如何在 Servlet 注册上设置MultipartConfigElement
:
1 | public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { |
一旦 Servlet 3.0 configuration 到位,就可以添加一个StandardServletMultipartResolver
类型的 bean,其 name 为multipartResolver
。
Logging
Spring MVC 中的 DEBUG-level logging 设计为紧凑,最小和 human-friendly。它侧重于反复使用的 high-value 位信息,而只有在调试特定问题时才有用。
TRACE-level logging 通常遵循与 DEBUG 相同的原则(并且,例如,也不应该是消防水管),但可以用于调试任何问题。此外,一些 log 消息可能在 TRACE 与 DEBUG 中显示不同的级细节。
好 logging 来自使用日志的经验。如果您发现任何不符合既定目标的事件,请告诉我们。
敏感数据
DEBUG 和 TRACE logging 可以 log 敏感信息。这就是默认情况下屏蔽请求参数和 headers 的原因,并且必须通过DispatcherServlet
上的enableLoggingRequestDetails
property 显式启用它们的 logging。
以下 example 显示了如何使用 Java configuration 执行此操作:
1 | public class MyInitializer |
过滤器
spring-web
模块提供了一些有用的过滤器:
表单数据
浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据,但 non-browser clients 也可以使用 HTTP PUT,PATCH 和 DELETE。 Servlet API 需要ServletRequest.getParameter*()
方法才支持仅对 HTTP POST 进行表单字段访问。
spring-web
模块提供FormContentFilter
来拦截带有 content type application/x-www-form-urlencoded
的 HTTP PUT,PATCH 和 DELETE 请求,从请求正文中读取表单数据,并包装ServletRequest
以通过ServletRequest.getParameter*()
方法系列提供表单数据。
转发Headers
当请求通过代理(例如负载平衡器)时,host, port 和 scheme 可能会发生变化,这使得从 client 透视图创建指向正确的 host,port 和 scheme 的链接成为一项挑战。
RFC 7239定义了代理可用于提供有关原始请求的信息的Forwarded
HTTP 标头。还有其他 non-standard headers,包括X-Forwarded-Host
,X-Forwarded-Port
,X-Forwarded-Proto
,X-Forwarded-Ssl
和X-Forwarded-Prefix
。
ForwardedHeaderFilter
是一个 Servlet 过滤器,它根据Forwarded
headers 修改请求的 host,port 和 scheme,然后删除那些 headers。
转发 headers 有安全注意事项,因为 application 无法知道代理是否按预期添加了 headers,还是恶意 client。这就是为什么应该配置信任边界的代理来删除来自外部的不受信任的Forwarded
_header。您还可以使用removeOnly=true
配置ForwardedHeaderFilter
,在这种情况下,它会删除但不使用 headers。
浅ETag
ShallowEtagHeaderFilter
过滤器通过缓存写入响应的内容并从中计算 MD5 哈希来创建“浅”ETag。 _ 客户端发送的下一个 time,它也会相同,但它也会将计算出的 value 与If-None-Match
请求标头进行比较,如果两者相等,则返回 304(NOT_MODIFIED)。
此策略可节省网络带宽,但不能节省 CPU,因为必须为每个请求计算完整响应。前面描述的控制器 level 的其他策略可以避免计算。见HTTP 缓存。
此过滤器具有writeWeakETag
参数,该参数将过滤器配置为写入弱 ETag,类似于以下内容:W/"02a2d595e6ed9a0b24f027f2b63b134d6"
(在RFC 7232 第 2.3 节中定义)。
为了支持异步请求,此筛选器必须与DispatcherType.ASYNC
进行映射。异步,以便过滤器能够延迟并成功地生成一个ETag,直到最后一个异步分派的结束。如果使用Spring框架的AbstractAnnotationConfigDispatcherServletInitializer
(参见Servlet配置),那么所有的过滤器都会自动为所有的分派类型注册。但是,如果通过web.xml
或在Spring引导中通过FilterRegistrationBean
注册过滤器,请确保包括DispatcherType.ASYNC
。
CORS
Spring MVC 通过控制器上的 annotations 为 CORS configuration 提供 fine-grained 支持。但是,当与 Spring Security 一起使用时,我们建议依赖于必须在 Spring Security 的过滤器链之前排序的 built-in CorsFilter
。
带注解的控制器
Spring MVC 提供 annotation-based 编程 model,其中@Controller
和@RestController
组件使用 annotations 来表达请求映射,请求输入,exception 处理等。带注释的控制器具有灵活的方法签名,不必扩展 base classes,也不必实现特定的接口。以下 example 显示了 annotations 定义的控制器:
1 |
|
在前面的 example 中,该方法接受Model
并将 view name 作为String
返回,但是存在许多其他选项,本章稍后将对此进行说明。
上的指南和教程使用本节中描述的 annotation-based 编程模型。
宣言
您可以使用 Servlet 的WebApplicationContext
中的标准 Spring bean 定义来定义 controller beans。 @Controller
构造型允许 auto-detection,与 Spring 一致支持,用于检测 classpath 中的@Component
classes 和 auto-registering bean 定义。它还充当带注释的 class 的构造型,表明它作为 web component 的角色。
要启用 auto-detection 这样的@Controller
beans,可以将 component 扫描添加到 Java configuration 中,如下面的 example 所示:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 |
|
@RestController
是一个复合注解本身 meta-annotated,@Controller
和@ResponseBody
表示一个控制器,其每个方法都继承 type-level @ResponseBody
注释,因此,直接写入响应主体与视图分辨率和使用 HTML 模板呈现。
AOPProxies
在某些情况下,您需要在运行时使用 AOP 代理装饰控制器。一个示例是您选择直接在控制器上使用@Transactional
annotations。在这种情况下,对于控制器,我们建议使用 class-based 代理。这通常是控制器的默认选择。但是,如果控制器必须实现不是 Spring Context 回调的接口(例如InitializingBean
,*Aware
和其他),则可能需要显式配置 class-based 代理。对于 example,使用<tx:annotation-driven/>
,您可以更改为<tx:annotation-driven proxy-target-class="true"/>
。
请求映射
您可以使用@RequestMapping
annotation 来向控制器方法发送 map 请求。它具有 match 的各种属性,包括 URL,HTTP 方法,请求参数,headers 和媒体类型。您可以在 class level 中使用它来表示共享映射,或者在方法 level 中使用它来缩小到特定的端点映射。
还有@RequestMapping
的 HTTP 方法特定快捷方式变体:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
提供的快捷方式是自定义注解,因为可以说,大多数控制器方法应该映射到特定的 HTTP 方法而不是使用@RequestMapping
,默认情况下,它与所有 HTTP 方法匹配。同时,class level 仍然需要@RequestMapping
来表示共享映射。
以下 example 具有类型和方法 level 映射:
1 |
|
URI模式
您可以使用以下 glob 模式和通配符 map 请求:
?
匹配一个字符*
匹配路径段中的零个或多个字符**
匹配零个或多个路径段
您还可以声明 URI 变量并使用@PathVariable
访问它们的值,如下面的 example 所示:
1 |
|
您可以在 class 和方法级别声明 URI 变量,如下面的 example 所示:
1 |
|
URI 变量会自动转换为适当的类型,或者引发TypeMismatchException
。默认情况下支持简单类型(int
,long
,Date
等),您可以注册对任何其他数据类型的支持。见类型转换和DataBinder。
您可以显式 name URI 变量(对于 example,@PathVariable("customId")
),但如果名称相同并且您的 code 是使用调试信息编译的,或者使用 Java 8 上的-parameters
编译器 flag 编译,则可以保留该详细信息。
语法{varName:regex}
声明一个 URI 变量,其正则表达式的语法为{varName:regex}
。对于 example,给定 URL "/spring-web-3.0.5 .jar"
,以下方法提取 name,version 和文件扩展名:
1 |
|
URI 路径模式还可以嵌入${…}
占位符,这些占位符在启动时通过对本地,系统,环境和其他 property 源使用PropertyPlaceHolderConfigurer
来解析。例如,您可以使用此参数来基于某些外部配置参数化基本 URL。
Spring MVC 使用
PathMatcher
contract 和spring-core
implementation 来实现 URI 路径匹配。
Pattern比较
当多个模式匹配 URL 时,必须对它们进行比较以找到最佳的 match。这是通过使用AntPathMatcher.getPatternComparator(String path)
来完成的,它会查找更具体的模式。
如果 pattern 的 URI 变量数量较少而单个通配符计为 1 且 double 通配符计为 2,则 pattern 的特定性较低。给定相等的分数,选择较长的 pattern。给定相同的分数和长度,选择具有比通配符更多的 URI 变量的 pattern。
默认映射 pattern(/**
)从评分中排除,并始终排序到最后。此外,前缀模式(例如/public/**
)被认为不如没有 double 通配符的其他 pattern 特定。
有关完整的详细信息,请参阅AntPathMatcher中的AntPatternComparator,并记住您可以自定义和 PathMatcher 实现。请参阅 configuration 部分中的路径匹配。
后缀Match
默认情况下,Spring MVC 执行.*
后缀 pattern 匹配,以便映射到/person
的控制器也隐式映射到/person.*
。然后,文件扩展名用于解释请求的 content type 以用于响应(即,而不是Accept
标头) - 用于 example,/person.pdf
,/person.xml
等。
当浏览器用于发送难以一致解释的Accept
headers 时,以这种方式使用文件 extensions 是必要的。目前,这不再是必需品,使用Accept
标头应该是首选。
在 time 期间,文件 name extensions 的使用已经证明在各种方面存在问题。当使用 URI 变量,路径参数和 URI 编码进行覆盖时,它可能会导致歧义。关于 URL-based 授权和安全性的推理(更多细节见下一节)也变得更加困难。
要完全禁用文件 extensions,必须同时设置以下两项:
useSuffixPatternMatching(false)
,见PathMatchConfigurerfavorPathExtension(false)
,见ContentNegotiationConfigurer
URL-based content negotiation 仍然有用(例如,在浏览器中 typing 一个 URL 时)。为了实现这一点,我们建议使用查询 parameter-based 策略来避免文件 extensions 带来的大多数问题。或者,如果必须使用文件 extensions,请考虑通过ContentNegotiationConfigurer的mediaTypes
property 将它们限制为显式注册的 extensions 列表。
后缀匹配和RFD
反射文件下载(RFD)攻击类似于 XSS,因为它依赖于响应中反映的请求输入(对于 example,查询参数和 URI 变量)。但是,RFD 攻击不依赖于将 JavaScript 插入 HTML,而是依赖浏览器切换来执行下载,并在 double-clicked 之后将响应视为可执行脚本。
在 Spring MVC 中,@ResponseBody
和ResponseEntity
方法存在风险,因为它们可以呈现不同的内容类型,客户端可以通过 URL 路径 extensions 请求这些内容类型。禁用后缀 pattern 匹配并使用路径 extensions 进行内容协商可降低风险,但不足以防止 RFD 攻击。
为了防止 RFD 攻击,在呈现响应主体之前,Spring MVC 添加Content-Disposition:inline;filename=f.txt
标头以建议固定且安全的下载文件。仅当 URL 路径包含既未列入白名单也未明确注册内容 negotiation 的文件扩展名时,才会执行此操作。但是,当直接在浏览器中输入 URL 时,它可能会产生副作用。
默认情况下,许多 common path extensions 都列入白名单。具有自定义HttpMessageConverter
实现的应用程序可以显式注册文件 extensions 以进行内容 negotiation,以避免为这些 extensions 添加Content-Disposition
标头。见内容类型。
有关 RFD 的其他建议,请参阅CVE-2015-5211。
可消费的媒体类型
您可以根据请求的Content-Type
缩小请求映射,如下面的 example 所示:
1 | 1) ( |
1 | 使用consumes 属性通过 content type 缩小映射范围。 |
consumes
属性还支持否定表达式 - 对于 example,!text/plain
表示除text/plain
之外的任何 content type。
您可以在 class level 声明共享的consumes
属性。但是,与大多数其他 request-mapping 属性不同,当在 class level 中使用时,method-level consumes
属性会覆盖而不是扩展 class-level 声明。
MediaType
提供常用媒体类型的常量,例如APPLICATION_JSON_VALUE
和APPLICATION_XML_VALUE
。
可生产的媒体类型
您可以根据Accept
请求标头和控制器方法生成的内容类型列表缩小请求映射,如下面的示例所示:
1 | 1) ( |
1 | 使用produces 属性通过 content type 缩小映射范围。 |
媒体类型可以指定字符集。支持否定表达式 - 例如,!text/plain
表示除“text/plain”之外的任何 content type。
对于 JSON content type,即使RFC7159明确指出“没有为此注册定义 charset 参数”,也应指定 UTF-8 charset,因为某些浏览器要求它正确解释 UTF-8 特殊字符。
您可以在 class level 声明共享的produces
属性。但是,与大多数其他 request-mapping 属性不同,当在 class level 中使用时,method-level produces
属性会覆盖而不是扩展 class-level 声明。
MediaType
提供常用媒体类型的常量,例如APPLICATION_JSON_UTF8_VALUE
和APPLICATION_XML_VALUE
。
参数和Headers
您可以根据请求参数条件缩小请求映射。您可以测试是否存在请求参数(myParam
),缺少一个(!myParam
)或特定 value(myParam=myValue
)。以下 example 显示了如何测试特定的 value:
1 | 1) ( |
1 | 测试myParam 是否等于myValue 。 |
您还可以将其与请求标头条件一起使用,如下面的 example 所示:
1 | 1) ( |
1 | 测试myHeader 是否等于myValue 。 |
HTTP头,OPTIONS
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)透明地支持 HTTP HEAD 以进行请求映射。控制器方法无需更改。在javax.servlet.http.HttpServlet
中应用的响应 wrapper 确保将Content-Length
标头设置为写入的字节数(不实际写入响应)。
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)隐式映射到并支持 HTTP HEAD。处理 HTTP HEAD 请求就像它是 HTTP GET 一样,除了写入字体而不是写入主体,计算字节数并设置Content-Length
标头。
默认情况下,通过将Allow
响应标头设置为具有匹配的 URL 模式的所有@RequestMapping
方法中列出的 HTTP 方法列表来处理 HTTP OPTIONS。
对于没有 HTTP 方法声明的@RequestMapping
,Allow
标头设置为GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
。控制器方法应始终声明支持的 HTTP 方法(对于 example,使用 HTTP 方法特定的变体:@GetMapping
,@PostMapping
和其他)。
您可以将@RequestMapping
方法显式 map 映射到 HTTP HEAD 和 HTTP OPTIONS,但在 common 情况下这不是必需的。
自定义注解
Spring MVC 支持使用复合注释进行请求映射。这些是注释本身 meta-annotated 与@RequestMapping
并且用于重新声明@RequestMapping
属性的子集(或全部)以及更窄,更具体的目的。
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
和@PatchMapping
是组合注释的示例。提供它们是因为,可以说,大多数控制器方法应该映射到特定的 HTTP 方法而不是使用@RequestMapping
,默认情况下,它与所有 HTTP 方法匹配。如果您需要_notample of annotations,请查看如何声明它们。
Spring MVC 还支持使用自定义 request-matching 逻辑的自定义 request-mapping 属性。这是一个更高级的选项,需要子类化RequestMappingHandlerMapping
并覆盖getCustomMethodCondition
方法,您可以在其中检查自定义属性并 return 您自己的RequestCondition
。
显式注册
您可以以编程方式注册处理程序方法,可以将其用于动态注册或高级情况,例如不同 URL 下的同一处理程序的不同实例。以下 example 注册了一个处理程序方法:
1 |
|
1 | Inject 目标处理程序和控制器的处理程序映射。 |
2 | 准备映射元数据的请求。 |
3 | 获取处理程序方法。 |
4 | 添加注册。 |
处理程序方法
@RequestMapping
处理程序方法具有灵活的签名,可以从一系列受支持的控制器方法 arguments 和 return 值中进行选择。
方法Arguments
下一个 table 描述了支持的控制器方法 arguments。 参数不支持 Reactive 类型。
支持 JDK 8 的java.util.Optional
作为方法参数与具有required
属性(对于 example,@RequestParam
,@RequestHeader
和其他)的 annotations 结合使用,并且等同于required=false
。
控制器方法参数 | 描述 |
---|---|
WebRequest , NativeWebRequest |
无需直接使用 Servlet API 即可访问请求参数以及 request 和 session 属性。 |
javax.servlet.ServletRequest , javax.servlet.ServletResponse |
选择任何特定请求或响应类型 - 对于 example,ServletRequest ,HttpServletRequest 或 Spring 的MultipartRequest ,MultipartHttpServletRequest 。 |
javax.servlet.http.HttpSession |
强制存在 session。因此,这样的论证永远不会null 。请注意,session 访问不是 thread-safe。如果允许多个请求同时访问 session,请考虑将RequestMappingHandlerAdapter 实例的synchronizeOnSession flag 设置为true 。 |
javax.servlet.http.PushBuilder |
Servlet 4.0 push builder API 用于程序化 HTTP/2 资源推送。请注意,根据 Servlet 规范,如果 client 不支持 HTTP/2 feature,则注入的PushBuilder 实例可以为 null。 |
java.security.Principal |
目前经过身份验证的用户 - 如果已知,可能是特定的Principal implementation class。 |
HttpMethod |
请求的 HTTP 方法。 |
java.util.Locale |
当前请求 locale,由最具体的LocaleResolver 可用(实际上是已配置的LocaleResolver 或LocaleContextResolver )确定。 |
java.util.TimeZone + java.time.ZoneId |
与当前请求关联的 time zone,由LocaleContextResolver 确定。 |
java.io.InputStream , java.io.Reader |
用于访问 Servlet API 公开的原始请求主体。 |
java.io.OutputStream , java.io.Writer |
用于访问 Servlet API 公开的原始响应主体。 |
@PathVariable |
用于访问 URI 模板变量。见URI 模式。 |
@MatrixVariable |
用于访问 URI 路径段中的 name-value 对。见矩阵变量。 |
@RequestParam |
用于访问 Servlet 请求参数,包括 multipart files。参数值将转换为声明的方法参数类型。见@RequestParam以及Multipart。 注意,对于简单的参数值,使用@RequestParam 是可选的。请参阅本 table 末尾的“任何其他参数”。 |
@RequestHeader |
用于访问请求 headers。标头值将转换为声明的方法参数类型。见@RequestHeader。 |
@CookieValue |
访问 cookies。 Cookies 值将转换为声明的方法参数类型。见@CookieValue。 |
@RequestBody |
用于访问 HTTP 请求正文。使用HttpMessageConverter 实现将正文内容转换为声明的方法参数类型。见@RequestBody。 |
HttpEntity<B> |
用于访问请求 headers 和 body。身体用HttpMessageConverter 转换。见HttpEntity。 |
@RequestPart |
要访问multipart/form-data 请求中的零件,请使用HttpMessageConverter 转换零件的主体。见Multipart。 |
java.util.Map , org.springframework.ui.Model , org.springframework.ui.ModelMap |
用于访问 HTML 控制器中使用的 model,并作为视图呈现的一部分向模板公开。 |
RedirectAttributes |
指定在重定向(即,要附加到查询 string)的情况下使用的属性,以及临时存储的 flash 属性,直到重定向后的请求为止。见重定向属性和Flash 属性。 |
@ModelAttribute |
用于访问 model 中的现有属性(如果不存在则实例化),并应用数据 binding 和验证。见@ModelAttribute以及模型和DataBinder。 请注意,使用@ModelAttribute 是可选的(对于 example,设置其属性)。请参阅本 table 末尾的“任何其他参数”。 |
Errors , BindingResult |
用于访问命令 object(即@ModelAttribute 参数)的验证和数据 binding 的错误,或者验证@RequestBody 或@RequestPart arguments 的错误。您必须在经过验证的方法参数后立即声明Errors 或BindingResult 参数。 |
SessionStatus class-level @SessionAttributes |
用于标记表单处理完成,它触发通过 class-level @SessionAttributes 注释声明的 session 属性的清除。有关详细信息,请参阅@SessionAttributes。 |
UriComponentsBuilder |
用于准备相对于当前请求的 host, port,scheme,context 路径和 servlet 映射的文字部分的 URL。见URI 链接。 |
@SessionAttribute |
用于访问任何 session 属性,与 session 属性存储在 session 中的 model 属性相反。有关详细信息,请参阅@SessionAttribute。 |
@RequestAttribute |
用于访问请求属性。有关详细信息,请参阅@RequestAttribute。 |
任何其他论点 | 如果方法参数与此 table 中的任何早期值不匹配,并且它是一个简单类型(由BeanUtils 的#isSimpleProperty确定,则将其解析为@RequestParam 。否则,它将被解析为@ModelAttribute 。 |
Return值
下一个 table 描述了支持的控制器方法 return 值。所有 return 值都支持 Reactive 类型。
控制器方法 return value | 描述 |
---|---|
@ResponseBody |
return value 通过HttpMessageConverter implementations 转换并写入响应。见@ResponseBody。 |
HttpEntity<B> , ResponseEntity<B> |
指定完整响应(包括 HTTP headers 和 body)的 return value 将通过HttpMessageConverter implementations 转换并写入响应。见ResponseEntity。 |
HttpHeaders |
用 headers 返回响应,没有正文。 |
String |
要使用ViewResolver implementations 解析并与隐式 model 一起使用的视图 name - 通过命令 objects 和@ModelAttribute 方法确定。处理程序方法还可以通过声明Model 参数(参见明确的注册)以编程方式丰富 model。 |
View |
一个View 实例,用于与隐式 model 一起呈现 - 通过命令 objects 和@ModelAttribute 方法确定。处理程序方法还可以通过声明Model 参数(参见明确的注册)以编程方式丰富 model。 |
java.util.Map , org.springframework.ui.Model |
要添加到隐式 model 的属性,其中 view name 通过RequestToViewNameTranslator 隐式确定。 |
@ModelAttribute |
要添加到 model 的属性,其中 view name 通过RequestToViewNameTranslator 隐式确定。 注意@ModelAttribute 是可选的。请参阅本 table 末尾的“任何其他 return value”。 |
ModelAndView object |
要使用的视图和 model 属性,以及可选的响应状态。 |
void |
具有void return 类型(或null return value)的方法如果还具有ServletResponse ,OutputStream 参数或@ResponseStatus 注释,则认为已完全处理了响应。如果控制器进行了正ETag 或lastModified 时间戳检查,那么同样也是 true(详见控制器)。 如果上面的 none 是 true,则void return 类型也可以为 REST 控制器指示“无响应主体”或为 HTML 控制器指示默认视图 name 选择。 |
DeferredResult<V> |
从任何线程异步生成任何前面的 return 值 - 对于 example,作为某些 event 或回调的结果。见异步请求和DeferredResult。 |
Callable<V> |
在 Spring MVC-managed 线程中异步生成上述任何 return 值。见异步请求和可赎回。 |
ListenableFuture<V> , java.util.concurrent.CompletionStage<V> , java.util.concurrent.CompletableFuture<V> |
作为方便的替代DeferredResult (例如,当底层服务返回其中一个时)。 |
ResponseBodyEmitter , SseEmitter |
异步发送 objects 流以使用HttpMessageConverter implementations 写入响应。也支持ResponseEntity 的主体。见异步请求和HTTP流。 |
StreamingResponseBody |
异步写入响应OutputStream 。也支持ResponseEntity 的主体。见异步请求和HTTP 流媒体。 |
Reactive 类型 - Reactor,RxJava 或其他通过ReactiveAdapterRegistry |
DeferredResult 替代 multi-value 流(对于 example,Flux ,Observable )收集到List 。 对于流方案(对于 example,text/event-stream ,application/json+stream ),使用SseEmitter 和ResponseBodyEmitter ,其中ServletOutputStream 阻塞 I/O 在 Spring MVC-managed 线程上执行,并且对每次写入完成施加背压。 见异步请求和Reactive 类型。 |
任何其他 return value | 任何 return value 不匹配此 table 中的任何早期值,并且或void 被视为视图 name(默认视图 name 选择通过RequestToViewNameTranslator 适用),前提是它不是一个简单类型,由BeanUtils 的#isSimpleProperty确定。简单类型的值仍未解决。 |
类型转换
如果参数声明为String
以外的其他内容,则表示String
-based 请求输入的某些带注释的控制器方法 arguments(例如@RequestParam
,@RequestHeader
,@PathVariable
,@MatrixVariable
和@CookieValue
)可能需要进行类型转换。
对于此类情况,将根据配置的转换器自动应用类型转换。默认情况下,支持简单类型(int
,long
,Date
和其他)。您可以通过WebDataBinder
(请参阅DataBinder)或使用FormattingConversionService
注册Formatters
来自定义类型转换。见Spring 字段格式。
矩阵变量
RFC 3986讨论路径段中的 name-value 对。在 Spring MVC 中,我们将它们称为基于 Tim Berners-Lee 的“老帖子”的“矩阵变量”,但它们也可以称为 URI 路径参数。
矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔(对于 example,/cars;color=red,green;year=2012
)。也可以通过重复的变量名称指定多个值(对于 example,color=red;color=green;color=blue
)。
如果 URL 预计包含矩阵变量,则控制器方法的请求映射必须使用 URI 变量来屏蔽该变量内容,并确保请求可以成功匹配,而与矩阵变量 order 和 presence 无关。以下 example 使用矩阵变量:
1 | // GET /pets/42;q=11;r=22 |
鉴于所有路径段都可能包含矩阵变量,您有时可能需要消除矩阵变量预期所在的路径变量的歧义。以下 example 显示了如何执行此操作:
1 | // GET /owners/42;q=11/pets/21;q=22 |
矩阵变量可以定义为可选,并指定默认 value,如下面的 example 所示:
1 | // GET /pets/42 |
要获取所有矩阵变量,可以使用MultiValueMap
,如下面的 example 所示:
1 | // GET /owners/42;q=11;r=12/pets/21;q=22;s=23 |
请注意,您需要启用矩阵变量的使用。在 MVC Java configuration 中,您需要使用removeSemicolonContent=false
到路径匹配设置UrlPathHelper
。在 MVC XML 命名空间中,您可以设置<mvc:annotation-driven enable-matrix-variables="true"/>
。
RequestParam注解
您可以使用@RequestParam
annotation 将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。
以下 example 显示了如何执行此操作:
1 |
|
1 | 使用@RequestParam 绑定petId 。 |
默认情况下,需要使用此 annotation 的方法参数,但您可以通过将@RequestParam
annotation 的required
flag 设置为false
或通过使用java.util.Optional
wrapper 声明参数来指定方法参数是可选的。
如果目标方法参数类型不是String
,则会自动应用类型转换。见类型转换。
将参数类型声明为 array 或 list 允许为同一参数 name 解析多个参数值。
当@RequestParam
annotation 声明为Map<String, String>
或MultiValueMap<String, String>
时,如果没有在 annotation 中指定参数 name,则使用每个给定参数 name 的请求参数值填充 map。
请注意,使用@RequestParam
是可选的(对于 example,设置其属性)。默认情况下,任何作为简单 value 类型的参数(由BeanUtils 的#isSimpleProperty确定)并且不由任何其他参数解析器解析,将被视为使用@RequestParam
进行注释。
RequestHeader注解
您可以使用@RequestHeader
annotation 将请求标头绑定到控制器中的方法参数。
使用 headers 考虑以下请求:
1 | Host localhost:8080 |
以下 example 获取Accept-Encoding
和Keep-Alive
headers 的 value:
1 |
|
1 | 获取Accept-Encoding 标头的 value。 |
2 | 获取Keep-Alive 标头的 value。 |
如果目标方法参数类型不是String
,则会自动应用类型转换。见类型转换。
在Map<String, String>
,MultiValueMap<String, String>
或HttpHeaders
参数上使用@RequestHeader
annotation 时,map 将填充所有标头值。
Built-in 支持可用于将 comma-separated string 转换为 array 或 strings 集合或类型转换系统已知的其他类型。对于 example,使用
@RequestHeader("Accept")
注释的方法参数可以是String
类型,但也可以是String[]
或List<String>
。
CookieValue注解
您可以使用@CookieValue
annotation 将 HTTP cookie 的 value 绑定到控制器中的方法参数。
考虑使用以下 cookie 的请求:
1 | JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 |
以下 example 显示了如何获取 cookie value:
1 |
|
1 | 获取JSESSIONID cookie 的 value。 |
如果目标方法参数类型不是String
,则自动应用类型转换。见类型转换。
ModelAttribute注解
您可以在方法参数上使用@ModelAttribute
annotation 来从 model 访问属性,或者如果不存在则将其实例化。 model 属性还覆盖了 HTTP Servlet 请求参数的值,这些参数的名称为 match 到字段名称。这称为数据 binding,它使您不必处理解析和转换单个查询参数和表单字段。以下 example 显示了如何执行此操作:
1 |
|
1 | 绑定Pet 的实例。 |
上面的Pet
实例解析如下:
- 从 model 开始,如果已经使用模型添加了。
- 通过使用@SessionAttributes从 HTTP session。
- 从通过
Converter
的 URI 路径变量(参见下一个 example)。 - 从默认构造函数的调用。
- 从“主构造函数”调用 arguments match 到 Servlet 请求参数。参数名称通过字节码中的 JavaBeans
@ConstructorProperties
或 runtime-retained 参数名称确定。
虽然使用模型来使用属性填充_mode 是常见的,但另一种替代方法是依赖于Converter<String, T>
与 URI 路径变量约定的组合。在下面的示例中,model 属性 name,account
与 URI 路径变量account
匹配,并通过将String
帐号传递给已注册的Converter<String, Account>
来加载Account
:
1 |
|
获取 model 属性实例后,将应用数据 binding。 WebDataBinder
class 将 Servlet 请求参数名称(查询参数和表单字段)与目标Object
上的字段名称相匹配。在必要时,在应用类型转换后填充匹配字段。有关数据 binding(和验证)的更多信息,请参阅验证。有关自定义数据 binding 的更多信息,请参阅DataBinder。
数据绑定可能导致错误。默认情况下,会引发BindException
。但是,要在控制器方法中检查此类错误,可以在@ModelAttribute
旁边添加BindingResult
参数,如下面的 example 所示:
1 |
|
1 | 在@ModelAttribute 旁边添加BindingResult 。 |
在某些情况下,您可能希望在没有数据 binding 的情况下访问 model 属性。对于这种情况,您可以_将Model
注入控制器并直接访问它,或者设置@ModelAttribute(binding=false)
,如下面的 example 所示:
1 |
|
1 | 设置@ModelAttribute(binding=false) 。 |
通过添加javax.validation.Valid
annotation 或 Spring 的@Validated
annotation(Bean 验证和Spring 验证),您可以在数据 binding 后自动应用验证。以下 example 显示了如何执行此操作:
1 |
|
1 | 验证Pet 实例。 |
请注意,使用@ModelAttribute
是可选的(对于 example,设置其属性)。默认情况下,任何不是简单 value 类型(由BeanUtils 的#isSimpleProperty确定)并且未被任何其他参数解析器解析的参数都被视为使用@ModelAttribute
进行注解。
SessionAttributes注解
@SessionAttributes
用于在请求之间的 HTTP Servlet session 中存储 model 属性。它是 type-level annotation,声明特定控制器使用的 session 属性。这通常列出 model 属性的名称或 model 属性的类型,这些属性应该透明地存储在 session 中以供后续访问请求使用。
以下 example 使用@SessionAttributes
annotation:
1 |
|
1 | 使用@SessionAttributes annotation。 |
在第一个请求中,当 model 的 model 属性被添加到 model 时,它会自动提升并保存在 HTTP Servlet session 中。它保持不变,直到另一个控制器方法使用SessionStatus
方法参数来清除存储,如下面的 example 所示:
1 |
|
1 | 将Pet value 存储在 Servlet session 中。 |
2 | 清除 Servlet session 中的Pet value。 |
SessionAttribute注解
如果需要访问全局管理的 pre-existing session 属性(即,在控制器外部 - 对于 example,通过过滤器)并且可能存在或不存在,则可以在方法参数上使用@SessionAttribute
annotation,如下所示 example 说明:
1 |
|
1 | 使用@SessionAttribute 注释。 |
对于需要添加或删除 session 属性的用例,请考虑将org.springframework.web.context.request.WebRequest
或javax.servlet.http.HttpSession
注入控制器方法。
要在 session 中临时存储 model 属性作为控制器工作流的一部分,请考虑使用@SessionAttributes
,如@SessionAttributes中所述。
RequestAttribute注解
与@SessionAttribute
类似,您可以使用@RequestAttribute
annotations 访问之前创建的 pre-existing 请求属性(对于 example,Servlet Filter
或HandlerInterceptor
):
1 |
|
1 | 使用@RequestAttribute annotation。 |
重定向属性
默认情况下,所有 model 属性都被视为在重定向 URL 中公开为 URI 模板变量。在其余属性中,原始类型或集合或基本类型数组的属性会自动附加为查询参数。
如果专门为重定向准备了 model 实例,则将原始类型属性作为查询参数附加可以是期望的结果。但是,在带注释的控制器中,model 可以包含为渲染目的而添加的其他属性(对于 example,drop-down 字段值)。为了避免在 URL 中出现此类属性的可能性,@RequestMapping
方法可以声明类型为RedirectAttributes
的参数,并使用它来指定可供RedirectView
使用的确切属性。如果方法重定向,则使用RedirectAttributes
的内容。否则,使用 model 的内容。
RequestMappingHandlerAdapter
提供了一个名为ignoreDefaultModelOnRedirect
的 flag,您可以使用它来指示如果控制器方法重定向,则永远不应使用默认Model
的内容。相反,控制器方法应声明类型为RedirectAttributes
的属性,如果不这样做,则不应将任何属性传递给RedirectView
。 MVC 命名空间和 MVC Java configuration 都将此 flag 设置为false
,以保持向后兼容性。但是,对于新的 applications,我们建议将其设置为true
。
请注意,在展开重定向 URL 时,当前请求中的 URI 模板变量会自动变为可用,您需要通过Model
或RedirectAttributes
显式添加它们。以下 example 显示了如何定义重定向:
1 |
|
将数据传递到重定向目标的另一种方法是使用 flash 属性。与其他重定向属性不同,Flash 属性保存在 HTTP session 中(因此,不会出现在 URL 中)。有关更多信息,请参见Flash 属性。
Flash属性
Flash 属性为一个请求提供了一种方法来存储打算在另一个请求中使用的属性。这在重定向时最常见 - 例如,Post-Redirect-Get pattern。 Flash 属性在重定向之前临时保存(通常在 session 中),以便在重定向后对请求可用并立即删除。
Spring MVC 有两个主要的抽象支持 flash 属性。 FlashMap
用于保存 flash 属性,而FlashMapManager
用于 store,检索和管理FlashMap
实例。
Flash 属性支持始终处于“打开”状态,无需显式启用。但是,如果不使用,它永远不会导致 HTTP session 创建。在每个请求中,都有一个“输入”FlashMap
,其中包含从先前请求(如果有)传递的属性,以及“输出”FlashMap
,其中包含要为后续请求保存的属性。 FlashMap
实例都可以通过RequestContextUtils
中的静态方法从 Spring MVC 中的任何位置访问。
带注释的控制器通常不需要直接使用FlashMap
。相反,@RequestMapping
方法可以接受RedirectAttributes
类型的参数,并使用它为重定向方案添加 flash 属性。通过RedirectAttributes
添加的 Flash 属性会自动传播到“输出”FlashMap。同样,在重定向之后,“input”FlashMap
中的属性会自动添加到为目标 URL 提供服务的控制器的Model
中。
匹配闪存属性的请求
flash 属性的概念存在于许多其他 web 框架中,并且已证明有时会暴露于并发问题。这是因为,根据定义,闪存属性将被存储直到下一个请求。但是,非常“下一个”请求可能不是预期的接收者而是另一个异步请求(对于 example,轮询或资源请求),在这种情况下,过早地删除 flash 属性。
为了减少此类问题的可能性,RedirectView
使用目标重定向 URL 的路径和查询参数自动“标记”FlashMap
实例。反过来,默认FlashMapManager
在查找“输入”FlashMap
时将该信息与传入请求进行匹配。
这并不能完全消除并发问题的可能性,但会使用重定向 URL 中已有的信息大大减少并发问题。因此,我们建议您主要使用 Flash 属性进行重定向方案。
Multipart
在MultipartResolver
已经启用之后,具有multipart/form-data
的 POST 请求的内容被解析并可作为常规请求参数访问。以下 example 访问一个常规表单字段和一个上载文件:
1 |
|
将参数类型声明为List<MultipartFile>
允许为同一参数 name 解析多个 files。
当@RequestParam
annotation 声明为Map<String, MultipartFile>
或MultiValueMap<String, MultipartFile>
时,如果没有在 annotation 中指定参数 name,则 map 将使用 multipart files 为每个给定参数 name 填充。
使用 Servlet 3.0 multipart 解析,您还可以声明
javax.servlet.http.Part
而不是 Spring 的MultipartFile
,作为方法参数或集合 value 类型。
您还可以将 multipart 内容用作数据 binding 到命令 object的一部分。对于 example,前面 example 中的表单字段和文件可以是表单 object 上的字段,如下面的 example 所示:
1 | class MyForm { |
Multipart 请求也可以在 RESTful 服务方案中从 non-browser clients 提交。以下 example 显示了一个带有 JSON 的文件:
1 | POST /someUrl |
您可以使用@RequestParam
作为String
访问“meta-data”部分,但您可能希望它从 JSON 反序列化(类似于@RequestBody
)。在使用HttpMessageConverter转换后,使用@RequestPart
annotation 访问 multipart:
1 |
|
您可以将@RequestPart
与javax.validation.Valid
结合使用或使用 Spring 的@Validated
annotation,这两种方法都会导致应用标准 Bean 验证。默认情况下,验证错误会导致MethodArgumentNotValidException
,这会转换为 400(BAD_REQUEST)响应。或者,您可以通过Errors
或BindingResult
参数在控制器内本地处理验证错误,如下面的示例所示:
1 |
|
RequestBody注解
您可以使用@RequestBody
annotation 将请求主体读取并反序列化为Object
到HttpMessageConverter。以下 example 使用@RequestBody
参数:
1 |
|
您可以使用MVC 配置的消息转换器选项来配置或自定义邮件转换。
您可以将@RequestBody
与javax.validation.Valid
或 Spring 的@Validated
annotation 结合使用,这两种方法都会导致应用标准 Bean 验证。默认情况下,验证错误会导致MethodArgumentNotValidException
,这会转换为 400(BAD_REQUEST)响应。或者,您可以通过Errors
或BindingResult
参数在控制器内本地处理验证错误,如下面的示例所示:
1 |
|
HttpEntity
HttpEntity
与使用@RequestBody或多或少相同,但是基于暴露请求 headers 和 body 的容器 object。以下清单显示了一个 example:
1 |
|
ResponseBody注解
您可以在方法上使用@ResponseBody
annotation 通过HttpMessageConverter将 return 序列化到响应正文。以下清单显示了一个 example:
1 |
|
class level 也支持@ResponseBody
,在这种情况下,它由所有控制器方法继承。这是@RestController
的效果,它只不过标有@Controller
和@ResponseBody
。
您可以将@ResponseBody
与 reactive 类型一起使用。有关详细信息,请参阅异步请求和Reactive 类型。
您可以使用MVC 配置的消息转换器选项来配置或自定义邮件转换。
您可以将@ResponseBody
方法与 JSON 序列化视图结合使用。有关详细信息,请参阅Jackson JSON。
ResponseEntity
ResponseEntity
就像@ResponseBody但有状态和 headers。例如:
1 |
|
Spring MVC 支持使用单个 value reactive 类型为主体生成ResponseEntity
异步,and/or 单和 multi-valuereactive 类型。
JacksonJSON
Spring 支持 Jackson JSON library。
Jackson序列化视图
Spring MVC 为Jackson 的序列化视图提供了 built-in 支持,它允许只渲染Object
中所有字段的子集。要将其与@ResponseBody
或ResponseEntity
控制器方法一起使用,可以使用 Jackson 的@JsonView
annotation 来激活序列化视图 class,如下面的 example 所示:
1 |
|
@JsonView
允许 array 视图 classes,但是每个控制器方法只能指定一个。如果需要激活多个视图,可以使用复合接口。
对于依赖于视图分辨率的控制器,可以将序列化视图 class 添加到 model,如下面的 example 所示:
1 |
|
Model
您可以使用@ModelAttribute
annotation:
- 在方法论证 in
@RequestMapping
方法中,从 model 创建或访问Object
并通过WebDataBinder
将其绑定到请求。 - 作为
@Controller
或@ControllerAdvice
classes 中的 method-level annotation,有助于在任何@RequestMapping
方法调用之前初始化 model。 - 在方法上标记其 return value 是 model 属性。
本节讨论@ModelAttribute
方法 - 前面列表中的第二个 item。控制器可以有任意数量的@ModelAttribute
方法。所有这些方法都在同一控制器中的@RequestMapping
方法之前调用。也可以通过@ControllerAdvice
在控制器之间共享@ModelAttribute
方法。有关详细信息,请参阅控制器建议部分。
@ModelAttribute
方法具有灵活的方法签名。它们支持许多与@RequestMapping
方法相同的 arguments,除了@ModelAttribute
本身或与请求体相关的任何东西。
以下 example 显示了@ModelAttribute
方法:
1 |
|
以下 example 仅添加一个属性:
1 |
|
如果未明确指定 name,则会根据
Object
类型选择默认 name,如约定的 javadoc 中所述。您始终可以使用重载的addAttribute
方法或@ModelAttribute
上的name
属性(对于 return value)分配显式 name。
您还可以在@RequestMapping
方法上使用@ModelAttribute
作为 method-level annotation,在这种情况下,@RequestMapping
方法的 return value 将被解释为 model 属性。这通常不是必需的,因为它是 HTML 控制器中的默认行为,除非 return value 是String
否则将被解释为 view name。 @ModelAttribute
还可以自定义 model 属性 name,如下面的 example 所示:
1 |
|
DataBinder
@Controller
或@ControllerAdvice
classes 可以有@InitBinder
方法来初始化WebDataBinder
的实例,而这些方法又可以:
- 将请求参数(即表单或查询数据)绑定到 model object。
- 将 String-based 请求值(例如请求参数,路径变量,headers,cookies 等)转换为控制器方法 arguments 的目标类型。
- 在呈现 HTML 表单时将 model object 值格式化为
String
值。
@InitBinder
方法可以注册 controller-specific java.bean.PropertyEditor
或 Spring Converter
和Formatter
组件。此外,您可以使用MVC 配置在全局共享的FormattingConversionService
中注册Converter
和Formatter
类型。
@InitBinder
方法支持许多与@RequestMapping
方法相同的 arguments,但@ModelAttribute
(命令 object)arguments 除外。通常,它们使用WebDataBinder
参数(用于注册)和void
return value 声明。以下清单显示了一个 example:
1 |
|
1 | 定义@InitBinder 方法。 |
或者,当您通过共享FormattingConversionService
使用Formatter
-based 设置时,您可以 re-use 采用相同的方法并注册 controller-specific Formatter
实现,如下面的 example 所示:
1 |
|
1 | 在自定义格式化程序上定义@InitBinder 方法。 |
异常
@Controller
和@ControllerAdvice classes 可以有@ExceptionHandler
方法来处理来自控制器方法的 exceptions,如下面的 example 所示:
1 |
|
exception 可以_对正在传播的 top-level exception(即,直接IOException
被抛出)或wrapper exception 中的直接原因匹配(对于 example,包裹在IllegalStateException
内)。
对于匹配 exception 类型,最好将目标 exception 声明为方法参数,如前面的 example 所示。当多个 exception 方法 match 时,root exception match 通常优先于 cause exception match。更具体地说,ExceptionDepthComparator
用于根据抛出的 exception 类型的深度对 exceptions 进行排序。
或者,annotation 声明可以将 exception 类型缩小为 match,如下面的 example 所示:
1 |
|
您甚至可以使用具有非常通用参数签名的特定 exception 类型列表,如以下 example 所示:
1 |
|
root 和 cause exception 匹配之间的区别可能是令人惊讶的。
在前面显示的IOException
变体中,通常使用实际的FileSystemException
或RemoteException
实例作为参数调用该方法,因为它们都从IOException
延伸。但是,如果在 wrapper exception 中传播任何这样的匹配 exception,它本身就是IOException
,那么 passed-in exception 实例就是 wrapper exception。
在handle(Exception)
变体中,行为更简单。这总是在包装场景中使用 wrapper exception 调用,在这种情况下通过ex.getCause()
找到实际匹配的 exception。 passed-in exception 只有当它们被抛出为 top-level exceptions 时才是实际的FileSystemException
或RemoteException
实例。
我们通常建议您在参数签名中尽可能具体,减少 root 和 cause exception 类型之间不匹配的可能性。考虑将 multi-matching 方法分解为单独的@ExceptionHandler
方法,每个方法通过其签名匹配单个特定的 exception 类型。
在多@ControllerAdvice
排列中,我们建议在@ControllerAdvice
上使用相应的 order 优先声明主根 exception 映射。虽然根 exception match 优先于某个原因,但这是在给定控制器或@ControllerAdvice
class 的方法中定义的。这意味着 higher-priority @ControllerAdvice
bean 上的原因 match 优先于 lower-priority @ControllerAdvice
bean 上的任何 match(对于 example,root)。
最后但并非最不重要的是,@ExceptionHandler
方法 implementation 可以选择通过以原始形式重新抛出它来退出处理给定的 exception 实例。这在您仅对 root-level 匹配或在无法静态确定的特定 context 中的匹配中感兴趣的情况下非常有用。重新生成的 exception 通过剩余的解析链传播,就好像给定的@ExceptionHandler
方法首先不匹配一样。
Spring MVC 中对@ExceptionHandler
方法的支持建立在DispatcherServlet
level,HandlerExceptionResolver机制之上。
方法Arguments
@ExceptionHandler
方法支持以下 arguments:
方法论证 | 描述 |
---|---|
Exception 类型 | 用于访问引发的 exception。 |
HandlerMethod |
用于访问引发 exception 的控制器方法。 |
WebRequest , NativeWebRequest |
无需直接使用 Servlet API 即可访问请求参数和 request 和 session 属性。 |
javax.servlet.ServletRequest , javax.servlet.ServletResponse |
选择任何特定请求或响应类型(对于 example,ServletRequest 或HttpServletRequest 或 Spring 的MultipartRequest 或MultipartHttpServletRequest )。 |
javax.servlet.http.HttpSession |
强制存在 session。因此,这样的论证永远不会null 。 注意 session 访问不是 thread-safe。如果允许多个请求同时访问 session,请考虑将RequestMappingHandlerAdapter 实例的synchronizeOnSession flag 设置为true 。 |
java.security.Principal |
目前经过身份验证的用户 - 如果已知,可能是特定的Principal implementation class。 |
HttpMethod |
请求的 HTTP 方法。 |
java.util.Locale |
当前请求 locale,由最具体的LocaleResolver 可用 - 确定 - 生效,配置为LocaleResolver 或LocaleContextResolver 。 |
java.util.TimeZone , java.time.ZoneId |
与当前请求关联的 time zone,由LocaleContextResolver 确定。 |
java.io.OutputStream , java.io.Writer |
用于访问原始响应主体,由 Servlet API 公开。 |
java.util.Map , org.springframework.ui.Model , org.springframework.ui.ModelMap |
用于访问 model 以获取错误响应。永远是空的。 |
RedirectAttributes |
指定在重定向的情况下使用的属性 - (将附加到查询 string)和临时存储的 flash 属性,直到重定向后的请求为止。见重定向属性和Flash 属性。 |
@SessionAttribute |
用于访问任何 session 属性,与 session 属性存储在 session 中的 model 属性相反。有关详细信息,请参阅@SessionAttribute。 |
@RequestAttribute |
用于访问请求属性。有关详细信息,请参阅@RequestAttribute。 |
Return值
@ExceptionHandler
方法支持以下 return 值:
Return value | 描述 |
---|---|
@ResponseBody |
return value 通过HttpMessageConverter 实例转换并写入响应。见@ResponseBody。 |
HttpEntity<B> , ResponseEntity<B> |
return value 指定完整响应(包括 HTTP headers 和正文)通过HttpMessageConverter 实例转换并写入响应。见ResponseEntity。 |
String |
要使用ViewResolver implementations 解析并与隐式 model 一起使用的视图 name - 通过命令 objects 和@ModelAttribute 方法确定。处理程序方法还可以通过声明Model 参数(如前所述)以编程方式丰富 model。 |
View |
一个View 实例,用于与隐式 model 一起呈现 - 通过命令 objects 和@ModelAttribute 方法确定。处理程序方法还可以通过声明Model 参数(先前描述)以编程方式丰富 model。 |
java.util.Map , org.springframework.ui.Model |
要添加到隐式 model 的属性,其中 view name 通过RequestToViewNameTranslator 隐式确定。 |
@ModelAttribute |
要添加到 model 的属性,其中 view name 通过RequestToViewNameTranslator 隐式确定。 注意@ModelAttribute 是可选的。请参阅本 table 末尾的“任何其他 return value”。 |
ModelAndView object |
要使用的视图和 model 属性,以及可选的响应状态。 |
void |
具有void return 类型(或null return value)的方法被认为已完全处理了响应,如果它还具有ServletResponse OutputStream 参数或@ResponseStatus annotation。如果控制器进行了正ETag 或lastModified 时间戳检查,那么同样也是 true(详见控制器)。 如果上面的 none 是 true,则void return 类型也可以为 REST 控制器指示“无响应主体”或为 HTML 控制器指定默认视图 name。 |
任何其他 return value | 如果 return value 与上述任何一个都不匹配且不是简单类型(由BeanUtils 的#isSimpleProperty确定),则默认情况下,它被视为要添加到 model 的 model 属性。如果它是一个简单的类型,它仍然没有得到解决。 |
REST-API异常
REST 服务的 common 要求是在响应正文中包含错误详细信息。 Spring Framework 不会自动执行此操作,因为响应正文中的错误详细信息的表示形式为 application-specific。但是,@RestController
可以使用@ExceptionHandler
方法和ResponseEntity
return value 来设置响应的状态和正文。此类方法也可以在@ControllerAdvice
classes 中声明,以便全局应用它们。
在响应主体中实现带有错误详细信息的 global exception 处理的应用程序应考虑扩展ResponseEntityExceptionHandler,它提供 Spring MVC 引发的 exceptions 的处理,并提供钩子来自定义响应主体。要使用它,创建ResponseEntityExceptionHandler
的子类,用@ControllerAdvice
注释它,覆盖必要的方法,并将其声明为 Spring bean。
ControllerAdvice
通常,@ExceptionHandler
,@InitBinder
和@ModelAttribute
方法适用于声明它们的@Controller
class(或 class 层次结构)。如果您希望此类方法更全局地应用(跨控制器),则可以在标有@ControllerAdvice
或@RestControllerAdvice
的 class 中声明它们。
@ControllerAdvice
标有@Component
,这意味着这样的 classes 可以通过component 扫描注册为 Spring beans。 @RestControllerAdvice
也是 meta-annotation 标有@ControllerAdvice
和@ResponseBody
,这实质上意味着通过消息转换(与视图分辨率或模板渲染相比)将@ExceptionHandler
方法呈现给响应主体。
在启动时,@RequestMapping
和@ExceptionHandler
方法的基础结构 classes 检测@ControllerAdvice
类型的 Spring beans,然后在运行时应用它们的方法。 Global @ExceptionHandler
方法(来自@ControllerAdvice
)在本地方法(来自@Controller
)之后应用。相比之下,global @ModelAttribute
和@InitBinder
方法在本地方法之前应用。
默认情况下,@ControllerAdvice
方法适用于每个请求(即所有控制器),但您可以使用 annotation 上的属性将其缩小到控制器的子集,如下面的 example 所示:
1 | // Target all Controllers annotated with @RestController |
前面的 example 中的 selectors 在运行时进行评估,如果广泛使用,可能会对 performance 产生负面影响。有关更多详细信息,请参阅@ControllerAdvice javadoc。
URI链接
本节介绍 Spring Framework 中可用于处理 URI 的各种选项。
UriComponents
Spring MVC 和 Spring WebFlux
UriComponentsBuilder
有助于使用变量从 URI 模板中构建 URI,如下面的 example 所示:
1 | UriComponents uriComponents = UriComponentsBuilder |
1 | 带有 URI 模板的静态工厂方法。 |
2 | 添加或替换 URI 组件。 |
3 | 请求编码 URI 模板和 URI 变量。 |
4 | Build a UriComponents 。 |
5 | 展开变量并获取URI 。 |
前面的 example 可以合并为一个链并用buildAndExpand
缩短,如下面的 example 所示:
1 | URI uri = UriComponentsBuilder |
您可以通过直接转到 URI(这意味着编码)来进一步缩短它,如下面的 example 所示:
1 | URI uri = UriComponentsBuilder |
您可以使用完整的 URI 模板进一步缩短它,如下面的 example 所示:
1 | URI uri = UriComponentsBuilder |
UriBuilder
Spring MVC 和 Spring WebFlux
UriComponentsBuilder实现UriBuilder
。您可以使用UriBuilderFactory
创建UriBuilder
。 UriBuilderFactory
和UriBuilder
一起提供了一种可插入机制,可以根据共享配置(例如基本 URL,编码首选项和其他详细信息)从 URI 模板中构建 URI。
您可以使用UriBuilderFactory
配置RestTemplate
和WebClient
以自定义 URI 的准备。 DefaultUriBuilderFactory
是UriBuilderFactory
的默认_impleration,它在内部使用UriComponentsBuilder
并公开共享 configuration 选项。
以下 example 显示了如何配置RestTemplate
:
1 | // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; |
以下 example 配置WebClient
:
1 | // import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; |
此外,您还可以直接使用DefaultUriBuilderFactory
。它类似于使用UriComponentsBuilder
但是,它不是静态工厂方法,而是一个包含 configuration 和 preferences 的实际实例,如下面的 example 所示:
1 | String baseUrl = "http://example.com"; |
URI编码
Spring MVC 和 Spring WebFlux
UriComponentsBuilder
在两个级别公开编码选项:
- 首先UriComponentsBuilder#encode():Pre-encodes URI 模板,然后在扩展时严格编码 URI 变量。
- UriComponents#encode():扩展 URI 变量后对 URI 组件进行编码。
这两个选项都使用转义的八位字节替换 non-ASCII 和非法字符。但是,第一个选项还会替换出现在 URI 变量中的保留含义的字符。
考虑“;”,这在路径中是合法的但具有保留意义。第一个选项取代“;”在 URI 变量中使用“%3B”但在 URI 模板中没有。相比之下,第二个选项永远不会替换“;”,因为它是路径中的合法字符。
对于大多数情况,第一个选项可能会给出预期结果,因为它将 URI 变量视为完全编码的不透明数据,而选项 2 仅在 URI 变量故意包含保留字符时才有用。
以下 example 使用第一个选项:
1 | URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") |
您可以通过直接转到 URI(这意味着编码)来缩短前面的 example,如下面的 example 所示:
1 | URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}") |
您可以使用完整的 URI 模板进一步缩短它,如下面的示例所示:
1 | URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}?q={q}") |
WebClient
和RestTemplate
通过UriBuilderFactory
策略在内部扩展和编码 URI 模板。两者都可以配置自定义策略。如下例所示:
1 | String baseUrl = "http://example.com"; |
DefaultUriBuilderFactory
implementation 在内部使用UriComponentsBuilder
来扩展和编码 URI 模板。作为工厂,它提供了一个单独的位置来配置编码方法,基于以下编码模式之一:
TEMPLATE_AND_VALUES
:使用UriComponentsBuilder#encode()
(对应于前面列表中的第一个选项)pre-encode URI 模板,并在扩展时严格编码 URI 变量。VALUES_ONLY
:不对 URI 模板进行编码,而是在将 URI 变量扩展到模板之前,通过UriUtils#encodeUriUriVariables
对 URI 变量应用严格编码。URI_COMPONENTS
:使用UriComponents#encode()
(对应于前面列表中的第二个选项),在扩展 URI 变量后对 URI component value 进行编码。NONE
:未应用编码。
出于历史原因和向后兼容性,RestTemplate
设置为EncodingMode.URI_COMPONENTS
。 WebClient
依赖于DefaultUriBuilderFactory
中的默认 value,它在 5.0.x 中从EncodingMode.URI_COMPONENTS
更改为 5.1 中的EncodingMode.TEMPLATE_AND_VALUES
。
相对Servlet请求
您可以使用ServletUriComponentsBuilder
创建相对于当前请求的 URI,如下面的 example 所示:
1 | HttpServletRequest request = ... |
您可以创建相对于 context 路径的 URI,如下面的 example 所示:
1 | // Re-uses host, port and context path... |
您可以创建相对于 Servlet 的 URI(对于 example,/main/*
),如下面的 example 所示:
1 | // Re-uses host, port, context path, and Servlet prefix... |
从 5.1 开始,
ServletUriComponentsBuilder
忽略来自Forwarded
和X-Forwarded-*
headers 的信息,这些信息指定了 client-originated 地址。考虑使用ForwardedHeaderFilter来提取和使用或丢弃此类_header。
控制器的链接
Spring MVC 提供了一种准备控制器方法链接的机制。对于 example,以下 MVC 控制器允许创建链接:
1 |
|
您可以通过 name 引用方法来准备链接,如下面的 example 所示:
1 | UriComponents uriComponents = MvcUriComponentsBuilder |
在前面的 example 中,我们提供了实际的方法参数值(在本例中为 long value:21
),用作路径变量并插入到 URL 中。此外,我们提供 value,42
来填充任何剩余的 URI 变量,例如从 type-level 请求映射继承的hotel
变量。如果方法有更多的 arguments,我们可以为 URL 不需要的 arguments 提供 null。通常,只有@PathVariable
和@RequestParam
arguments 与构造 URL 相关。
还有其他方法可以使用MvcUriComponentsBuilder
。对于 example,您可以使用类似于通过代理进行 mock 测试的技术,以避免通过 name 引用控制器方法,如下面的 example 所示(example 假定MvcUriComponentsBuilder.on
的静态 import):
1 | UriComponents uriComponents = MvcUriComponentsBuilder |
当控制器方法签名可用于与
fromMethodCall
创建链接时,它们的设计受到限制。除了需要正确的参数签名之外,return 类型还存在技术限制(即,为链接构建器调用生成运行时代理),因此 return 类型不能是final
。特别是,视图名称的 commonString
return 类型在此处不起作用。您应该使用ModelAndView
或甚至普通的Object
(带有String
return value)。
前面的示例在MvcUriComponentsBuilder
中使用静态方法。在内部,它们依赖ServletUriComponentsBuilder
从当前请求的 scheme,host,port,context 路径和 servlet 路径准备基本 URL。这在大多数情况下效果很好。但是,有时,它可能是不够的。例如,您可能位于请求的 context 之外(例如,准备链接的批处理 process),或者您可能需要插入路径前缀(例如从请求路径中删除的 locale 前缀,并且需要 re-inserted 进入链接)。
对于这种情况,您可以使用接受UriComponentsBuilder
的静态fromXxx
重载方法来使用基本 URL。或者,您可以使用基本 URL 创建MvcUriComponentsBuilder
的实例,然后使用 instance-based withXxx
方法。对于 example,以下列表使用withMethodCall
:
1 | UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en"); |
从 5.1 开始,
MvcUriComponentsBuilder
忽略来自Forwarded
和X-Forwarded-*
headers 的信息,这些信息指定了 client-originated 地址。考虑使用ForwardedHeaderFilter来提取和使用或丢弃此类_header。
视图中的链接
在 Thymeleaf,FreeMarker 或 JSP 等视图中,您可以通过引用每个请求映射的隐式或显式分配的 name 来构建指向已注释控制器的链接。
考虑以下 example:
1 |
|
给定前面的控制器,您可以从 JSP 准备链接,如下所示:
1 | <%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> |
前面的 example 依赖于 Spring 标记 library(即 META-INF/spring.tld)中声明的mvcUrl
function,但很容易定义自己的 function 或为其他模板技术准备类似的函数。
这是如何工作的。在启动时,每个@RequestMapping
都通过HandlerMethodMappingNamingStrategy
分配一个默认 name,其默认 implementation 使用 class 的大写字母和方法 name(对于 example,ThingController
中的getThing
方法变为“TC#getThing”)。如果存在 name 冲突,则可以使用@RequestMapping(name="..")
分配显式 name 或实现自己的HandlerMethodMappingNamingStrategy
。
异步请求
Spring MVC 与 Servlet 3.0 异步请求处理有一个广泛的 integration:
- DeferredResult和可赎回 return 控制器方法中的值,并为单个异步 return value 提供基本支持。
- 控制器可以流多个值,包括SSE和原始数据。
- 控制器可以使用 reactive clients 和 return reactive 类型进行响应处理。
DeferredResult
一旦异步请求处理 feature 在 Servlet 容器中启用,控制器方法就可以用DeferredResult
包装任何支持的控制器方法 return value,如下面的 example 所示:
1 |
|
控制器可以从不同的线程异步生成 return value - 例如,响应外部 event(JMS 消息),计划任务或其他 event。
Callable
控制器可以使用java.util.concurrent.Callable
包装任何受支持的 return value,如下面的 example 所示:
1 |
|
然后可以通过配置 TaskExecutor
运行给定任务来获得 return value。
处理
以下是 Servlet 异步请求处理的简要概述:
- 通过调用
request.startAsync()
可以将ServletRequest
置于异步模式。这样做的主要作用是 Servlet(以及任何过滤器)可以退出,但响应保持打开状态以便稍后处理完成。 - 对
request.startAsync()
的调用返回AsyncContext
,您可以使用它来进一步控制异步处理。对于 example,它提供dispatch
方法,类似于 Servlet API 中的 forward,除了它允许 Servlet 容器线程上的 application resume request 处理。 ServletRequest
提供对当前DispatcherType
的访问,您可以使用它来区分处理初始请求,异步调度,转发和其他调度程序类型。
DeferredResult
处理工作如下:
- 控制器返回
DeferredResult
并将其保存在可以访问的某个 in-memory 队列或列表中。 - Spring MVC calls
request.startAsync()
。 - 同时,
DispatcherServlet
和所有已配置的过滤器退出请求处理线程,但响应仍保持打开状态。 - application 从某个线程设置
DeferredResult
,Spring MVC 将请求调度回 Servlet 容器。 - 再次调用
DispatcherServlet
,并使用异步生成的 return value 继续处理。
Callable
处理工作如下:
- 控制器返回
Callable
。 - Spring MVC calls
request.startAsync()
并将Callable
提交给TaskExecutor
以便在单独的线程中进行处理。 - 同时,
DispatcherServlet
和所有过滤器退出 Servlet 容器线程,但响应仍保持打开状态。 - 最终
Callable
产生一个结果,Spring MVC 将请求发送回 Servlet 容器以完成处理。 - 再次调用
DispatcherServlet
,并使用Callable
中异步生成的 return value 继续处理。
对于更多背景和 context,您还可以阅读在 Spring MVC 3.2 中引入异步请求处理支持的博客文章。
Exception处理
使用DeferredResult
时,可以选择是否使用 exception 调用setResult
或setErrorResult
。在这两种情况下,Spring MVC 都会将请求调度回 Servlet 容器以完成处理。然后将其视为控制器方法返回给定的 value 或者就像它产生给定的 exception 一样。然后 exception 遍历常规 exception 处理机制(对于 example,调用@ExceptionHandler
方法)。
当你使用Callable
时,会出现类似的处理逻辑,主要区别在于结果是从Callable
返回的,或者是由它引发的 exception。
Interception
HandlerInterceptor
实例可以是AsyncHandlerInterceptor
类型,以便在启动异步处理的初始请求(而不是postHandle
和afterCompletion
)上接收afterConcurrentHandlingStarted
回调。
HandlerInterceptor
implementations 还可以注册CallableProcessingInterceptor
或DeferredResultProcessingInterceptor
,以更深入地集成异步请求的生命周期(例如,处理超时 event)。有关详细信息,请参阅AsyncHandlerInterceptor。
DeferredResult
提供onTimeout(Runnable)
和onCompletion(Runnable)
回调。有关详细信息,请参阅DeferredResult 的 javadoc。 Callable
可以替换WebAsyncTask
,它为超时和完成回调公开了其他方法。
与WebFlux相比
Servlet API 最初是为了通过 Filter-Servlet 链进行单次传递而构建的。在 Servlet 3.0 中添加的异步请求处理允许 applications 退出 Filter-Servlet 链但保持响应打开以进行进一步处理。 Spring MVC 异步支持是围绕该机制构建的。当控制器返回DeferredResult
时,退出 Filter-Servlet 链,并释放 Servlet 容器线程。稍后,当设置DeferredResult
时,会进行ASYNC
调度(到同一个 URL),在此期间再次映射控制器,但不使用调用它,而是使用DeferredResult
value(就像控制器返回它一样)以恢复处理。
相比之下,Spring WebFlux 既不构建在 Servlet API 上,也不需要这样的异步请求处理 feature,因为它是异步的设计。异步处理内置于所有 framework contracts 中,并通过请求处理的所有阶段进行内在支持。
从编程 model 的角度来看,Spring MVC 和 Spring WebFlux 在控制器方法中都支持异步和Reactive 类型作为 return 值。 Spring MVC 甚至支持流媒体,包括 reactive 背压。但是,对响应的单独写入仍然是阻塞(并且在单独的线程上执行),这与 WebFlux 不同,WebFlux 依赖于 non-blocking I/O 并且每次写入不需要额外的线程。
另一个根本区别是 Spring MVC 不支持控制器方法 arguments 中的异步或 reactive 类型(对于 example,@RequestBody
,@RequestPart
和其他),也不支持异步和 reactive 类型作为 model 属性。 Spring WebFlux 确实支持所有这些。
HTTP流
您可以将DeferredResult
和Callable
用于单个异步 return value。如果要生成多个异步值并将其写入响应,该怎么办?本节介绍如何执行此操作。
Objects
您可以使用ResponseBodyEmitter
return value 生成 objects 流,其中每个 object 都使用HttpMessageConverter序列化并写入响应,如下面的 example 所示:
1 |
|
您还可以在ResponseEntity
中使用ResponseBodyEmitter
作为正文,以便自定义响应的状态和 headers。
当emitter
抛出IOException
时(对于 example,如果 remote client 消失了),applications 不负责清理连接,不应该调用emitter.complete
或emitter.completeWithError
。相反,servlet 容器会自动启动AsyncListener
错误通知,其中 Spring MVC 进行completeWithError
调用。反过来,此调用会对 application 执行最后一次ASYNC
调度,在此期间 Spring MVC 将调用已配置的 exception 解析器并完成请求。
SSE
SseEmitter
(ResponseBodyEmitter
的子类)提供对Server-Sent Events的支持,其中从服务器发送的 events 根据 W3C SSE 规范进行格式化。要从控制器生成 SSE 流,return SseEmitter
,如下面的 example 所示:
1 |
|
虽然 SSE 是流式传输到浏览器的主要选项,但请注意 Internet Explorer 不支持 Server-Sent Events。考虑使用 Spring 的WebSocket 消息传递和SockJS 后备,它们针对各种浏览器。
有关 exception 处理的注释,另请参阅上一节。
原始数据
有时,绕过消息转换并直接流式传输到响应OutputStream
(对于 example,用于文件下载)非常有用。您可以使用StreamingResponseBody
return value 类型执行此操作,如以下 example 所示:
1 |
|
您可以在ResponseEntity
中使用StreamingResponseBody
作为正文来自定义响应的状态和 headers。
Reactive类型
Spring MVC 支持在控制器中使用 reactive client libraries(也可以在 WebFlux 部分中读取Reactive Libraries)。这包括spring-webflux
来自spring-webflux
和其他,例如 Spring Data reactive data repositories。在这种情况下,能够从控制器方法 return reactive 类型很方便。
Reactive return 值按如下方式处理:
- single-value promise 适用于,类似于使用
DeferredResult
。示例包括Mono
(Reactor)或Single
(RxJava)。 - 具有流媒体类型(例如
application/stream+json
或text/event-stream
)的 multi-value 流适用于,类似于使用ResponseBodyEmitter
或SseEmitter
。示例包括Flux
(Reactor)或Observable
(RxJava)。 Applications 也可以_retFlux<ServerSentEvent>
或Observable<ServerSentEvent>
。 - 具有任何其他媒体类型(例如
application/json
)的 multi-value 流适用于,类似于使用DeferredResult<List<?>>
。
Spring MVC 通过
spring-core
从spring-core
支持 Reactor 和 RxJava,这使得它可以适应多个 reactive libraries。
对于流式传输到响应,支持 reactive 背压,但是对响应的写入仍然是阻塞的,并且通过配置 TaskExecutor
在单独的线程上执行,以避免阻塞上游源(例如从WebClient
返回的Flux
)。默认情况下,SimpleAsyncTaskExecutor
用于阻塞写入,但在加载时不适用。如果您计划使用 reactive 类型进行流式处理,则应使用MVC configuration配置任务执行程序。
断开
当 remote client 消失时,Servlet API 不会提供任何通知。因此,在流式传输到响应时,无论是通过SseEmitter还是reactive types,定期发送数据都很重要,因为如果 client 断开连接,则写入失败。发送可以采用空(comment-only)SSE event 或任何其他数据的形式,另一方必须将其解释为心跳并忽略。
或者,考虑使用具有 built-in 心跳机制的 web 消息传递解决方案(例如STOMP over WebSocket或带有SockJS的 WebSocket)。
Configuration
必须在 Servlet 容器 level 上启用异步请求处理 feature。 MVC configuration 还为异步请求公开了几个选项。
Servlet容器
Filter 和 Servlet 声明的asyncSupported
flag 需要设置为true
以启用异步请求处理。此外,应声明 Filter 映射以处理ASYNC
javax.servlet.DispatchType
。
在 Java configuration 中,当您使用AbstractAnnotationConfigDispatcherServletInitializer
初始化 Servlet 容器时,这将自动完成。
在web.xml
configuration 中,您可以将<async-supported>true</async-supported>
添加到DispatcherServlet
和Filter
声明中,并将<dispatcher>ASYNC</dispatcher>
添加到过滤器映射中。
SpringMVC
MVC configuration 公开了与异步请求处理相关的以下选项:
- Java configuration:在
WebMvcConfigurer
上使用configureAsyncSupport
回调。 - XML 命名空间:使用
<mvc:annotation-driven>
下的<async-support>
元素。
您可以配置以下内容:
- 异步请求的默认超时 value,如果未设置,则取决于底层的 Servlet 容器(对于 example,Tomcat 上为 10 秒)。
AsyncTaskExecutor
用于在使用Reactive 类型进行流式传输时阻止写入以及用于执行从控制器方法返回的Callable
实例。如果您使用 reactive 类型进行流式处理或者使用 returnCallable
的控制器方法,我们强烈建议您配置此 property,因为默认情况下它是SimpleAsyncTaskExecutor
。DeferredResultProcessingInterceptor
implementations 和CallableProcessingInterceptor
__mplementations。
请注意,您还可以在DeferredResult
,ResponseBodyEmitter
和SseEmitter
上设置默认超时值。对于Callable
,您可以使用WebAsyncTask
来提供超时值。
CORS
Spring MVC 允许您处理 CORS(Cross-Origin 资源共享)。本节介绍如何执行此操作。
介绍
出于安全原因,浏览器禁止 AJAX calls 到当前源之外的资源。例如,您可以将您的银行帐户放在一个标签中,将 evil.com 放在另一个标签中。来自 evil.com 的脚本不应该使用您的凭据向您的银行 API 发出 AJAX 请求 - 例如从您的帐户中提取资金!
Cross-Origin 资源共享(CORS)是由大多数浏览器实现的W3C 规范,它允许您指定授权的 cross-domain 请求类型,而不是使用基于 IFRAME 或 JSONP 的安全性较低且功能较弱的变通方法。
Processing
CORS 规范区分了预检,简单和实际请求。要了解 CORS 的工作原理,您可以阅读本文等许多其他内容,或者查看规范以获取更多详细信息。
Spring MVC HandlerMapping
implementations 为 CORS 提供 built-in 支持。成功将请求映射到处理程序后,HandlerMapping
implementations 检查给定请求和处理程序的 CORS configuration 并采取进一步操作。直接处理预检请求,同时拦截,验证简单和实际的 CORS 请求,并设置所需的 CORS 响应头。
在 order 中启用 cross-origin 请求(即,Origin
标头存在且与请求的 host 不同),您需要有一些显式声明的 CORS configuration。如果未找到匹配的 CORS configuration,则拒绝预检请求。没有 CORS headers 被添加到简单和实际 CORS 请求的响应中,因此浏览器拒绝它们。
每个HandlerMapping
可以_11单独使用 URL pattern-based CorsConfiguration
映射。在大多数情况下,applications 使用 MVC Java configuration 或 XML 命名空间来声明这样的映射,这会导致单个 global map 传递给所有HandlerMappping
实例。
您可以将HandlerMapping
level 上的 global CORS configuration 与更多 fine-grained,handler-level CORS configuration 结合使用。对于 example,带注释的控制器可以使用 class-或 method-level @CrossOrigin
注释(其他处理程序可以实现CorsConfigurationSource
)。
组合 global 和 local configuration 的规则通常是加法的 - 例如,所有 global 和所有本地起源。对于只能接受单个 value 的属性(例如allowCredentials
和maxAge
),本地会覆盖 global value。有关详细信息,请参阅CorsConfiguration#combine(CorsConfiguration)。
要从源中了解更多信息或进行高级自定义,请检查后面的 code:
CorsConfiguration
CorsProcessor
,DefaultCorsProcessor
AbstractHandlerMapping
CrossOrigin注解
@CrossOrigin 注解在带注解的控制器方法上启用 cross-origin 请求,如下面的 example 所示:
1 |
|
默认情况下,@CrossOrigin
允许:
- 所有起源。
- 所有 headers。
- 控制器方法映射到的所有 HTTP 方法。
默认情况下不启用allowedCredentials
,因为它建立了一个公开敏感 user-specific 信息的信任 level(例如 cookies 和 CSRF 令牌),并且只应在适当的地方使用。
maxAge
设置为 30 分钟。
class level 也支持@CrossOrigin
,并且由所有方法继承,如下面的 example 所示:
1 |
|
您可以在 class level 和方法 level 中使用@CrossOrigin
,如下面的 example 所示:
1 |
|
全局配置
除了 fine-grained,控制器方法 level configuration 之外,您可能还想定义一些 global CORS configuration。您可以在任何HandlerMapping
上单独设置 URL-based CorsConfiguration
映射。但是,大多数 applications 使用 MVC Java configuration 或 MVC XNM 命名空间来执行此操作。
默认情况下,global configuration 启用以下内容:
- 所有起源。
- 所有 headers。
GET
,HEAD
和POST
方法。
默认情况下不启用allowedCredentials
,因为它建立了一个公开敏感 user-specific 信息的信任 level(例如 cookies 和 CSRF 令牌),并且只应在适当的地方使用。
maxAge
设置为 30 分钟。
Java配置
要在 MVC Java 配置中启用 CORS,可以使用CorsRegistry
回调,如下面的 example 所示:
1 |
|
XML配置
要在 XML 命名空间中启用 CORS,可以使用<mvc:cors>
元素,如下面的 example 所示:
1 | <mvc:cors> |
CORS过滤器
您可以通过 built-in CorsFilter应用 CORS 支持。
如果您尝试将
CorsFilter
与 Spring Security 一起使用,请记住 Spring Security 对于 CORS 有built-in 支持。
要配置过滤器,请将CorsConfigurationSource
传递给其构造函数,如下面的 example 所示:
1 | CorsConfiguration config = new CorsConfiguration(); |
Web安全
Spring Security项目支持保护 web applications 免受恶意攻击。请参阅 Spring Security reference 文档,包括:
HDIV是另一个与 Spring MVC 集成的 web 安全 framework。
HTTP缓存
HTTP 缓存可以显着改善 web application 的 performance。 HTTP 缓存围绕Cache-Control
响应头,随后是条件请求 headers(例如Last-Modified
和ETag
)。 Cache-Control
建议私有(例如,浏览器)和公共(用于 example,代理)缓存如何缓存和 re-use 响应。如果内容未更改,ETag
标头用于生成条件请求,该请求可能导致 304(NOT_MODIFIED)没有正文。 ETag
可以被视为Last-Modified
标题的更复杂的继承者。
本节介绍 Spring Web MVC 中可用的 HTTP caching-related 选项。
CacheControl
CacheControl支持配置与Cache-Control
标头相关的设置,并且在许多地方被接受为参数:
虽然RFC 7234描述了Cache-Control
响应头的所有可能的指令,但CacheControl
类型采用了一种使用 case-oriented 方法,该方法专注于 common 场景:
1 | // Cache for an hour - "Cache-Control: max-age=3600" |
WebContentGenerator
也接受一个更简单的cachePeriod
property(以秒为单位定义),其工作方式如下:
-1
value 不会生成Cache-Control
响应头。0
value 通过使用'Cache-Control: no-store'
指令来防止缓存。n > 0
value 使用'Cache-Control: max-age=n'
指令缓存给定的响应n
秒。
控制器
控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为资源的lastModified
或ETag
value 需要先计算才能与条件请求 headers 进行比较。控制器可以将ETag
标头和Cache-Control
设置添加到ResponseEntity
,如下面的 example 所示:
1 |
|
如果与条件请求 headers 的比较表明内容未更改,则前面的 example 将发送一个 304(NOT_MODIFIED)响应与空体。否则,ETag
和Cache-Control
headers 将添加到响应中。
您还可以在控制器中对条件请求 headers 进行检查,如下面的 example 所示:
1 |
|
1 | Application-specific 计算。 |
2 | 响应已设置为 304(NOT_MODIFIED) - 无需进一步处理。 |
3 | 继续请求处理。 |
有三种变体可用于检查针对eTag
值,lastModified
值或两者的条件请求。对于条件GET
和HEAD
请求,您可以将响应设置为 304(NOT_MODIFIED)。对于条件POST
,PUT
和DELETE
,您可以将响应设置为 409(PRECONDITION_FAILED),以防止并发修改。
静态资源
您应该使用Cache-Control
和条件响应 headers 为静态资源提供最佳的 performance。请参阅有关配置静态资源的部分。
ETag过滤器
您可以使用ShallowEtagHeaderFilter
添加从响应内容计算的“浅”eTag
值,从而节省带宽但不节省 CPU time。见浅 ETag。
View技术
在 Spring MVC 中使用视图技术是可插拔的,无论您决定使用 Thymeleaf,Groovy 标记模板,JSP 还是其他技术,主要是配置更改的问题。本章介绍与 Spring MVC 集成的视图技术。我们假设您已经熟悉View解析器。
Thymeleaf
Thymeleaf 是一个现代的 server-side Java 模板引擎,它强调自然的 HTML 模板,可以通过 double-clicking 在浏览器中预览,这对于 UI 模板的独立工作非常有用(例如,设计者),而不需要运行服务器。如果您想要替换 JSP,Thymeleaf 提供了一组最广泛的 features,以使这种转换更容易。 Thymeleaf 积极开发和维护。有关更完整的介绍,请参阅Thymeleaf项目主页。
Thymeleaf 与 Spring MVC 的整合由 Thymeleaf 项目管理。 configuration 涉及一些 bean 声明,例如ServletContextTemplateResolver
,SpringTemplateEngine
和ThymeleafViewResolver
。有关详细信息,请参阅Thymeleaf Spring。
FreeMarker
Apache FreeMarker是一个模板引擎,用于生成从 HTML 到电子邮件和其他人的任何类型的文本输出。 Spring Framework 有一个 built-in integration 用于使用带有 FreeMarker 模板的 Spring MVC。
View配置
以下 example 显示了如何将 FreeMarker 配置为视图技术:
1 |
|
以下 example 显示了如何在 XML 中配置相同的内容:
1 | <mvc:annotation-driven/> |
或者,您也可以声明FreeMarkerConfigurer
bean 以完全控制所有 properties,如下面的 example 所示:
1 | <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> |
您的模板需要存储在前面的 example 中显示的FreeMarkerConfigurer
指定的目录中。给定前面的 configuration,如果控制器返回welcome
的 view name,则解析器将查找/WEB-INF/freemarker/welcome.ftl
模板。
FreeMarker配置
您可以通过在FreeMarkerConfigurer
bean 上设置相应的 bean properties,将 FreeMarker’Settings’和’SharedVariables’直接传递给 FreeMarker Configuration
object(由 Spring 管理)。 freemarkerSettings
property 需要java.util.Properties
object,而freemarkerVariables
property 需要java.util.Map
。以下 example 显示了如何执行此操作:
1 | <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> |
有关适用于Configuration
object 的设置和变量的详细信息,请参阅 FreeMarker 文档。
表单处理
Spring 提供了一个标记 library,用于 JSP,其中包含<spring:bind/>
元素。此元素主要允许表单显示 form-backing objects 中的值,并显示 web 或业务层中Validator
的验证失败结果。 Spring 还支持 FreeMarker 中的相同功能,还有用于生成表单输入元素的额外便利宏。
绑定宏
在两个语言的spring-webmvc.jar
文件中维护一组标准宏,因此它们始终可用于适当配置的 application。
Spring libraries 中定义的一些宏被认为是内部(私有),但宏定义中不存在这样的范围,使得所有宏都可以调用 code 和用户模板。以下部分仅关注您需要在模板中直接调用的宏。如果您希望直接查看宏 code,则该文件名为spring.ftl
并位于org.springframework.web.servlet.view.freemarker
包中。
简单Binding
在作为 Spring MVC 控制器的表单视图的 HTML 表单(vm 或 ftl 模板)中,您可以使用类似于下一个 example 的 code 绑定到字段值,并以与 JSP 类似的方式显示每个输入字段的错误消息当量。以下 example 显示了之前配置的personForm
视图:
1 | <!-- freemarker macros have to be imported into a namespace. We strongly |
<@spring.bind>
需要一个’path’参数,它包含命令 object 的 name(它是’command’,除非你在FormController
properties 中更改它),后跟一个句点和_jject 命令 object 上的字段的 name 希望绑定。您还可以使用嵌套字段,例如command.address.street
。 bind
宏假定web.xml
中 ServletContext 参数defaultHtmlEscape
指定的默认 HTML 转义行为。
名为<@spring.bindEscaped>
的宏的可选形式采用第二个参数,并明确指定是否应在状态错误消息或值中使用 HTML 转义。您可以根据需要将其设置为true
或false
。其他表单处理宏简化了 HTML 转义的使用,您应该尽可能使用这些宏。它们将在下一节中介绍。
输入宏
两种语言的附加便利宏简化了 binding 和表单生成(包括验证错误显示)。永远不必使用这些宏来生成表单输入字段,您可以将它们与简单的 HTML 混合和 match,或者直接 calls 到我们之前突出显示的 spring 绑定宏。
以下 table 可用宏显示 FTL 定义和每个参数列表:
宏 | FTL 定义 |
---|---|
message (根据 code 参数从资源包中输出 string) |
<@spring.message code/> |
messageText (根据 code 参数从资源包中输出 string,回退到默认参数的 value) |
<@spring.messageText code, text/> |
url (使用 application 的 context 根作为相对 URL 的前缀) |
<@spring.url relativeUrl/> |
formInput (用于收集用户输入的标准输入字段) |
<@spring.formInput path, attributes, fieldType/> |
formHiddenInput (用于提交 non-user 输入的隐藏输入字段) |
<@spring.formHiddenInput path, attributes/> |
formPasswordInput (用于收集密码的标准输入字段.请注意,此 type.)的字段中不会填充 value |
<@spring.formPasswordInput path, attributes/> |
formTextarea (用于收集 long,自由格式文本输入的大文本字段) |
<@spring.formTextarea path, attributes/> |
formSingleSelect (下拉框选项,允许选择一个必需的 value) |
<@spring.formSingleSelect path, options, attributes/> |
formMultiSelect (允许用户选择 0 个或更多值的选项列表框) |
<@spring.formMultiSelect path, options, attributes/> |
formRadioButtons (一组单选按钮,可以从可用选项中进行单个选择) |
<@spring.formRadioButtons path, options separator, attributes/> |
formCheckboxes (一组允许选择 0 或更多值的复选框) |
<@spring.formCheckboxes path, options, separator, attributes/> |
formCheckbox (单个复选框)<@spring.formCheckbox path, attributes/> |
<@spring.formCheckbox path, attributes/> |
showErrors (简化绑定字段的验证错误显示) |
<@spring.showErrors separator, classOrStyle/> |
- 在 FTL(FreeMarker)中,实际上不需要
formHiddenInput
和formPasswordInput
,因为您可以使用普通的formInput
宏,指定hidden
或password
作为fieldType
参数的 value。
任何上述宏的参数都具有一致的含义:
path
:要绑定的字段的 name(即“command.name”)options
:可以在输入字段中选择的所有可用值的Map
。 map 的键表示从表单返回并绑定到命令 object 的值。针对键存储的 Map objects 是表单上显示给用户的标签,可能与表单发回的相应值不同。通常,这样的 map 由控制器作为 reference 数据提供。您可以使用任何Map
implementation,具体取决于所需的行为。对于严格排序的 maps,您可以使用SortedMap
(例如TreeMap
)和合适的Comparator
,对于任意 Maps 应该 return insert order 中的值,使用LinkedHashMap
或LinkedMap
来自commons-collections
。separator
:多个选项可用作谨慎元素(单选按钮或复选框),用于分隔列表中每个元素的字符序列(例如<br>
)。attributes
:HTML 标记本身中包含的任意标记或文本的附加 string。这个 string 由宏按字面回显。例如,在textarea
字段中,您可以提供属性(例如’rows =“5”cols =“60”’),或者您可以传递样式信息,例如’style =“border:1px solid silver”’。classOrStyle
:对于showErrors
宏,包含每个错误的span
元素使用的 CSS class 的 name。如果未提供任何信息(或 value 为空),则错误将包含在<b></b>
标记中。
以下部分概述了宏的示例(一些在 FTL 中,一些在 VTL 中)。如果两种语言之间存在使用差异,则会在说明中对其进行说明。
formInput
宏采用path
参数(command.name
)和一个额外的attributes
参数(在即将发生的 example 中为空)。宏以及所有其他表单生成宏在 path 参数上执行隐式 Spring 绑定。 binding 在发生新绑定之前一直有效,因此showErrors
宏不需要再次传递 path 参数 - 它在最后创建绑定的字段上运行。
showErrors
宏采用 separator 参数(用于分隔给定字段上的多个错误的字符),还接受第二个参数 - 此 time,class name 或 style 属性。请注意,FreeMarker 可以为 attributes 参数指定默认值。以下 example 显示了如何使用formInput
和showWErrors
宏:
1 | <@spring.formInput "command.name"/> |
下一个 example 显示表单片段的输出,生成 name 字段并在提交表单后在字段中没有 value 时显示验证错误。验证通过 Spring 的验证 framework 进行。
生成的 HTML 类似于以下 example:
1 | Name: |
formTextarea
宏的工作方式与formInput
宏的工作方式相同,并接受相同的参数列表。通常,第二个参数(属性)用于传递textarea
的样式信息或rows
和cols
属性。
您可以使用四个选择字段宏在 HTML 表单中生成 common UI value 选择输入:
formSingleSelect
formMultiSelect
formRadioButtons
formCheckboxes
四个宏中的每一个都接受一个Map
选项,其中包含表单字段的 value 和与该 value 对应的标签。 value 和标签可以相同。
下一个示例用于 FTL 中的单选按钮。 form-backing object 为此字段指定了“伦敦”的默认值值,因此无需进行验证。呈现表单时,可以在 name’cityMap’下的 model 中作为 reference 数据提供可供选择的整个城市列表。以下清单显示了 example:
1 | ... |
前面的列表呈现了一个 line 的单选按钮,cityMap
中每个 value 一个,并使用""
的分隔符。没有提供其他属性(缺少宏的最后一个参数)。 cityMap
对 map 中的每个 key-value 对使用相同的String
。 map 的键是表单实际提交为 POSTed 请求参数的键。 map 值是用户看到的标签。在前面的示例中,给定一个包含三个众所周知的城市的列表以及支持 object 形式的默认 value,HTML 类似于以下内容:
1 | Town: |
如果您的 application 希望按内部代码处理城市(对于 example),您可以使用合适的密钥创建代码的 map,如下面的 example 所示:
1 | protected Map<String, String> referenceData(HttpServletRequest request) throws Exception { |
code 现在生成输出,其中无线电值是相关代码,但用户仍然看到更多 user-friendly 城市名称,如下所示:
1 | Town: |
HTML转义
前面描述的表单宏的默认用法会导致符合 HTML 4.01 的 HTML 元素,并使用文件中定义的 HTML 转义的默认值,如 Spring 的绑定支持所使用的那样。要使元素符合 XHTML 要求或覆盖默认的 HTML 转义 value,您可以在模板中指定两个变量(或者在 model 中指定模板可见的变量)。在模板中指定它们的优点是,它们可以在模板处理中稍后更改为不同的值,以便为表单中的不同字段提供不同的行为。
要切换为标记的 XHTML 合规性,请为名为xhtmlCompliant
的 model 或 context 变量指定的 value,如下面的 example 所示:
1 | <#-- for FreeMarker --> |
处理完该指令后,Spring 宏生成的任何元素现在都符合 XHTML 标准。
以类似的方式,您可以指定每个字段的 HTML 转义,如下面的 example 所示:
1 | <#-- until this point, default HTML escaping is used --> |
Groovy标记
Groovy 标记模板引擎主要用于生成 XML-like 标记(XML,XHTML,HTML5 等),但您可以使用它来生成任何 text-based 内容。 Spring Framework 有一个 built-in integration 用于将 Spring MVC 与 Groovy Markup 一起使用。
Groovy 标记模板引擎需要 Groovy 2.3.1.
Configuration
以下 example 显示了如何配置 Groovy 标记模板引擎:
1 |
|
以下 example 显示了如何在 XML 中配置相同的内容:
1 | <mvc:annotation-driven/> |
示例
与传统的模板引擎不同,Groovy Markup 依赖于使用构建器语法的 DSL。以下 example 显示了 HTML 页面的 sample 模板:
1 | yieldUnescaped '<!DOCTYPE html>' |
脚本视图
Spring Framework 有一个 built-in integration 用于使用 Spring MVC 和任何可以在JSR-223 Java 脚本引擎之上运行的模板 library。我们在不同的脚本引擎上测试了以下模板 libraries:
脚本 Library | 脚本引擎 |
---|---|
Handlebars | Nashorn |
mustache | Nashorn |
React | Nashorn |
EJS | Nashorn |
ERB | JRuby |
String templates | Jython |
Kotlin 脚本模板 | Kotlin |
集成任何其他脚本引擎的基本规则是它必须实现
ScriptEngine
和Invocable
接口。
需求
您需要在 classpath 上安装脚本引擎,其详细信息因脚本引擎而异:
- Nashorn JavaScript 引擎随 Java 8 一起提供。强烈建议使用最新的更新版本。
- 应该添加JRuby作为 Ruby 支持的依赖项。
- 应该添加Jython作为 Python 支持的依赖项。
- 应为 Kotlin 脚本支持添加
org.jetbrains.kotlin:kotlin-script-util
依赖项和包含org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
line 的META-INF/services/javax.script.ScriptEngineFactory
文件。有关详细信息,请参阅这个 example。
你需要有脚本模板 library。对 Javascript 执行此操作的一种方法是通过WebJars。
脚本模板
您可以声明ScriptTemplateConfigurer
bean 来指定要使用的脚本引擎,要加载的脚本 files,要调用渲染模板的 function,等等。以下 example 使用 Mustache 模板和 Nashorn JavaScript 引擎:
1 |
|
以下 example 在 XML 中显示相同的排列:
1 | <mvc:annotation-driven/> |
对于 Java 和 XML 配置,控制器看起来没有什么不同,如下面的示例所示:
1 |
|
以下 example 显示了 Mustache 模板:
1 | <html> |
使用以下参数调用 render function:
String template
:模板内容Map model
:视图 modelRenderingContext renderingContext
:RenderingContext可以访问 application context,locale,模板加载器和 URL(自 5.0 以来)
Mustache.render()
本身与此签名兼容,因此您可以直接调用它。
如果您的模板技术需要一些自定义,您可以提供实现自定义 render function 的脚本。例如,Handlerbars需要在使用它们之前编译模板,并且需要填充工具来模拟 server-side 脚本引擎中不可用的某些浏览器工具。
以下 example 显示了如何执行此操作:
1 |
|
当您使用 non-thread-safe 脚本引擎以及不是为并发而设计的模板 libraries 时,需要将
sharedEngine
property 设置为false
,例如 Handlebars 或 Nactorn 上的 React running。在这种情况下,由于这个 bug,需要 Java 8u60 或更高版本。
polyfill.js
仅定义 Handlebars 所需的window
object 以正确 run,如下所示:
1 | var window = {}; |
这个基本的render.js
implementation 在使用之前编译模板。 production-ready 实现还应该存储任何重用的缓存模板或 pre-compiled 模板。您可以在脚本端执行此操作(并处理所需的任何自定义 - 管理模板引擎 configuration,用于 example)。以下 example 显示了如何执行此操作:
1 | function render(template, model) { |
查看 Spring Framework 单元测试,Java和资源,了解更多配置示例。
JSP和JSTL
Spring Framework 有一个 built-in integration 用于将 Spring MVC 与 JSP 和 JSTL 一起使用。
View解析器
使用 JSP 开发时,可以声明InternalResourceViewResolver
或ResourceBundleViewResolver
bean。
ResourceBundleViewResolver
依赖于 properties 文件来定义映射到 class 和 URL 的视图名称。使用ResourceBundleViewResolver
,您可以通过仅使用一个解析器来混合不同类型的视图,如下面的示例所示:
1 | <!-- the ResourceBundleViewResolver --> |
InternalResourceBundleViewResolver
也可以用于 JSP。作为最佳实践,我们强烈建议将 JSP files 放在'WEB-INF'
目录下的目录中,以便 clients 无法直接访问。
1 | <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> |
JSPs与JSTL
使用 Java 标准标记 Library 时,必须使用特殊视图 class,JstlView
,因为 JSTL 需要一些准备工作,比如 I18N features 之类的东西才能工作。
Spring的JSP标签库
Spring 为命令 objects 提供请求参数的数据 binding,如前面章节所述。为了便于 JSP 页面的开发与 binding features 数据的结合,Spring 提供了一些标签,使事情变得更加容易。所有 Spring 标签都具有 HTML 转义 features 以启用或禁用字符转义。
spring.tld
标签 library 描述符(TLD)包含在spring-webmvc.jar
中。有关单个标记的综合参考,请浏览API reference或查看标记 library 描述。
Spring的表单标签库
从 version 2.0 开始,Spring 提供了一组全面的数据 binding-aware 标签,用于在使用 JSP 和 Spring Web MVC 时处理表单元素。每个标记都支持其相应 HTML 标记对应的属性集,使标记熟悉且直观易用。 tag-generated HTML 符合 HTML 4.01/XHTML 1.0.
与其他 form/input tag libraries 不同,Spring 的表单标签 library 与 Spring Web MVC 集成,使标签可以访问控制器处理的命令 object 和 reference 数据。正如我们在以下示例中所示,表单标记使 JSP 更易于开发,读取和维护。
我们浏览表单标签,查看每个标签的使用方式的示例。我们已经包含生成的 HTML 代码段,其中某些代码需要进一步评论。
Configuration
表单标签 library 捆绑在spring-webmvc.jar
中。 library 描述符称为spring-form.tld
。
要使用此 library 中的标记,请将以下指令添加到 JSP 页面的顶部:
1 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> |
其中form
是您要用于此 library 的标记的标记 name 前缀。
表单标签
此标记呈现 HTML’form’元素,并为 binding 公开内部标记的 binding 路径。它将命令 object 放在PageContext
中,以便内部标记可以访问命令 object。此 library 中的所有其他标记都是form
标记的嵌套标记。
假设我们有一个名为User
的域 object。它是一个带有 properties 的 JavaBean,例如firstName
和lastName
。我们可以将它用作表单控制器的 form-backing object,它返回form.jsp
。以下 example 显示了form.jsp
的样子:
1 | <form:form> |
firstName
和lastName
值是从页面控制器放置在PageContext
中的命令 object 中检索的。继续阅读以查看内部标记与form
标记一起使用的更复杂示例。
以下清单显示了生成的 HTML,它看起来像标准表单:
1 | <form method="POST"> |
前面的 JSP 假定 form-backing object 的变量 name 是command
。如果已将 form-backing object 放入另一个 name 下的 model(绝对是最佳实践),则可以将表单绑定到命名变量,如下面的 example 所示:
1 | <form:form modelAttribute="user"> |
输入标签
此标记默认使用绑定 value 和type='text'
呈现 HTML input
元素。有关此标记的 example,请参阅表单标签。您还可以使用 HTML5-specific 类型,例如email
,tel
,date
等。
复选框标签
此标记呈现 HTML input
标记,type
设置为checkbox
。
假设我们的User
具有诸如简报订阅和爱好列表之类的偏好。以下 example 显示了Preferences
class:
1 | public class Preferences { |
相应的form.jsp
可能类似于以下内容:
1 | <form:form> |
checkbox
标签有三种方法,可满足您的所有复选框需求。
- 方法一:当绑定 value 的类型为
java.lang.Boolean
时,如果绑定的 value 为true
,则input(checkbox)
标记为checked
。value
属性对应于setValue(Object)
value property 的已解析 value。 - 方法二:当绑定 value 的类型为
array
或java.util.Collection
时,如果配置的setValue(Object)
value 存在于绑定Collection
中,input(checkbox)
将标记为checked
。 - 方法三:对于任何其他绑定 value 类型,如果配置的
setValue(Object)
等于绑定 value,input(checkbox)
将标记为checked
。
请注意,无论采用何种方法,都会生成相同的 HTML 结构。以下 HTML 代码段定义了一些复选框:
1 | <tr> |
您可能不希望在每个复选框后看到额外的隐藏字段。如果未检查 HTML 页面中的复选框,则在提交表单后,其 value 不会作为 HTTP 请求参数的一部分发送到服务器,因此我们需要为 HTML 中的这个怪癖提供一种解决方法,以便 Spring 表单数据 binding 工作。 checkbox
标记遵循现有的 Spring 约定,包括每个复选框的下划线(_
)前缀的隐藏参数。通过这样做,你实际上告诉 Spring“复选框在表单中是可见的,我希望表单数据绑定的 object 反映复选框的 state,无论如何。”
checkboxes标签
此标记呈现多个 HTML input标签
,type
设置为checkbox
。
此部分 build 位于前一个checkbox
标记部分的 example 上。有时,您不希望在 JSP 页面中列出所有可能的爱好。您宁愿在运行时提供可用选项的列表,并将其传递给标记。这就是checkboxes
标签的目的。您可以传入包含items
property 中可用选项的Array
,或Map
。通常,绑定的 property 是一个集合,因此它可以包含用户选择的多个值。以下 example 显示了使用此标记的 JSP:
1 | <form:form> |
此 example 假定interestList
是List
可用作 model 属性,该属性包含要从中选择的值的 strings。如果使用Map
,则 map 条目 key 用作 value,map 条目的 value 用作要显示的标签。您还可以使用自定义 object,您可以使用itemValue
为 value 提供 property 名称,使用itemLabel
提供标签。
radiobutton标签
此标记呈现 HTML input
元素,type
设置为radio
。
典型用法 pattern 涉及绑定到同一 property 但具有不同值的多个标记实例,如下面的 example 所示:
1 | <tr> |
radiobuttons标签
此标记呈现多个 HTML input
元素,type
设置为radio
。
与复选框标记一样,您可能希望将可用选项作为运行时变量传递。对于此用法,您可以使用radiobuttons
标记。传入Array
,List
或Map
,其中包含items
property 中的可用选项。如果使用Map
,则 map 条目 key 用作 value,map 条目的 value 用作要显示的标签。您还可以使用自定义 object,您可以使用itemValue
为 value 提供 property 名称,使用itemLabel
提供标签,如下面的 example 所示:
1 | <tr> |
密码标签
此标记使用绑定 value 呈现类型设置为password
的 HTML input
标记。
1 | <tr> |
请注意,默认情况下,不显示密码 value。如果确实需要显示密码 value,可以将showPassword
属性的 value 设置为true
,如下面的 example 所示:
1 | <tr> |
select标签
此标记呈现 HTML’select’元素。它支持所选选项的数据绑定以及嵌套option
和options
标记的使用。
假设User
有一个技能列表。相应的 HTML 可以如下:
1 | <tr> |
如果User's
技能在草药学中,则“技能”行的 HTML 源可能如下:
1 | <tr> |
options标签
此标记呈现 HTML option
元素。它根据绑定的 value sets selected
。以下 HTML 显示了它的典型输出:
1 | <tr> |
如果User's
房子在格兰芬多,那么’House’行的 HTML 源代码如下:
1 | <tr> |
1 | 请注意添加selected 属性。 |
选项标签
此标记呈现 HTML option
元素的列表。它根据绑定的 value 设置selected
属性。以下 HTML 显示了它的典型输出:
1 | <tr> |
如果User
位于英国,则“Country”行的 HTML 源代码如下:
1 | <tr> |
1 | 请注意添加selected 属性。 |
如前面的示例所示,option
标记与options
标记的组合使用生成相同的标准 HTML,但允许您在 JSP 中显式指定仅用于显示(它所属的位置)的 value,例如默认的 string example:“ - 请选择”。
items
属性通常填充 item objects 的集合或 array。 itemValue
和itemLabel
引用那些 item objects 的 bean properties,如果指定的话。否则,item objects 本身会变成 strings。或者,您可以指定Map
项,在这种情况下,map 键被解释为选项值,map 值对应于选项标签。如果恰好也指定itemValue
或itemLabel
(或两者),则 item value property 应用于 map key,item 标签 property 应用于 map value。
textarea标签
此标记呈现 HTML textarea
元素。以下 HTML 显示了它的典型输出:
1 | <tr> |
隐藏的标签
此标记将带有type
的 HTML input
标记设置为带有绑定 value 的hidden
。要提交未绑定的隐藏 value,请使用设置为hidden
的 HTML input
标记。以下 HTML 显示了它的典型输出:
1 | <form:hidden path="house"/> |
如果我们选择将house
value 作为隐藏提交,则 HTML 将如下所示:
1 | <input name="house" type="hidden" value="Gryffindor"/> |
错误标签
此标记在 HTML span
元素中呈现字段错误。它可以访问控制器中创建的错误或由与控制器关联的任何验证器创建的错误。
假设我们要在提交表单后显示firstName
和lastName
字段的所有错误消息。我们有一个名为UserValidator
的User
class 实例的验证器,如下面的 example 所示:
1 | public class UserValidator implements Validator { |
form.jsp
可以如下:
1 | <form:form> |
如果我们在firstName
和lastName
字段中提交带有空值的表单,则 HTML 将如下所示:
1 | <form method="POST"> |
如果我们想显示给定页面的整个错误列表怎么办?下一个 example 显示errors
标记还支持一些基本的通配符功能。
path="*"
:显示所有错误。path="lastName"
:显示与lastName
字段关联的所有错误。- 如果省略
path
,则仅显示 object 错误。
以下 example 在页面顶部显示错误列表,然后在字段旁边显示 field-specific 错误:
1 | <form:form> |
HTML 将如下:
1 | <form method="POST"> |
spring-form.tld
标签 library 描述符(TLD)包含在spring-webmvc.jar
中。有关单个标记的综合参考,请浏览API reference或查看标记 library 描述。
HTTP方法转换
REST 的 key 原则是使用“Uniform Interface”。这意味着可以使用相同的四种 HTTP 方法操作所有资源(URL):GET,PUT,POST 和 DELETE。对于每个方法,HTTP 规范定义了确切的语义。例如,GET 应始终是一个安全的操作,这意味着它没有副作用,PUT 或 DELETE 应该是幂等的,这意味着你可以反复重复这些操作,但最终结果应该是相同的。虽然 HTTP 定义了这四种方法,但 HTML 只支持两种:GET 和 POST。幸运的是,有两种可能的解决方法:您可以使用 JavaScript 来执行 PUT 或 DELETE,也可以使用“real”方法作为附加参数进行 POST(在 HTML 表单中建模为隐藏输入字段)。 Spring 的HiddenHttpMethodFilter
使用后一种技巧。这个过滤器是一个普通的 Servlet 过滤器,因此,它可以与任何 web framework(不仅仅是 Spring MVC)结合使用。将此过滤器添加到 web.xml,并将具有隐藏method
参数的 POST 转换为相应的 HTTP 方法请求。
为了支持 HTTP 方法转换,更新了 Spring MVC 表单标记以支持设置 HTTP 方法。对于 example,以下代码段来自 Pet Clinic sample:
1 | <form:form method="delete"> |
前面的 example 执行 HTTP POST,“real”DELETE 方法隐藏在请求参数后面。它由HiddenHttpMethodFilter
拾取,它在 web.xml 中定义,如下面的 example 所示:
1 | <filter> |
以下 example 显示了相应的@Controller
方法:
1 |
|
HTML5标签
Spring 表单标记 library 允许输入动态属性,这意味着您可以输入任何 HTML5 特定属性。
表单input
标记支持输入text
以外的类型属性。这旨在允许呈现新的 HTML5 特定输入类型,例如email
,date
,range
等。请注意,不需要输入type='text'
,因为text
是默认类型。
Tiles
您可以在使用 Spring 的 web applications 中集成 Tiles - 就像任何其他视图技术一样。本节以广泛的方式介绍了如何执行此操作。
本节重点介绍 Spring 对
org.springframework.web.servlet.view.tiles3
包中 Tiles version 3 的支持。
依赖关系
为了能够使用 Tiles,您必须为 Tiles version 3.0.1 或更高版本以及它的传递依赖添加对项目的依赖性。
Configuration
为了能够使用 Tiles,您必须使用包含定义的 files 来配置它(有关定义和其他 Tiles 概念的基本信息,请参阅http://tiles.apache.org。在 Spring 中,这是通过使用TilesConfigurer
来完成的。以下 example ApplicationContext
configuration 显示了如何执行此操作:
1 | <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> |
前面的 example 定义了五个包含定义的 files。 files 都位于WEB-INF/defs
目录中。在初始化WebApplicationContext
时,将加载 files,并初始化定义工厂。完成之后,定义 files 中包含的 Tiles 可以用作 Spring web application 中的视图。为了能够使用视图,您必须使用ViewResolver
与 Spring 使用的任何其他视图技术一样。您可以使用两个 implementations 中的任何一个,UrlBasedViewResolver
和ResourceBundleViewResolver
。
您可以通过添加下划线然后添加 locale 来指定 locale-specific Tiles 定义,如下面的 example 所示:
1 | <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> |
使用前面的 configuration,tiles_fr_FR.xml
用于具有fr_FR
locale 的请求,默认情况下使用tiles.xml
。
由于下划线用于表示区域设置,因此我们建议不要在 Tiles 定义的文件名中使用它们。
UrlBasedViewResolver
UrlBasedViewResolver
为它必须解析的每个视图实例化给定的viewClass
。以下 bean 定义了一个UrlBasedViewResolver
:
1 | <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> |
ResourceBundleViewResolver
必须为ResourceBundleViewResolver
提供 property 文件,该文件包含解析程序可以使用的视图名称和视图 classes。以下 example 显示了ResourceBundleViewResolver
的 bean 定义以及相应的视图名称和视图 classes(取自 Pet Clinic sample):
1 | <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> |
1 | ... |
使用ResourceBundleViewResolver
时,可以轻松混合使用不同的视图技术。
请注意,TilesView
class 支持 JSTL(JSP 标准标记 Library)。
SimpleSpringPreparerFactory和SpringBeanPreparerFactory
作为高级 feature,Spring 还支持两个特殊的 Tiles PreparerFactory
implementations。有关如何在 Tiles 定义 files 中使用ViewPreparer
references 的详细信息,请参阅 Tiles 文档。
您可以根据指定的 preparer classes 指定SimpleSpringPreparerFactory
到 autowire ViewPreparer
实例,应用 Spring 的容器回调以及应用已配置的 Spring BeanPostProcessors。如果已激活 Spring 的 context-wide annotation configuration,则会自动检测并应用ViewPreparer
classes 中的注释。请注意,这需要 Tiles 定义 files 中的 preparer classes,如默认PreparerFactory
所做的那样。
您可以指定SpringBeanPreparerFactory
来操作指定的 preparer 名称(而不是 classes),从 DispatcherServlet 的 application context 中获取相应的 Spring bean。在这种情况下,完整的 bean creation process 控制着 Spring application context,允许使用显式依赖注入 configuration,作用域 beans 等。请注意,您需要为每个 preparer name 定义一个 Spring bean 定义(在 Tiles 定义中使用)。以下 example 显示了如何在TilesConfigurer
bean 上定义SpringBeanPreparerFactory
property 集:
1 | <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> |
RSS和Atom
AbstractAtomFeedView
和AbstractRssFeedView
都从AbstractFeedView
base class 继承,分别用于提供 Atom 和 RSS Feed 视图。它们基于 java.net 的罗马项目,位于包org.springframework.web.servlet.view.feed
中。
AbstractAtomFeedView
要求您实现buildFeedEntries()
方法并可选地覆盖buildFeedMetadata()
方法(默认 implementation 为空)。以下 example 显示了如何执行此操作:
1 | public class SampleContentAtomView extends AbstractAtomFeedView { |
类似的要求适用于实现AbstractRssFeedView
,如下面的示例所示:
1 | public class SampleContentAtomView extends AbstractRssFeedView { |
如果您需要访问 Locale,buildFeedItems()
和buildFeedEntries()
方法会传入 HTTP 请求。 HTTP 响应仅传递给 cookies 或其他 HTTP headers 的设置。方法返回后,Feed 会自动写入响应 object。
有关创建 Atom 视图的示例,请参阅 Alef Arendsen 的 Spring 团队博客条目。
PDF和Excel
Spring 提供了除 HTML 以外的 return 输出的方法,包括 PDF 和 Excel 电子表格。本节介绍如何使用这些 features。
文档视图简介
HTML 页面并不总是用户查看 model 输出的最佳方式,而 Spring 使得从 model 数据动态生成 PDF 文档或 Excel 电子表格变得简单。该文档是视图,并使用正确的 content type 从服务器流式传输,以(希望)使 client PC 能够运行其电子表格或 PDF 查看器应用程序作为响应。
在 order 中使用 Excel 视图,您需要将 Apache POI library 添加到 classpath,对于 PDF 生成,您需要添加(最好)OpenPDF library。
如果可能,您应该使用底层 document-generation libraries 的最新版本。特别是,我们强烈建议使用 OpenPDF(例如,OpenPDF 1.0.5)而不是过时的原始 iText 2.1.7,因为 OpenPDF 是主动维护的,并修复了不受信任的 PDF 内容的重要漏洞。
PDF视图
单词列表的简单 PDF 视图可以扩展org.springframework.web.servlet.view.document.AbstractPdfView
并实现buildPdfDocument()
方法,如下面的 example 所示:
1 | public class PdfWordList extends AbstractPdfView { |
控制器可以从外部视图定义(通过 name 引用它)或作为处理程序方法的View
实例返回此类视图。
Excel视图
自 Spring Framework 4.2 以来,org.springframework.web.servlet.view.document.AbstractXlsView
作为 Excel 视图的 base class 提供。它基于 Apache POI,具有专门的子类(AbstractXlsxView
和AbstractXlsxStreamingView
),取代过时的AbstractExcelView
class。
编程 model 类似于AbstractPdfView
,其中buildExcelDocument()
作为中心模板方法,控制器能够从外部定义(通过 name)返回这样的视图,或者从处理程序方法返回View
实例。
Jackson
Spring 支持 Jackson JSON library。
基于Jackson的JSON视图
MappingJackson2JsonView
使用 Jackson library 的ObjectMapper
将响应内容呈现为 JSON。默认情况下,model map 的全部内容(类的 exception)被编码为 JSON。对于需要过滤 map 内容的情况,您可以使用modelKeys
property 指定要编码的特定 model 属性集。您还可以使用extractValueFromSingleKeyModel
property 直接提取和序列化 single-key 模型中的 value,而不是 model 属性的 map。
您可以使用 Jackson 提供的 annotations 根据需要自定义 JSON 映射。当您需要进一步控制时,您可以 inject ObjectMapper
通过ObjectMapper
property,以便您需要为特定类型提供自定义 JSON 序列化程序和反序列化程序。
基于Jackson的XML视图
MappingJackson2XmlView
使用Jackson XML 扩展 XmlMapper
将响应内容呈现为 XML。如果 model 包含多个条目,则应使用modelKey
bean property 显式设置 object 以进行序列化。如果 model 包含单个条目,则会自动序列化。
您可以使用 JAXB 或 Jackson 提供的 annotations 根据需要自定义 XML 映射。当您需要进一步控制时,您可以 inject XmlMapper
通过ObjectMapper
property,以便自定义 XML 需要为特定类型提供序列化程序和反序列化程序。
XML编组
MarshallingView
使用 XML Marshaller
(在org.springframework.oxm
包中定义)将响应内容呈现为 XML。您可以使用MarshallingView
实例的modelKey
bean property 显式设置要被编组的对象。或者,视图迭代所有 model properties 并编组Marshaller
支持的第一个类型。有关org.springframework.oxm
包中功能的更多信息,请参阅使用 O/X Mappers 编组 XML。
XSLT视图
XSLT 是 XML 的转换语言,在 web applications 中作为视图技术很受欢迎。如果您的 application 自然地处理 XML 或者您的 model 可以很容易地转换为 XML,那么 XSLT 可以作为一种视图技术。以下部分显示如何将 XML 文档生成为 model 数据,并在 Spring Web MVC application 中使用 XSLT 进行转换。
这个 example 是一个简单的 Spring application,可以在Controller
中创建一个单词列表,并将它们添加到 model map 中。返回 map 以及 XSLT 视图的 view name。有关 Spring Web MVC 的Controller
接口的详细信息,请参阅带注释的控制器。 XSLT 控制器将单词列表转换为准备转换的简单 XML 文档。
Beans
Configuration 是一个简单的 Spring web application 的标准:MVC configuration 必须定义XsltViewResolver
bean 和常规的 MVC annotation configuration。以下 example 显示了如何执行此操作:
1 |
|
控制器
我们还需要一个封装 word-generation 逻辑的 Controller。
控制器逻辑封装在@Controller
class 中,处理程序方法定义如下:
1 |
|
到目前为止,我们只创建了一个 DOM 文档并将其添加到 Model map 中。请注意,您还可以将 XML 文件作为Resource
加载,并使用它而不是自定义 DOM 文档。
有些软件包可以自动“统一”object 图形,但是,在 Spring 中,您可以完全灵活地以您选择的任何方式从 model 创建 DOM。这可以防止 XML 的转换在 model 数据的结构中扮演太大的角色,这在使用工具管理 DOMification process 时是一种危险。
转型
最后,XsltViewResolver
解析“home”XSLT 模板文件并将 DOM 文档合并到其中以生成我们的视图。如XsltViewResolver
configuration 中所示,XSLT 模板位于WEB-INF/xsl
目录中的war
文件中,并以xslt
文件扩展名结尾。
以下 example 显示了一个 XSLT 转换:
1 |
|
前面的转换呈现为以下 HTML:
1 | <html> |
MVC配置
MVC Java configuration 和 MVC XML 名称空间提供适用于大多数 applications 的默认 configuration 和用于自定义它的 configuration API。
有关 configuration API 中未提供的更高级自定义,请参阅高级 Java 配置和高级 XML 配置。
您不需要了解 MVC Java configuration 和 MVC 名称空间创建的基础 beans。如果您想了解更多信息,请参阅特殊的Bean类型和Web MVC 配置。
启用MVC配置
在 Java configuration 中,您可以使用@EnableWebMvc
annotation 来启用 MVC configuration,如下面的 example 所示:
1 |
|
在 XML configuration 中,您可以使用<mvc:annotation-driven>
元素启用 MVC configuration,如下面的 example 所示:
1 |
|
前面的 example 注册了许多 Spring MVC 基础设施 beans并适应 classpath 上可用的依赖项(对于示例,JSON,XML 和其他的有效负载转换器)。
MVC配置API
在 Java configuration 中,您可以实现WebMvcConfigurer
接口,如下面的 example 所示:
1 |
|
在 XML 中,您可以检查<mvc:annotation-driven/>
的属性和 sub-elements。您可以查看Spring MVC XML schema或使用 IDE 的 code completion feature 来发现可用的属性和 sub-elements。
类型转换
默认格式化,安装了Number
和Date
类型,包括对@NumberFormat
和@DateTimeFormat
注释的支持。如果 class 路径中存在 Joda-Time,则还会安装对 Joda-Time 格式 library 的完全支持。
在 Java configuration 中,您可以注册自定义格式化程序和转换器,如下面的示例所示:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 |
|
有关何时使用 FormatterRegistrar implementations 的详细信息,请参阅FormatterRegistrar SPI和
FormattingConversionServiceFactoryBean
。
验证
默认情况下,如果 class 路径上存在Bean 验证(对于 example,Hibernate Validator),则LocalValidatorFactoryBean
将注册为 global 验证器,以便与控制器方法 arguments 上的@Valid
和Validated
一起使用。
在 Java configuration 中,您可以自定义 global Validator
实例,如下面的 example 所示:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 |
|
请注意,您还可以在本地注册Validator
implementations,如下面的 example 所示:
1 |
|
如果需要在某处注入
LocalValidatorFactoryBean
,请创建 bean 并在 order 中使用@Primary
标记它以避免与 MVC configuration 中声明的冲突。
拦截器
在 Java configuration 中,您可以注册拦截器以应用传入请求,如下面的 example 所示:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:interceptors> |
内容类型
您可以配置 Spring MVC 如何根据请求确定所请求的媒体类型(对于 example,Accept
标头,URL 路径扩展,查询参数等)。
默认情况下,首先检查 URL 路径扩展名 - 将json
,xml
,rss
和atom
注册为已知的 extensions(取决于 classpath 依赖项)。第二次检查Accept
标题。
请考虑仅将这些默认值更改为Accept
标头,如果必须使用 URL-based content type 解析,请考虑在路径 extensions 上使用查询参数策略。有关详细信息,请参阅后缀 Match和后缀 Match 和 RFD。
在 Java configuration 中,您可以自定义请求的 content type 解析,如下面的 example 所示:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/> |
消息转换器
您可以通过覆盖configureMessageConverters()(替换 Spring MVC 创建的默认转换器)或覆盖extendMessageConverters()(自定义默认转换器或将其他转换器添加到默认转换器)来自定义 Java configuration 中的HttpMessageConverter
。
以下 example 添加了带有自定义ObjectMapper
而不是默认值的 XML 和 Jackson JSON 转换器:
1 |
|
在上面的示例中,Jackson2ObjectMapperBuilder用于为启用缩进的MappingJackson2HttpMessageConverter
和MappingJackson2XmlHttpMessageConverter
创建 common configuration,自定义 date 格式,以及jackson-module-parameter-names的注册,这增加了对访问参数名称的支持(在 Java 8 中添加了 feature)。
此构建器自定义 Jackson 的默认 properties,如下所示:
如果在 classpath 上检测到以下 well-known 模块,它还会自动注册:
- jackson-datatype-jdk7:支持 Java 7 类型,例如
java.nio.file.Path
。 - jackson-datatype-joda:支持 Joda-Time 类型。
- jackson-datatype-jsr310:支持 Java 8 Date 和 Time 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-specific 类型和 properties(包括 lazy-loading 方面)。
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:annotation-driven> |
View控制器
这是一个快捷方式,用于定义在调用时立即转发到视图的ParameterizableViewController
。如果在视图生成响应之前没有要执行的 Java 控制器逻辑,则可以在静态情况下使用它。
下面的 Java configuration 示例将/
的请求转发给名为home
的视图:
1 |
|
以下 example 与前面的 example 实现相同的功能,但使用 XML,使用<mvc:view-controller>
元素:
1 | <mvc:view-controller path="/" view-name="home"/> |
View解析器
MVC configuration 简化了视图解析器的注册。
以下 Java configuration example 使用 JSP 和 Jackson 作为 JSON 呈现的默认View
配置内容 negotiation view resolution:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:view-resolvers> |
但请注意,FreeMarker,Tiles,Groovy Markup 和脚本模板也需要配置底层视图技术。
MVC 名称空间提供专用元素。以下 example 适用于 FreeMarker:
1 | <mvc:view-resolvers> |
在 Java configuration 中,您可以添加相应的Configurer
bean,如下面的 example 所示:
1 |
|
静态资源
此选项提供了一种从资源 -based 位置列表中提供静态资源的便捷方法。
在下一个示例中,给定以/resources
开头的请求,相对路径用于在 web application 根目录下或/static
下的 classpath 上查找和提供相对于/public
的静态资源。资源的使用期限为 one-year,以确保最大程度地使用浏览器缓存并减少浏览器发出的 HTTP 请求。还会评估Last-Modified
标头,如果存在,则返回304
status code。
以下清单显示了如何使用 Java configuration 执行此操作:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:resources mapping="/resources/**" |
资源处理程序还支持ResourceResolver 实现和ResourceTransformer 实现链,您可以使用它来创建工具链以使用优化的资源。
您可以将VersionResourceResolver
用于基于从内容计算的 MD5 哈希,固定 application version 或其他的版本化资源 URL。 ContentVersionStrategy
(MD5 哈希)是一个不错的选择 - 有一些值得注意的 exceptions,例如与模块加载器一起使用的 JavaScript 资源。
以下 example 显示了如何在 Java configuration 中使用VersionResourceResolver
:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:resources mapping="/resources/**" location="/public/"> |
然后,您可以使用ResourceUrlProvider
来 rewrite URL 并将完整的解析器和变换器链应用于 example,以插入版本。 MVC configuration 提供ResourceUrlProvider
bean,以便可以将其注入其他人。您还可以使用ResourceUrlEncodingFilter
使用ResourceUrlEncodingFilter
透明化 Thymeleaf,JSP,FreeMarker 以及其他依赖HttpServletResponse#encodeURL
的 URL 标记。
请注意,当同时使用EncodedResourceResolver
(对于 example,用于提供 gzip 或 brotli-encoded 资源)和VersionedResourceResolver
时,必须在此 order 中注册它们。这样可以确保始终根据未编码的文件可靠地计算 content-based 版本。
WebJarsResourceResolver
也通过WebJarsResourceResolver
支持,并在 class 路径上存在org.webjars:webjars-locator
时自动注册。解析器可以 re-write URL 包含 jar 的 version,也可以匹配到没有版本的传入 URL - 例如,/jquery/jquery.min.js
到/jquery/1.2.0/jquery.min.js
。
默认Servlet
Spring MVC 允许将DispatcherServlet
映射到/
(从而覆盖容器的默认 Servlet 的映射),同时仍允许容器的默认 Servlet 处理静态资源请求。它使用/**
的 URL 映射配置DefaultServletHttpRequestHandler
,并将相对于其他 URL 映射的最低优先级配置为DefaultServletHttpRequestHandler
。
此处理程序将所有请求转发到默认的 Servlet。因此,它必须保留在所有其他 URL HandlerMappings
的 order 中的最后一个。如果你使用<mvc:annotation-driven>
就是这种情况。或者,如果您设置自己的自定义HandlerMapping
实例,请务必将其order
property 设置为低于DefaultServletHttpRequestHandler
的 value,即Integer.MAX_VALUE
。
以下 example 显示了如何使用默认设置启用 feature:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:default-servlet-handler/> |
覆盖/
Servlet 映射的警告是,必须通过 name 而不是 path 来检索默认 Servlet 的RequestDispatcher
。 DefaultServletHttpRequestHandler
使用大多数主要 Servlet 容器(包括 Tomcat,Jetty,GlassFish,JBoss,Resin,WebLogic 和 WebSphere)的已知名称列表,在启动 time 时尝试 auto-detect 为容器的默认 Servlet。如果默认 Servlet 是 custom-configured 且具有不同的 name,或者如果在默认的 Servlet name 未知的情况下使用了不同的 Servlet 容器,则必须显式提供默认的 Servlet 的 name,如下面的 example 所示:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/> |
路径匹配
您可以自定义与路径匹配和 URL 处理相关的选项。有关各个选项的详细信息,请参阅PathMatchConfigurer javadoc。
以下 example 显示了如何在 Java configuration 中自定义路径匹配:
1 |
|
以下 example 显示了如何在 XML 中实现相同的 configuration:
1 | <mvc:annotation-driven> |
高级Java配置
@EnableWebMvc
进口DelegatingWebMvcConfiguration
,其中:
- 为 Spring MVC applications 提供默认的 Spring configuration
- 检测并委托
WebMvcConfigurer
implementations 来自定义 configuration。
对于高级模式,您可以删除@EnableWebMvc
并直接从DelegatingWebMvcConfiguration
扩展而不是实现WebMvcConfigurer
,如下面的 example 所示:
1 |
|
您可以在WebConfig
中保留现有方法,但现在您也可以从 base class 覆盖 bean 声明,并且您仍然可以在 classpath 上包含任意数量的其他WebMvcConfigurer
__mplement。
高级XML配置
MVC 命名空间没有高级模式。如果你需要在 bean 上自定义 property,否则你无法改变,你可以使用 Spring ApplicationContext
的BeanPostProcessor
生命周期 hook,如下面的 example 所示:
1 |
|
请注意,您需要将MyPostProcessor
声明为 bean,显式为 XML 或允许通过<component-scan/>
声明进行检测。
HTTP/2
Servlet 4 容器需要支持 HTTP/2,而 Spring Framework 5 与 Servlet API 兼容 4.从编程 model 的角度来看,没有特定的 applications 需要做的事情。但是,有一些与 server configuration 相关的注意事项。有关更多详细信息,请参阅HTTP/2 维基页面。
Servlet API 确实公开了一个与 HTTP/2 相关的构造。您可以使用javax.servlet.http.PushBuilder
主动将资源推送到 clients,并且它支持方法参数到@RequestMapping
方法。
REST-Clients
本节介绍 client-side 访问 REST endpoints 的选项。
RestTemplate
RestTemplate
是执行 HTTP 请求的同步 client。它是原始的 Spring REST client,并在底层的 HTTP client libraries 上公开了一个简单的 template-method API。
从 5.0 开始,non-blocking,reactive
WebClient
提供了RestTemplate
的现代替代方案,同时有效地支持同步和异步以及流方案。RestTemplate
将在未来的 version 中弃用,并且不会在未来添加主要的新 features。
有关详细信息,请参阅REST Endpoints。
Web客户端
WebClient
是执行 HTTP 请求的 non-blocking,reactive client。它在 5.0 中引入并提供了RestTemplate
的现代替代方案,同时有效支持同步和异步以及流方案。
与RestTemplate
相反,WebClient
支持以下内容:
- Non-blocking I/O。
- Reactive Streams 背压。
- 高并发性,硬件资源更少。
- Functional-style,fluent API 利用 Java 8 lambdas。
- 同步和异步交互。
- 从服务器流式传输或向下传输。
有关详细信息,请参阅Web 客户端。
Testing
本节总结了spring-test
中可用于 Spring MVC applications 的选项。
- Servlet API 模拟:Mock _实现 Servlet API contracts 用于单元测试控制器,过滤器和其他 web 组件。有关详细信息,请参阅Servlet API mock objects。
- TestContext Framework:支持在 JUnit 和 TestNG 测试中 loading Spring configuration,包括跨测试方法高效缓存已加载的 configuration,并支持使用
MockServletContext
加载WebApplicationContext
。有关详细信息,请参阅TestContext Framework。 - Spring MVC 测试:framework,也称为
MockMvc
,用于通过DispatcherServlet
(即支持 annotations)测试带注释的控制器,完成 Spring MVC 基础结构但没有 HTTP 服务器。有关详细信息,请参阅Spring MVC 测试。 - Client-side REST:
spring-test
提供MockRestServiceServer
,您可以将其用作 mock 服务器来测试内部使用RestTemplate
的 client-side code。有关详细信息,请参阅Client REST 测试。 WebTestClient
:专为测试 WebFlux applications 而构建,但它也可用于通过 HTTP 连接对任何服务器进行 end-to-end integration 测试。它是 non-blocking,reactive client,非常适合测试异步和流式方案。
WebSockets
reference 文档的这一部分包括对 Servlet 堆栈的支持,包括原始 WebSocket 交互的 WebSocket 消息传递,通过 SockJS 的 WebSocket 仿真,以及通过 STOMP 作为 sub-protocol over WebSocket 的 publish-subscribe 消息传递。
WebSocket 简介
WebSocket 协议RFC 6455提供了一种标准方法,可以通过单个 TCP 连接在 client 和服务器之间建立 full-duplex,two-way 通信 channel。它是来自 HTTP 的不同 TCP 协议,但设计为使用端口 80 和 443 并允许 re-use 现有防火墙规则通过 HTTP 工作。
WebSocket 交互以 HTTP 请求开始,该 HTTP 请求使用 HTTP Upgrade
标头进行升级,或者在这种情况下,切换到 WebSocket 协议。以下 example 显示了这样的交互:
1 | GET /spring-websocket-portfolio/portfolio HTTP/1.1 |
1 | Upgrade 标题。 |
2 | 使用Upgrade 连接。 |
具有 WebSocket 支持的服务器返回类似于以下内容的输出,而不是通常的 200 状态 code:
1 | HTTP/1.1 101 Switching Protocols (1) |
1 | 协议切换 |
成功握手后,HTTP 升级请求所基于的 TCP socket 将保持打开状态,以便 client 和服务器继续发送和接收消息。
有关 WebSockets 如何工作的完整介绍超出了本文档的范围。请参阅 RFC 6455,HTML5 的 WebSocket 章节,或者 Web 上的任何介绍和教程。
请注意,如果 WebSocket 服务器在 web 服务器(e.g. nginx)后面运行,则可能需要将其配置为将 WebSocket 升级请求传递到 WebSocket 服务器。同样,如果 application 在云环境中运行,请检查与 WebSocket 支持相关的云提供程序的说明。
HTTP与WebSocket
尽管 WebSocket 被设计为 HTTP-compatible 并以 HTTP 请求开始,但重要的是要理解这两种协议会导致非常不同的体系结构和 application 编程模型。
在 HTTP 和 REST 中,application 被建模为多个 URL。要与 application 进行交互,clients 访问这些 URL,request-response 样式。 Servers 根据 HTTP URL,方法和 headers 将请求路由到相应的处理程序。
相比之下,在 WebSockets 中,初始连接通常只有一个 URL。随后,所有 application 消息都在同一 TCP 连接上流动。这指向一个完全不同的异步,event-driven,messaging architecture。
WebSocket 也是一种 low-level 传输协议,与 HTTP 不同,它不对消息内容规定任何语义。这意味着除非 client 和服务器就消息语义达成一致,否则无法对路由进行 route 或 process。
WebSocket clients 和服务器可以通过 HTTP 握手请求上的Sec-WebSocket-Protocol
标头协商使用 higher-level,消息传递协议(对于 example,STOMP)。如果没有,他们需要提出自己的惯例。
何时使用WebSockets
WebSockets 可以使 web 页面成为动态和交互式的。但是,在许多情况下,Ajax 和 HTTP 流式传输或 long 轮询的组合可以提供简单有效的解决方案。
例如,新闻,邮件和社交订阅源需要动态更新,但每隔几分钟就可以完全正常更新。另一方面,协作,游戏和财务应用程序需要更接近 real-time。
仅延迟不是决定因素。如果消息量相对较低(例如,监视网络故障),HTTP 流式传输或轮询可以提供有效的解决方案。它是低延迟,高频率和高容量的组合,是使用 WebSocket 的最佳选择。
还要记住,在 Internet 上,超出控制范围的限制性代理可能会阻止 WebSocket 交互,因为它们未配置为传递Upgrade
标头,或者因为它们关闭 long-lived 连接,这些连接显示为 idle。这意味着在防火墙内部使用 WebSocket 进行内部应用比使用面向公众的应用程序更直接。
WebSocketAPI
Spring Framework 提供了一个 WebSocket API,您可以使用它来编写处理 WebSocket 消息的 client-和 server-side applications。
WebSocketHandler
创建 WebSocket 服务器就像实现WebSocketHandler
一样简单,或者更有可能扩展TextWebSocketHandler
或BinaryWebSocketHandler
。以下 example 使用TextWebSocketHandler
:
1 | import org.springframework.web.socket.WebSocketHandler; |
有专门的 WebSocket Java configuration 和 XML 名称空间支持,用于将前面的 WebSocket 处理程序映射到特定的 URL,如下面的示例所示:
1 | import org.springframework.web.socket.config.annotation.EnableWebSocket; |
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
pereceding example 用于 Spring MVC applications,应该包含在DispatcherServlet的 configuration 中。但是,Spring 的 WebSocket 支持不依赖于 Spring MVC。在WebSocketHttpRequestHandler的帮助下将WebSocketHandler
集成到其他 HTTP-serving 环境中相对简单。
WebSocket信号交换
自定义初始 HTTP WebSocket 握手请求的最简单方法是通过HandshakeInterceptor
,它在握手之前“之前”和“之后”暴露方法。您可以使用此类拦截器来阻止握手或使WebSocketSession
可以使用任何属性。以下 example 使用 built-in 拦截器将 HTTP session 属性传递给 WebSocket session:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
更高级的选项是扩展执行 WebSocket 握手步骤的DefaultHandshakeHandler
,包括验证 client 原点,协商 sub-protocol 和其他详细信息。如果需要在 order 中配置自定义RequestUpgradeStrategy
以适应 WebSocket 服务器引擎和尚未支持的 version,则 application 可能还需要使用此选项(有关此主题的更多信息,请参阅部署。 Java configuration 和 XML 命名空间都可以配置自定义HandshakeHandler
。
Spring 提供
WebSocketHandlerDecorator
base class,您可以使用它来装饰带有其他行为的WebSocketHandler
。使用 WebSocket Java configuration 或 XML 命名空间时,默认情况下会提供并添加 Logging 和 exception 处理 implementations。ExceptionWebSocketHandlerDecorator
捕获由任何WebSocketHandler
方法引起的所有未捕获的 exceptions,并以状态1011
关闭 WebSocket session,这表示服务器错误。
部署
Spring WebSocket API 易于集成到 Spring MVC application 中,其中DispatcherServlet
同时提供 HTTP WebSocket 握手和其他 HTTP 请求。通过调用WebSocketHttpRequestHandler
也可以轻松地集成到其他 HTTP 处理场景中。这很方便易懂。但是,有关 JSR-356 运行时的特殊注意事项。
Java WebSocket API(JSR-356)提供了两种部署机制。第一个涉及启动时的 Servlet 容器 classpath 扫描(Servlet 3 feature)。另一个是在 Servlet 容器初始化时使用的注册 API。这些机制都不能使用单个“前端控制器”进行所有 HTTP 处理 - 包括 WebSocket 握手和所有其他 HTTP 请求 - 例如 Spring MVC 的DispatcherServlet
。
这是 JSR-356 的一个重要限制,Spring 的 WebSocket 支持使用 server-specific RequestUpgradeStrategy
__mplement 进行地址,即使在 JSR-356 运行时运行时也是如此。目前,Tomcat,Jetty,GlassFish,WebLogic,WebSphere 和 Undertow(以及 WildFly)都有这样的策略。
已经创建了一个克服 Java WebSocket API 中的上述限制的请求,可以在eclipse-ee4j/websocket-api#211处遵循。 Tomcat,Undertow 和 WebSphere 提供了自己的 API 替代方案,可以实现这一点,并且 Jetty 也可以。我们希望更多的服务器能够做到这一点。
第二个考虑因素是具有 JSR-356 支持的 Servlet 容器应该执行ServletContainerInitializer
(SCI)扫描,这可以减慢 application 启动速度 - 在某些情况下,显着。如果在使用 JSR-356 支持升级到 Servlet 容器 version 后观察到重大影响,则应该可以通过使用web.xml
中的<absolute-ordering />
元素选择性地启用或禁用 web 片段(和 SCI 扫描),如下面的示例所示:
1 | <web-app xmlns="http://java.sun.com/xml/ns/javaee" |
然后,您可以通过 name 选择性地启用 web 片段,例如 Spring 自己的SpringServletContainerInitializer
,它提供对 Servlet 3 Java 初始化 API 的支持。以下 example 显示了如何执行此操作:
1 | <web-app xmlns="http://java.sun.com/xml/ns/javaee" |
服务器Configuration
每个底层 WebSocket 引擎都会公开控制运行时特征的 configuration properties,例如消息缓冲区大小,idle 超时等等。
对于 Tomcat,WildFly 和 GlassFish,您可以将ServletServerContainerFactoryBean
添加到 WebSocket Java 配置中,如下面的示例所示:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
对于 client-side WebSocket configuration,您应该使用
WebSocketContainerFactoryBean
(XML)或ContainerProvider.getWebSocketContainer()
(Java configuration)。
对于 Jetty,您需要提供 pre-configured Jetty WebSocketServerFactory
并通过 WebSocket Java 配置将其插入 Spring 的DefaultHandshakeHandler
。以下 example 显示了如何执行此操作:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
允许Origins
从 Spring Framework 4.1.5 开始,WebSocket 和 SockJS 的默认行为是仅接受 same-origin 请求。也可以允许所有或指定的起源列表。此检查主要是为浏览器客户端设计的。没有什么可以阻止其他类型的 clients 修改Origin
标头 value(有关详细信息,请参阅RFC 6454:Web Origin 概念)。
三种可能的行为是:
- 仅允许 same-origin 个请求(默认):在此模式下,启用 SockJS 时,Iframe HTTP 响应头
X-Frame-Options
设置为SAMEORIGIN
,并且禁用 JSONP 传输,因为它不允许检查请求的来源。因此,启用此模式时不支持 IE6 和 IE7。 - 允许指定的原始列表:每个允许的原点必须以
http://
或https://
开头。在此模式下,启用 SockJS 时,将禁用 IFrame 传输。因此,启用此模式时,不支持 IE6 到 IE9。 - 允许所有来源:要启用此模式,您应提供
*
作为允许的原始值。在此模式下,所有传输都可用。
您可以配置 WebSocket 和 SockJS 允许的源,如下面的示例所示:
1 | import org.springframework.web.socket.config.annotation.EnableWebSocket; |
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
SockJS后备
在公共 Internet 上,受控制之外的限制性代理可能会阻止 WebSocket 交互,因为它们未配置为传递Upgrade
标头,或者因为它们关闭似乎为 idle 的 long-lived 连接。
这个问题的解决方案是 WebSocket 仿真 - 也就是说,首先尝试使用 WebSocket,然后再依靠 HTTP-based 技术来模拟 WebSocket 交互并公开相同的 application-level API。
在 Servlet 堆栈上,Spring Framework 为 SockJS 协议提供服务器(以及 client)支持。
概述
SockJS 的目标是让 applications 使用 WebSocket API,但在运行时必要时可以回退到 non-WebSocket 替代品,而无需更改 application code。
SockJS 包括:
- SockJS 协议以可执行文件叙述测试的形式定义。
- SockJS JavaScript client - 用于浏览器的 client library。
- SockJS 服务器 implementations,包括 Spring Framework
spring-websocket
模块中的一个。 spring-websocket
模块中的 SockJS Java client(自 version 4.1 起)。
SockJS 专为在浏览器中使用而设计。它使用各种技术来支持各种浏览器版本。有关 SockJS 传输类型和浏览器的完整列表,请参阅SockJS client页面。传输分为三大类:WebSocket,HTTP Streaming 和 HTTP Long Polling。有关这些类别的概述,请参阅这篇博文。
SockJS client 首先发送GET /info
以从服务器获取基本信息。之后,它必须决定使用什么传输。如果可能,使用 WebSocket。如果没有,在大多数浏览器中,至少有一个 HTTP 流选项。如果不是,则使用 HTTP(long)轮询。
所有传输请求都具有以下 URL 结构:
1 | http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport} |
哪里:
{server-id}
对于在 cluster 中路由请求很有用,但是否则不会使用。{session-id}
关联属于 SockJS session 的 HTTP 请求。{transport}
表示传输类型(对于 example,websocket
,xhr-streaming
和其他)。
WebSocket 传输只需要一个 HTTP 请求即可进行 WebSocket 握手。之后的所有消息都在该 socket 上交换。
HTTP 传输需要更多请求。 Ajax/XHR streaming,对于 example,依赖于 server-to-client 消息的 long-running 请求和 client-to-server 消息的其他 HTTP POST 请求。 Long 轮询类似,只是它在每次 server-to-client 发送后_end 当前请求。
SockJS 增加了最小的消息框架。例如,服务器最初发送字母o
(“打开”框架),如果没有消息流动 25 秒(默认情况下),消息将发送为a["message1","message2"]
(JSON-encoded array),字母h
(“心跳”帧),以及字母c
(“关闭”框架)关闭 session。
要了解更多信息,请在浏览器中运行 example 并查看 HTTP 请求。 SockJS client 允许修复传输列表,因此可以在 time 时查看每个传输。 SockJS client 还提供了一个 debug flag,它在浏览器 console 中启用了有用的消息。在服务器端,您可以为org.springframework.web.socket
启用TRACE
logging。有关更多详细信息,请参阅 SockJS 协议叙述测试。
启用SockJS
您可以通过 Java configuration 启用 SockJS,如下面的示例所示:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
前面的 example 用于 Spring MVC applications,应该包含在DispatcherServlet的 configuration 中。但是,Spring 的 WebSocket 和 SockJS 支持并不依赖于 Spring MVC。在SockJsHttpRequestHandler的帮助下集成到其他 HTTP 服务环境相对简单。
在浏览器端,applications 可以使用sockjs-client(version 1.0.x)。它模拟 W3C WebSocket API 并与服务器通信以选择最佳传输选项,具体取决于运行它的浏览器。请参阅sockjs-client页面和浏览器支持的传输类型列表。 client 还提供了几个 configuration 选项 - 对于 example,指定要包含的传输。
IE8和9
Internet Explorer 8 和 9 仍在使用中。他们是拥有 SockJS 的关键原因。本节介绍有关在这些浏览器中运行的重要注意事项。
SockJS client 通过使用 Microsoft 的XDomainRequest支持 IE 8 和 9 中的 Ajax/XHR 流式传输。这适用于跨域但不支持发送 cookies。 Cookies 通常对 Java applications 很重要。但是,由于 SockJS client 可以与许多服务器类型(不仅仅是 Java 类型)一起使用,因此需要知道 cookies 是否重要。如果是这样,SockJS client 更喜欢 Ajax/XHR 进行流式传输。否则,它依赖于 iframe-based 技术。
来自 SockJS client 的第一个/info
请求是对可能影响客户端传输选择的信息的请求。其中一个细节是 server application 是否依赖于 cookies(用于 example,用于身份验证或使用粘性会话进行群集)。 Spring 的 SockJS 支持包括一个名为sessionCookieNeeded
的 property。默认情况下启用它,因为大多数 Java applications 依赖于JSESSIONID
cookie。如果您的 application 不需要它,您可以关闭此选项,然后 SockJS client 应该在 IE 8 和 9 中选择xdr-streaming
。
如果您确实使用 iframe-based 传输,请记住,可以通过将 HTTP 响应头X-Frame-Options
设置为DENY
,SAMEORIGIN
或ALLOW-FROM <origin>
来指示浏览器阻止在给定页面上使用 IFrame。这用于防止点击劫持。
Spring Security 3.2 支持在每个响应上设置
X-Frame-Options
。默认情况下,Spring Security Java configuration 将其设置为DENY
。在 3.2 中,Spring Security XML 命名空间默认情况下不设置该标头,但可以配置为执行此操作。将来,它可以默认设置它。
有关如何配置X-Frame-Options
标头设置的详细信息,请参阅 Spring Security 文档的默认安全 Headers。您还可以查看SEC-2501以获取更多背景信息。
如果你的 application 添加X-Frame-Options
响应头(因为它应该!)并依赖于 iframe-based 传输,你需要将头 value 设置为SAMEORIGIN
或ALLOW-FROM <origin>
。 Spring SockJS 支持还需要知道 SockJS client 的位置,因为它是从 iframe 加载的。默认情况下,iframe 设置为从 CDN 位置下载 SockJS client。配置此选项以使用与 application 相同的源的 URL 是一个很好的 idea。
以下 example 显示了如何在 Java configuration 中执行此操作:
1 |
|
XML 命名空间通过<websocket:sockjs>
元素提供类似的选项。
在初始开发期间,请启用 SockJS client
devel
模式,以防止浏览器缓存否则将被缓存的 SockJS 请求(如 iframe)。有关如何启用它的详细信息,请参阅SockJS client页面。
心跳
SockJS 协议要求服务器发送心跳消息以阻止代理断定连接已挂起。 Spring SockJS configuration 有一个名为heartbeatTime
的 property,可用于自定义频率。默认情况下,假设在该连接上没有发送其他消息,则会在 25 秒后发送心跳。对于公共 Internet 应用,此 25-second value 位于 line 中,以下IETF 推荐。
当通过 WebSocket 和 SockJS 使用 STOMP 时,如果 STOMP client 和服务器协商要交换的心跳,则禁用 SockJS 心跳。
Spring SockJS 支持还允许您配置TaskScheduler
来安排心跳任务。任务计划程序由线程池支持,默认设置基于可用处理器的数量。您应该考虑根据您的特定需求自定义设置。
Client断开连接
HTTP 流和 HTTP long 轮询 SockJS 传输要求连接保持打开时间比平时长。有关这些技术的概述,请参阅这篇博文。
在 Servlet 容器中,这是通过 Servlet 3 异步支持完成的,它允许退出 Servlet 容器线程,处理请求,并继续写入来自另一个线程的响应。
一个特定的问题是 Servlet API 不为已经消失的客户端提供通知。见eclipse-ee4j/servlet-api#44。但是,Servlet 容器会在后续尝试写入响应时引发 exception。由于 Spring 的 SockJS 服务支持 server-sent 心跳(默认情况下每 25 秒),这意味着通常会在_time 期间(或更早,如果更频繁地发送消息)检测到 client 断开连接。
因此,可能会发生网络 I/O 故障,因为 client 已断开连接,这可能会使用不必要的堆栈跟踪填充 log。 Spring 尽最大努力识别代表 client 断开连接(特定于每个服务器)的此类网络故障,并使用专用 log 类别(在
AbstractSockJsSession
中定义)log 记录最小消息。如果需要查看堆栈跟踪,可以将 log 类别设置为 TRACE。
SockJS和CORS
如果允许 cross-origin 请求(请参阅允许起源),则 SockJS 协议在 XHR 流和轮询传输中使用 CORS 进行 cross-domain 支持。因此,除非检测到响应中存在 CORS headers,否则会自动添加 CORS headers。因此,如果已经将 application 配置为提供 CORS 支持(对于 example,通过 Servlet 过滤器),Spring 的SockJsService
将跳过此部分。
也可以通过在 Spring 的 SockJsService 中设置suppressCors
property 来禁用这些 CORS _header 的添加。
SockJS 期望以下 headers 和值:
Access-Control-Allow-Origin
:从Origin
请求标头的 value 初始化。Access-Control-Allow-Credentials
:始终设置为true
。Access-Control-Request-Headers
:从等效请求标头中的值初始化。Access-Control-Allow-Methods
:传输支持的 HTTP 方法(请参阅TransportType
enum)。Access-Control-Max-Age
:设置为 31536000(1 年)。
对于确切的 implementation,请参阅AbstractSockJsService
中的addCorsHeaders
和 source code 中的TransportType
enum。
或者,如果 CORS configuration 允许,请考虑使用 SockJS 端点前缀排除 URL,从而让 Spring 的SockJsService
处理它。
SockJsClient
Spring 提供了一个 SockJS Java client,无需使用浏览器即可连接到 remote SockJS endpoints。当需要通过公共网络在两个服务器之间进行双向通信时(即,网络代理可以排除使用 WebSocket 协议的情况下),这尤其有用。 SockJS Java client 对于测试目的也非常有用(例如,模拟大量并发用户)。
SockJS Java client 支持websocket
,xhr-streaming
和xhr-polling
传输。其余的仅适用于浏览器。
您可以使用以下命令配置WebSocketTransport
:
- 在 JSR-356 运行时
StandardWebSocketClient
。 JettyWebSocketClient
使用 Jetty 9 本机 WebSocket API。- Spring 的
WebSocketClient
的任何 implementation。
根据定义,XhrTransport
支持xhr-streaming
和xhr-polling
,因为从 client 的角度来看,除了用于连接服务器的 URL 之外没有其他区别。目前有两个实现:
RestTemplateXhrTransport
对 HTTP 请求使用 Spring 的RestTemplate
。JettyXhrTransport
使用 Jetty 的HttpClient
进行 HTTP 请求。
以下 example 显示了如何创建 SockJS client 并连接到 SockJS 端点:
1 | List<Transport> transports = new ArrayList<>(2); |
SockJS 使用 JSON 格式的数组进行消息传递。默认情况下,使用 Jackson 2 并且需要在 classpath 上。或者,您可以配置
SockJsMessageCodec
的自定义 implementation 并在SockJsClient
上配置它。
要使用SockJsClient
模拟大量并发用户,您需要配置基础 HTTP client(用于 XHR 传输)以允许足够数量的连接和线程。以下 example 显示了如何使用 Jetty 执行此操作:
1 | HttpClient jettyHttpClient = new HttpClient(); |
以下 example 显示了您应该考虑自定义的 server-side SockJS-related properties(请参阅 javadoc 以获取详细信息):
1 |
|
1 | 将streamBytesLimit property 设置为 512KB(默认值为 128KB - 128 * 1024 )。 |
2 | 将httpMessageCacheSize property 设置为 1,000(默认值为100 )。 |
3 | 将disconnectDelay property 设置为 30 property 秒(默认值为 5 秒 - 5 * 1000 )。 |
STOMP
WebSocket 协议定义了两种类型的消息(文本和二进制),但它们的内容是未定义的。该协议定义了 client 和服务器协商 sub-protocol(即 higher-level 消息传递协议)以在 WebSocket 之上使用的机制,以定义每个消息可以发送什么类型的消息,格式是什么,每条消息的内容,以及等等。使用 sub-protocol 是可选的,但无论如何,client 和服务器需要就定义消息内容的某些协议达成一致。
概述
STOMP(简单文本导向的消息传递协议)最初是为脚本语言(如 Ruby,Python 和 Perl)创建的,用于连接企业消息代理。它旨在解决常用消息传递模式的最小子集。 STOMP 可用于任何可靠的 two-way 流网络协议,例如 TCP 和 WebSocket。虽然 STOMP 是 text-oriented 协议,但消息有效负载可以是文本或二进制。
STOMP 是一个 frame-based 协议,其帧在 HTTP 上建模。以下清单显示了 STOMP 框架的结构:
1 | COMMAND |
Clients 可以使用SEND
或SUBSCRIBE
命令发送或订阅消息,以及描述消息内容和应该接收消息的destination
标头。这启用了一个简单的 publish-subscribe 机制,您可以使用该机制通过 broker 将消息发送到其他连接的客户端,或者向服务器发送消息以请求执行某些工作。
当您使用 Spring 的 STOMP 支持时,Spring WebSocket application 充当客户端的 STOMP broker。消息被路由到@Controller
message-handling 方法或简单的 in-memory broker,它跟踪订阅并向订阅用户广播消息。您还可以将 Spring 配置为使用专用的 STOMP broker(例如 RabbitMQ,ActiveMQ 和其他)来实现消息的实际 broadcasting。在这种情况下,Spring 维护与 broker 的 TCP 连接,将消息中继到它,并将消息从它传递给连接的 WebSocket clients。因此,Spring web applications 可以依赖统一的 HTTP-based 安全性,common 验证和熟悉的编程 model 来进行消息处理。
以下 example 显示了一个 client 订阅接收股票报价,服务器可以定期发出(例如,通过向 broker 发送消息的计划任务):
1 | SUBSCRIBE |
以下 example 显示了一个发送交易请求的客户端,服务器可以通过@MessageMapping
方法处理该请求:
1 | SEND |
在执行之后,服务器可以_b 广播交易确认消息并向 client 详细说明。
目的地的含义在 STOMP 规范中故意保持不透明。它可以是任何 string,完全取决于 STOMP 服务器来定义它们支持的目标的语义和语法。然而,非常常见的是,目标是 path-like strings,其中/topic/..
暗示 publish-subscribe(one-to-many),/queue/
暗示 point-to-point(one-to-one)消息交换。
STOMP 服务器可以使用MESSAGE
命令向所有订户 broadcast 消息。以下 example 显示了服务器向订阅的 client 发送股票报价:
1 | MESSAGE |
服务器无法发送未经请求的消息。来自服务器的所有消息必须响应特定的 client 订阅,并且服务器消息的subscription-id
标头必须 match client 订阅的id
标头。
前面的概述旨在提供对 STOMP 协议的最基本的了解。我们建议全面审查协议规格。
优点
使用 STOMP 作为 sub-protocol 可以让 Spring Framework 和 Spring Security 提供更丰富的编程模型,而不是使用原始 WebSockets。关于 HTTP 与原始 TCP 以及如何让 Spring MVC 和其他 web 框架提供丰富的功能,可以做出同样的观点。以下是一系列好处:
- 无需发明自定义消息传递协议和消息格式。
- STOMP clients,包括 Spring Framework 中的Java client,可用。
- 您可以(可选)使用消息代理(例如 RabbitMQ,ActiveMQ 等)来管理订阅和 broadcast 消息。
- Application 逻辑可以组织在任意数量的
@Controller
实例中,并且可以根据 STOMP 目标标头将消息路由到它们,而不是使用单个WebSocketHandler
处理给定连接的原始 WebSocket 消息。 - 您可以使用 Spring Security 来保护基于 STOMP 目标和消息类型的消息。
启用STOMP
在spring-messaging
和spring-websocket
模块中提供了对 WebSocket 支持的 STOMP。一旦拥有了这些依赖项,就可以通过 WebSocket 使用SockJS 后备公开 STOMP endpoints,如下面的 example 所示:
1 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; |
1 | /portfolio 是 WebSocket(或 SockJS)客户端为 WebSocket 握手需要连接的端点的 HTTP URL。 |
2 | 目标头以/app 开头的 STOMP 消息将路由到@Controller classes 中的@MessageMapping 方法。 |
3 | 使用 built-in message broker 进行订阅,并使用 broadcasting 和 route 将目标标题以/topic 或/queue 开头的消息发送到 broker。 |
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
对于 built-in simple broker,
/topic
和/queue
前缀没有任何特殊含义。它们只是区分 pub-sub 与 point-to-point 消息传递的惯例(即许多订阅者与一个消费者)。当您使用外部 broker 时,请检查 broker 的 STOMP 页面以了解它支持的 STOMP 目标和前缀类型。
要从浏览器连接,对于 SockJS,您可以使用sockjs-client。对于 STOMP,许多 applications 使用了jmesnil/stomp-websocket library(也称为 stomp.js),这是 feature-complete 并且已经在 production 中使用了多年但不再维护。目前,JSteunou/webstomp-client是该图书馆中最积极维护和不断发展的继承者。以下 example code 基于它:
1 | var socket = new SockJS("/spring-websocket-portfolio/portfolio"); |
或者,如果通过 WebSocket 连接(没有 SockJS),则可以使用以下 code:
1 | var socket = new WebSocket("/spring-websocket-portfolio/portfolio"); |
请注意,前面的 example 中的stompClient
不需要指定login
和passcode
headers。即使它确实如此,它们也会在服务器端被忽略(或者更确切地说,被覆盖)。有关身份验证的详细信息,请参阅连接到 Broker和认证。
有关更多 example code,请参阅:
- 使用 WebSocket build 交互式 web application - 入门指南。
- 股票投资组合 - 一个 sample application。
WebSocket服务器
要配置基础 WebSocket 服务器,服务器 Configuration中的信息适用。对于 Jetty,您需要通过StompEndpointRegistry
设置HandshakeHandler
和WebSocketPolicy
:
1 |
|
消息流
一旦暴露了 STOMP 端点,Spring application 就成为连接的 clients 的 STOMP broker。本节介绍服务器端的消息流。
spring-messaging
模块包含对源自Spring Integration的消息传递应用程序的基础支持,后来被提取并合并到 Spring Framework 中,以便在许多Spring 项目和 application 场景中得到更广泛的使用。以下列表简要介绍了一些可用的消息传递抽象:
- 信息:消息的简单表示,包括 headers 和 payload。
- MessageHandler:Contract 用于处理消息。
- MessageChannel:Contract 用于发送允许生产者和消费者之间松散耦合的消息。
- SubscribableChannel:
MessageChannel
与MessageHandler
订阅者。 - ExecutorSubscribableChannel:
SubscribableChannel
使用Executor
传递消息。
Java configuration(即@EnableWebSocketMessageBroker
)和 XML 名称空间 configuration(即<websocket:message-broker>
)都使用前面的组件来组合消息工作流。下图显示了启用简单 built-in 消息 broker 时使用的组件:
上图显示了三个消息 channels:
clientInboundChannel
:用于传递从 WebSocket clients 收到的消息。clientOutboundChannel
:用于将服务器消息发送到 WebSocket clients。brokerChannel
:用于从 server-side application code 中发送消息 broker。
下图显示了配置外部 broker(例如 RabbitMQ)以管理订阅和 broadcasting 消息时使用的组件:
前两个图的主要区别在于使用“broker relay”将消息通过 TCP 传递到外部 STOMP broker,并将消息从 broker 传递到订阅的 clients。
当从 WebSocket 连接接收消息时,它们被解码为 STOMP 帧,变成 Spring Message
表示,并发送到clientInboundChannel
以进行进一步处理。对于 example,目标 headers 以/app
开头的 STOMP 消息可以路由到带注释的控制器中的@MessageMapping
方法,而/topic
和/queue
消息可以直接路由到消息 broker。
处理来自 client 的 STOMP 消息的带注释的@Controller
可以通过brokerChannel
向消息 broker 发送消息,broker 通过clientOutboundChannel
将消息广播给匹配的订阅者。同一个控制器也可以响应 HTTP 请求,因此 client 可以执行 HTTP POST,然后@PostMapping
方法可以向消息 broker 发送消息以 broadcast 发送给订阅的 clients。
我们可以通过一个简单的 example 跟踪流程。考虑以下 example,它设置服务器:
1 |
|
前面的 example 支持以下流程:
- client 连接到
http://localhost:8080/portfolio
,并且一旦建立 WebSocket 连接,STOMP 帧就开始在其上流动。 - client 发送一个 SUBSCRIBE 帧,其目标头为
/topic/greeting
。收到并解码后,消息将发送到clientInboundChannel
,然后路由到消息 broker,log 存储 client 订阅。 - client 将_SEND 帧发送到
/app/greeting
。/app
前缀有助于将其路由到带注释的控制器。删除/app
前缀后,目标的剩余/greeting
部分将映射到GreetingController
中的@MessageMapping
方法。 - 从
GreetingController
返回的 value 变为 SpringMessage
,其有效负载基于 return value 和/topic/greeting
的默认目标头(从输入目标派生,/app
替换为/topic
)。生成的消息将发送到brokerChannel
并由消息 broker 处理。 - 消息 broker 找到所有匹配的订阅者,并通过
clientOutboundChannel
向每个订阅者发送一个 MESSAGE 帧,消息被编码为 STOMP 帧并在 WebSocket 连接上发送。
下一节提供了有关注释方法的更多详细信息,包括支持的 arguments 和 return 值的类型。
带注解的控制器
Applications 可以使用带注释的@Controller
classes 来处理来自 clients 的消息。这样的 classes 可以声明@MessageMapping
,@SubscribeMapping
和@ExceptionHandler
方法,如以下主题中所述:
MessageMapping注解
您可以使用@MessageMapping
来注释根据目的地 route 消息的方法。方法 level 和 level 类型都支持它。在 level 类型中,@MessageMapping
用于表示控制器中所有方法的共享映射。
默认情况下,映射值为 Ant-style 路径模式(对于 example /thing*
,/thing/**
),包括对模板变量的支持(对于 example,/thing/{id}
)。可以通过@DestinationVariable
method arguments 引用这些值。 Applications 还可以切换到映射的 dot-separated 目标约定,如点作为分隔符中所述。
支持的方法Arguments
以下 table 描述了方法 arguments:
方法论证 | 描述 |
---|---|
Message |
用于访问完整的消息。 |
MessageHeaders |
用于访问Message 中的 headers。 |
MessageHeaderAccessor ,SimpMessageHeaderAccessor 和StompHeaderAccessor |
用于通过类型化访问器方法访问 headers。 |
@Payload |
用于访问消息的有效负载,由已配置的MessageConverter 转换(对于 example,来自 JSON)。 不需要此 annotation 的存在,因为默认情况下,假设没有其他参数匹配。 您可以使用@javax.validation.Valid 或 Spring 的@Validated 注释有效负载 arguments,以自动验证有效负载 arguments。 |
@Header |
用于访问特定标头 value - 以及使用org.springframework.core.convert.converter.Converter 进行类型转换(如有必要)。 |
@Headers |
用于访问消息中的所有 headers。此参数必须可分配给java.util.Map 。 |
@DestinationVariable |
用于访问从消息目标中提取的模板变量。根据需要将值转换为声明的方法参数类型。 |
java.security.Principal |
反映在 WebSocket HTTP 握手的 time 登录的用户。 |
Return值
默认情况下,@MessageMapping
方法的 return value 通过匹配MessageConverter
序列化为有效负载,并作为Message
发送到brokerChannel
,从_b 广播到订阅者。出站消息的目的地与入站消息的目的地相同,但前缀为/topic
。
您可以使用@SendTo
和@SendToUser
注释来自定义输出消息的目标。 @SendTo
用于自定义目标目标或指定多个目标。 @SendToUser
用于将输出消息仅定向到与输入消息关联的用户。见用户目的地。
您可以在同一个方法上同时使用@SendTo
和@SendToUser
,并且 class level 都支持它们,在这种情况下它们作为 class 中方法的默认值。但是,请记住,任何 method-level @SendTo
或@SendToUser
注释都会覆盖 class level 上的任何此类注释。
消息可以异步处理,@MessageMapping
方法可以_ret ,CompletableFuture
或CompletionStage
。
请注意,@SendTo
和@SendToUser
仅仅是一种便利,相当于使用SimpMessagingTemplate
发送消息。如有必要,对于更高级的方案,@MessageMapping
方法可以直接使用SimpMessagingTemplate
。这可以代替返回 value,或者可能另外返回 value。见发送消息。
SubscribeMapping注解
@SubscribeMapping
类似于@MessageMapping
,但仅将映射缩小为订阅消息。它支持与@MessageMapping
相同的方法 arguments。但是对于 return value,默认情况下,消息会直接发送到 client(通过clientOutboundChannel
,以响应订阅)而不是 broker(通过brokerChannel
,作为 broadcast 到匹配的订阅)。添加@SendTo
或@SendToUser
会覆盖此行为并发送到 broker。
什么时候有用?假设 broker 映射到/topic
和/queue
,而 application 控制器映射到/app
。在此设置中,broker store 对/topic
和/queue
的所有订阅都用于重复广播,并且不需要涉及 application。 client 也可以订阅某个/app
目的地,并且控制器可以 return 一个 value 以响应该订阅而不涉及 broker 而不再存储或使用订阅(实际上是 one-time request-reply 交换)。一个用例是在启动时使用初始数据填充 UI。
这什么时候没用?不要尝试将 broker 和控制器 map 映射到相同的目标前缀,除非您由于某种原因希望两者都独立处理消息,包括订阅。入站消息在 parallel 中处理。无法保证 broker 或控制器是否首先处理给定的消息。如果在存储订阅并准备好广播时通知目标,则 client 应该在服务器支持时询问收据(简单的 broker 不支持)。对于 example,使用 Java STOMP client,您可以执行以下操作来添加收据:
1 |
|
服务器端选项是brokerChannel
上的注册和ExecutorChannelInterceptor
,并实现在处理完消息(包括订阅)后调用的afterMessageHandled
方法。
MessageExceptionHandler注解
application 可以使用@MessageExceptionHandler
方法来处理@MessageMapping
方法的 exceptions。如果要访问 exception 实例,可以在 annotation 本身或通过方法参数声明 exceptions。以下 example 通过方法参数声明 exception:
1 |
|
@MessageExceptionHandler
方法支持灵活的方法签名,并支持与@MessageMapping方法相同的方法参数类型和 return 值。
通常,@MessageExceptionHandler
方法适用于声明它们的@Controller
class(或 class 层次结构)。如果您希望此类方法更全局地应用(跨控制器),则可以在标有@ControllerAdvice
的 class 中声明它们。这与 Spring MVC 中可用的类似的支持相当。
发送消息
如果要从 application 的任何部分向连接的 clients 发送消息,该怎么办?任何 application component 都可以向brokerChannel
发送消息。最简单的方法是 inject SimpMessagingTemplate
并使用它来发送消息。通常,您可以按类型对其进行注入,如下面的 example 所示:
1 |
|
但是,如果存在另一个相同类型的 bean,您也可以通过 name(brokerMessagingTemplate
)限定它。
简单的Broker
built-in 简单消息 broker 处理来自 clients 的订阅请求,将它们存储在 memory 中,并将消息广播到具有匹配目标的已连接客户端。 broker 支持 path-like 目的地,包括对 Ant-style 目的地模式的订阅。
Applications 也可以使用 dot-separated(而不是 slash-separated)目的地。见点作为分隔符。
如果配置了任务调度程序,则简单的 broker 支持STOMP 心跳。为此,您可以声明自己的调度程序或使用在内部自动声明和使用的调度程序。以下 example 显示了如何声明自己的调度程序:
1 |
|
外部Broker
简单的 broker 非常适合入门,但仅支持 STOMP 命令的子集(它不支持 ack,收据和其他一些 features),依赖于简单的 message-sending 循环,不适合群集。作为替代方案,您可以升级 applications 以使用 full-featured 消息 broker。
请参阅 STOMP 文档以获取您选择的消息 broker(例如RabbitMQ,ActiveMQ和其他),安装 broker,并在启用 STOMP 支持的情况下运行它。然后,您可以在 Spring configuration 中启用 STOMP broker 中继(而不是简单的 broker)。
以下 example configuration 启用 full-featured broker:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
前面的 configuration 中的 STOMP broker 中继是一个 Spring MessageHandler,它通过将消息转发到外部消息 broker 来处理消息。为此,它建立与 broker 的 TCP 连接,将所有消息转发给它,然后通过其 WebSocket 会话将从 broker 接收的所有消息转发给 clients。从本质上讲,它充当“转发”,可以在两个方向上转发消息。
将
io.projectreactor.netty:reactor-netty
和io.netty:netty-all
依赖项添加到项目以进行 TCP 连接管理。
此外,application 组件(例如 HTTP 请求处理方法,业务服务等)也可以向 broker 中继发送消息,如发送消息中所述,将 broadcast 消息发送到订阅的 WebSocket clients。
实际上,broker 中继实现了健壮且可扩展的消息广播。
连接到Broker
STOMP broker 中继维护与 broker 的单个“系统”TCP 连接。此连接仅用于源自 server-side application 的消息,而不用于接收消息。您可以为此连接配置 STOMP 凭据(即 STOMP 帧login
和passcode
headers)。这在 XML 命名空间和 Java configuration 中都显示为systemLogin
和systemPasscode
properties,默认值为guest
和guest
。
STOMP broker 中继还为每个连接的 WebSocket client 创建单独的 TCP 连接。您可以配置用于代表 clients 创建的所有 TCP 连接的 STOMP 凭据。这在 XML 命名空间和 Java configuration 中都公开为clientLogin and
clientPasscode properties with default values of
guest and
guest`。
STOMP broker 中继始终在每个
CONNECT
帧上设置login
和passcode
headers,它们代表 clients 转发给 broker。因此,WebSocket clients 不需要设置那些 headers。他们被忽略了。正如认证部分所解释的那样,WebSocket clients 应该依赖 HTTP 身份验证来保护 WebSocket 端点并建立 client 身份。
STOMP broker 中继还通过“系统”TCP 连接向消息 broker 发送和接收心跳。您可以配置发送和接收心跳的间隔(默认情况下每个 10 秒)。如果与 broker 的连接丢失,broker 中继将继续尝试每 5 秒重新连接一次,直到成功为止。
任何 Spring bean 都可以实现ApplicationListener<BrokerAvailabilityEvent>
以在与 broker 的“系统”连接丢失和 re-established 时接收通知。例如,广播股票报价的股票报价服务可以在没有 active“系统”连接时停止尝试发送消息。
默认情况下,STOMP broker 中继始终连接,并在连接丢失时根据需要重新连接到同一个 host 和 port。如果您希望提供多个地址,则在每次尝试连接时,您都可以配置地址供应商,而不是固定的 host 和 port。以下 example 显示了如何执行此操作:
1 |
|
您还可以使用virtualHost
property 配置 STOMP broker 中继。此 property 的 value 被设置为每个CONNECT
帧的host
标头,并且可能很有用(例如,在云环境中,建立 TCP 连接的实际 host 与提供 cloud-basedSTOMP 服务的 host 不同)。
点作为分隔符
当消息路由到@MessageMapping
方法时,它们与AntPathMatcher
匹配。默认情况下,模式应使用斜杠(/
)作为分隔符。这是 web applications 中的一个很好的约定,类似于 HTTP URL。但是,如果您更习惯于消息传递约定,则可以切换到使用点(.
)作为分隔符。
以下 example 显示了如何在 Java configuration 中执行此操作:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
之后,控制器可以使用点(.
)作为@MessageMapping
方法中的分隔符,如下面的 example 所示:
1 |
|
client 现在可以向/app/red.blue.green123
发送消息。
在前面的示例中,我们没有更改“broker relay”上的前缀,因为这些前缀完全依赖于外部消息 broker。请参阅您使用的 broker 的 STOMP 文档页面,以查看它为目标标头支持的约定。
另一方面,“simple broker”依赖于配置的PathMatcher
,因此,如果切换分隔符,则该更改也适用于 broker 以及 broker 将消息中的目标与预订中的模式匹配的方式。
认证
WebSocket 消息传递 session 上的每个 STOMP 都以 HTTP 请求开头。这可以是升级到 WebSockets 的请求(即 WebSocket 握手),或者在 SockJS 回退的情况下,是一系列 SockJS HTTP 传输请求。
许多 web applications 已经具有用于保护 HTTP 请求的身份验证和授权。通常,通过使用某种机制(如登录页面,HTTP 基本身份验证或其他方式)通过 Spring Security 对用户进行身份验证。经过身份验证的用户的 security context 保存在 HTTP session 中,并与同一 cookie-based session 中的后续请求相关联。
因此,对于 WebSocket 握手或 SockJS HTTP 传输请求,通常已经通过HttpServletRequest#getUserPrincipal()
可访问经过身份验证的用户。 Spring 会自动将该用户与为其创建的 WebSocket 或 SockJS session 相关联,然后通过用户标头与通过该 session 传输的所有 STOMP 消息相关联。
简而言之,一个典型的 web application 除了它已经为安全做的事情之外什么都不做。用户在 HTTP 请求 level 上通过安全 context 进行身份验证,该安全 context 通过 cookie-based HTTP session 进行维护(然后与为该用户创建的 WebSocket 或 SockJS 会话相关联),并导致每个Message
上标记的用户标头流经应用。
请注意,STOMP 协议在CONNECT
帧上确实有login
和passcode
headers。这些最初设计用于并且仍然需要,例如,用于 TCP 上的 STOMP。但是,对于 STOMP over WebSocket,默认情况下,Spring 忽略 STOMP 协议 level 上的授权 headers,假定用户已经在 HTTP 传输 level 上进行了身份验证,并期望 WebSocket 或 SockJS session 包含经过身份验证的用户。
Spring Security 提供了WebSocket sub-protocol 授权,它使用
ChannelInterceptor
根据其中的用户头来授权消息。此外,Spring Session 提供了一个WebSocket integration,确保当 WebSocket session 仍处于 active 时,用户 HTTP session 不会过期。
令牌认证
Spring Security OAuth提供对基于令牌的安全性的支持,包括 JSON Web Token(JWT)。您可以将此作为 Web applications 中的身份验证机制,包括 STOMP over WebSocket 交互,如上一节所述(即通过 cookie-based session 维护身份)。
在同一时间,cookie-based 会话并不总是最合适的(例如,在 applications 中不保持 server-side session 或在移动应用程序中,common 使用 headers 进行身份验证)。
WebSocket 协议,RFC 6455“没有规定服务器在 WebSocket 握手期间可以对 clients 进行身份验证的任何特定方式。”但实际上,浏览器客户端只能使用标准身份验证 headers(即基本 HTTP 身份验证)或 cookies,而不能(用于 example)提供自定义 headers。同样,SockJS JavaScript client 不提供使用 SockJS 传输请求发送 HTTP headers 的方法。见sockjs-client issue 196。相反,它允许发送可用于发送令牌的查询参数,但这有其自身的缺点(例如,令牌可能无意中使用服务器日志中的 URL 记录)。
上述限制适用于 browser-based clients,不适用于 Spring Java-based STOMP client,它支持使用 WebSocket 和 SockJS 请求发送 headers。
因此,希望避免使用 cookies 的应用程序可能没有任何好的替代方法来进行 HTTP 协议 level 的身份验证。他们可能更喜欢在 STOMP 消息传递协议中使用 headers 进行身份验证,而不是使用 cookies。这样做需要两个简单的步骤:
- 使用 STOMP client 在 connect time 传递 authentication headers。
- 使用
ChannelInterceptor
处理身份验证 headers。
下一个 example 使用 server-side configuration 来注册自定义身份验证拦截器。请注意,拦截器只需要在 CONNECT Message
上进行身份验证并设置用户头。 Spring 记录并保存经过身份验证的用户,并将其与同一 session 上的后续 STOMP 消息相关联。以下 example 显示了如何注册自定义身份验证拦截器:
1 |
|
另请注意,当您使用 Spring Security 的邮件授权时,您需要确保在 Spring Security 之前排序身份验证ChannelInterceptor
config。最好通过在WebSocketMessageBrokerConfigurer
标记为@Order(Ordered.HIGHEST_PRECEDENCE + 99)
的 implementation implementation 中声明自定义拦截器来完成。
用户目的地
application 可以发送针对特定用户的消息,并且 Spring 的 STOMP 支持可以识别以/user/
为前缀的目标。对于 example,client 可能订阅/user/queue/position-updates
目标。此目标由UserDestinationMessageHandler
处理并转换为用户 session 唯一的目标(例如/queue/position-updates-user123
)。这提供了订阅一般命名目的地的便利,同时在同一时间确保不与订阅相同目的地的其他用户发生冲突,以便每个用户可以接收唯一的库存位置更新。
在发送方,消息可以被发送到诸如/user/{username}/queue/position-updates
的目的地,而/user/{username}/queue/position-updates
又由UserDestinationMessageHandler
转换成一个或多个目的地,一个用于与用户相关联的每个 session。这使得 application 中的任何 component 都可以发送针对特定用户的消息,而不必了解其 name 和通用目标。通过 annotation 和消息传递模板也支持此功能。
message-handling 方法可以向通过@SendToUser
annotation 处理的消息关联的用户发送消息(class-level 也支持共享 common 目的地),如下面的示例所示:
1 |
|
如果用户具有多个 session,则默认情况下,订阅给定目标的所有会话都是目标。但是,有时可能需要仅定位发送正在处理的消息的 session。您可以通过将broadcast
属性设置为 false 来实现,如下面的 example 所示:
1 |
|
虽然用户目的地通常意味着经过身份验证的用户,但并不是严格要求的。与经过身份验证的用户无关的 WebSocket session 可以订阅用户目标。在这种情况下,
@SendToUser
annotation 的行为与broadcast=false
完全相同(即,仅定位发送正在处理的消息的 session)。
您可以从任何 application component 向用户目标发送消息,例如,注入由 Java configuration 或 XML 命名空间创建的SimpMessagingTemplate
。 (如果需要@Qualifier
.),则 bean name 为"brokerMessagingTemplate"
以下 example 显示了如何执行此操作:
1 |
|
当您将用户目标与外部消息 broker 一起使用时,应检查 broker 文档,了解如何管理非活动队列,以便在用户 session 结束时删除所有唯一用户队列。对于 example,当您使用
/exchange/amq.direct/position-updates
等目标时,RabbitMQ 会创建 auto-delete 队列。因此,在这种情况下,client 可以订阅/user/exchange/amq.direct/position-updates
。同样,ActiveMQ 有configuration 选项用于清除非活动目标。
在 multi-application 服务器方案中,用户目标可能仍未解析,因为用户已连接到其他服务器。在这种情况下,您可以将目标配置为 broadcast 未解析的消息,以便其他服务器有机会尝试。这可以通过 Java configuration 中MessageBrokerRegistry
的userDestinationBroadcast
property 和 XML 中message-broker
元素的user-destination-broadcast
属性来完成。
消息顺序
来自 broker 的消息将发布到clientOutboundChannel
,从而将它们写入 WebSocket 会话。由于 channel 由ThreadPoolExecutor
支持,因此消息在不同的线程中处理,client 接收的结果序列可能不匹配发布的确切 order。
如果这是一个问题,请启用setPreservePublishOrder
flag,如下面的 example 所示:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
设置 flag 时,同一 client session 中的消息将在 time 发布到clientOutboundChannel
,以便保证发布的 order。请注意,这会产生很小的性能开销,因此只有在需要时才应启用它。
Events
发布了几个ApplicationContext
events,可以通过实现 Spring 的ApplicationListener
接口来接收:
BrokerAvailabilityEvent
:表示 broker 何时可用或不可用。虽然“简单”broker 在启动时立即可用,并且在 application 运行时仍然如此,但 STOMP“broker relay”可能会失去与全功能 broker 的连接(对于 example,如果 broker 重新启动)。 broker 中继重新连接逻辑,re-establishes 与 broker 的“系统”连接返回时。因此,只要 state 从连接变为 disconnected 和 vice-versa,就会发布此 event。使用SimpMessagingTemplate
的组件应订阅此 event,并避免在 broker 不可用时发送消息。无论如何,他们应该准备好在发送消息时处理MessageDeliveryException
。SessionConnectEvent
:收到新的 STOMP CONNECT 时发布,表示新 client session 的开始。 event 包含表示连接的消息,包括 session ID,用户信息(如果有)以及 client 发送的任何自定义 headers。这对于跟踪 client 会话非常有用。订阅此 event 的组件可以使用SimpMessageHeaderAccessor
或StompMessageHeaderAccessor
包装所包含的消息。SessionConnectedEvent
:当 broker 发送 STOMP CONNECTED 帧以响应 CONNECT 时后不久发布。此时,可以认为 STOMP session 已完全建立。SessionSubscribeEvent
:收到新的 STOMP SUBSCRIBE 时发布。SessionUnsubscribeEvent
:收到新的 STOMP UNSUBSCRIBE 时发布。SessionDisconnectEvent
:在 STOMP session ends 时发布。 DISCONNECT 可能已从 client 发送,或者可能在 WebSocket session 关闭时自动生成。在某些情况下,每个 session 都会多次发布此 event。对于多个 disconnect events,组件应该是幂等的。
当您使用 full-featured broker 时,如果 broker 暂时不可用,则 STOMP“broker relay”会自动重新连接“系统”连接。但是,Client 连接不会自动重新连接。假设启用了心跳,client 通常会注意到 broker 在 10 秒内没有响应。 Clients 需要实现自己的重新连接逻辑。
Interceptor
Events提供 STOMP 连接生命周期的通知,但不提供每个 client 消息的通知。 Applications 还可以注册ChannelInterceptor
来拦截任何消息以及处理链的任何部分。以下 example 显示了如何拦截来自 clients 的入站邮件:
1 |
|
自定义ChannelInterceptor
可以使用StompHeaderAccessor
或SimpMessageHeaderAccessor
来访问有关邮件的信息,如下面的示例所示:
1 | public class MyChannelInterceptor implements ChannelInterceptor { |
Applications 也可以实现ExecutorChannelInterceptor
,这是一个 sub-interface ChannelInterceptor
,在处理消息的线程中有回调。虽然为发送到 channel 的每条消息调用ChannelInterceptor
一次,但ExecutorChannelInterceptor
在每个MessageHandler
的线程中提供了挂钩,这些挂钩订阅了来自 channel 的消息。
请注意,与前面描述的SesionDisconnectEvent
一样,DISCONNECT 消息可以来自 client,也可以在 WebSocket session 关闭时自动生成。在某些情况下,拦截器可能会为每个 session 多次拦截此消息。对于多个 disconnect events,组件应该是幂等的。
STOMP-Client
Spring 通过 WebSocket client 提供 STOMP,通过 TCP client 提供 STOMP。
首先,您可以创建和配置WebSocketStompClient
,如下面的 example 所示:
1 | WebSocketClient webSocketClient = new StandardWebSocketClient(); |
在前面的 example 中,您可以将StandardWebSocketClient
替换为SockJsClient
,因为它也是WebSocketClient
的 implementation。 SockJsClient
可以使用 WebSocket 或 HTTP-based transport 作为后备。有关更多详细信息,请参阅SockJsClient。
接下来,您可以建立连接并为 STOMP session 提供处理程序,如下面的示例所示:
1 | String url = "ws://127.0.0.1:8080/endpoint"; |
当 session 准备好使用时,将通知处理程序,如下面的 example 所示:
1 | public class MyStompSessionHandler extends StompSessionHandlerAdapter { |
建立 session 后,可以发送任何有效负载并使用配置的MessageConverter
进行序列化,如下面的 example 所示:
1 | session.send("/topic/something", "payload"); |
您也可以订阅目的地。 subscribe
方法需要处理订阅消息的处理程序,并返回一个可用于取消订阅的Subscription
句柄。对于每个收到的消息,处理程序可以指定有效负载应反序列化的目标Object
类型,如下面的 example 所示:
1 | session.subscribe("/topic/something", new StompFrameHandler() { |
要启用 STOMP 心跳,可以使用TaskScheduler
配置WebSocketStompClient
,并可选择自定义心跳间隔(写入不活动 10 秒,导致心跳发送,读取不活动 10 秒,关闭连接)。
当您使用
WebSocketStompClient
进行 performance 测试来模拟来自同一台计算机的数千个 clients 时,请考虑关闭心跳,因为每个连接都会安排自己的心跳任务,并且不会针对同一台计算机上的大量客户端进行优化。
STOMP 协议还支持收据,其中 client 必须添加receipt
标头,服务器在处理发送或订阅后用 RECEIPT 帧响应。为了支持这一点,StompSession
提供setAutoReceipt(boolean)
,导致在每个后续发送或订阅 event 上添加receipt
标头。或者,您也可以手动将收据标题添加到StompHeaders
。发送和订阅 return 都会返回Receiptable
的实例,您可以使用它来注册接收成功和失败回调。对于此 feature,您必须在收据到期前(默认为 15 秒)为 client 配置TaskScheduler
和 time 的数量。
注意StompSessionHandler
本身是一个StompFrameHandler
,它允许它处理 ERROR 帧以及handleException
回调来处理消息的 exceptions 和handleTransportError
来处理 transport-level 错误,包括ConnectionLostException
。
WebSocket范围
每个 WebSocket session 都有一个属性 map。 map 作为标题附加到入站 client 消息,可以从控制器方法访问,如下面的示例所示:
1 |
|
您可以在websocket
范围内声明 Spring-managed bean。您可以 inject WebSocket-scoped beans 到控制器和clientInboundChannel
上注册的任何 channel 拦截器。这些通常是单身,比任何单独的 WebSocket session 都更长寿。因此,您需要为 WebSocket-scoped beans 使用范围代理模式,如下面的 example 所示:
1 |
|
与任何自定义作用域一样,Spring 在从控制器访问第一个 time 时初始化一个新的MyBean
实例,并在 WebSocket session 属性中存储实例。随后返回相同的实例,直到 session ends。 WebSocket-scoped beans 调用了所有 Spring 生命周期方法,如前面的示例所示。
性能
在绩效方面没有灵丹妙药。许多因素会影响它,包括消息的大小和数量,application 方法是否执行需要阻塞的工作,以及外部因素(如网络速度和其他问题)。本节的目标是提供可用 configuration 选项的概述以及有关如何推理扩展的一些想法。
在消息传递 application 中,消息通过 channels 传递给由线程池支持的异步执行。配置这样的 application 需要充分了解 channels 和消息流。因此,建议审核消息流。
显而易见的起点是配置支持clientInboundChannel
和clientOutboundChannel
的线程池。默认情况下,两者都配置为可用处理器数量的两倍。
如果注释方法中的消息处理主要是 CPU-bound,则clientInboundChannel
的线程数应保持接近处理器数。如果他们所做的工作更多 IO-bound 并且需要阻塞或等待数据库或其他外部系统,则可能需要增加线程池大小。
ThreadPoolExecutor
有三个重要的 properties:核心线程池大小,最大线程池大小,以及队列为 store 任务没有可用线程的任务的容量。
一个常见的混淆点是,配置核心池大小(对于 example,10)和最大池大小(对于 example,20)会导致一个具有 10 到 20 个线程的线程池。实际上,如果容量保留为 Integer.MAXVALUE 的默认值_,则线程池永远不会超出核心池大小,因为所有其他任务都排队。
请参阅ThreadPoolExecutor
的 javadoc 以了解这些 properties 如何工作并理解各种排队策略。
在clientOutboundChannel
方面,它是关于向 WebSocket clients 发送消息的全部内容。如果 clients 位于快速网络上,则线程数应保持接近可用处理器的数量。如果它们很慢或带宽较低,则消耗消息所需的时间会更长,并给线程池带来负担。因此,增加线程池大小变得必要。
虽然clientInboundChannel
的工作负载可以预测 - 毕竟,它基于 application 的作用 - 如何配置“clientOutboundChannel”更难,因为它基于 application 无法控制的因素。因此,另外两个 properties 与发送消息有关:sendTimeLimit
和sendBufferSizeLimit
。您可以使用这些方法配置允许发送 long 的时间以及向 client 发送消息时可以缓冲的数据量。
一般 idea 是,在任何给定的 time,只有一个线程可用于发送到 client。同时,所有其他消息都会被缓冲,您可以使用这些 properties 来决定如何允许发送消息的长度以及在此期间可以缓冲多少数据。有关重要的其他详细信息,请参阅 XML schema 的 javadoc 和文档。
以下 example 显示了可能的 configuration:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
您还可以使用前面显示的 WebSocket 传输 configuration 配置传入 STOMP 消息的最大允许大小。理论上,WebSocket 消息的大小几乎是无限的。在实践中,WebSocket 服务器强加限制 - 例如,Tomcat 上的 8K 和 Jetty 上的 64K。出于这个原因,STOMP clients(例如 JavaScript webstomp-client和其他)在 16K 边界处拆分较大的 STOMP 消息,并将它们作为多个 WebSocket 消息发送,这需要服务器缓冲和 re-assemble。
Spring 的 STOMP-over-WebSocket 支持执行此操作,因此 applications 可以配置 STOMP 消息的最大大小,而不管 WebSocket server-specific 消息大小如何。请记住,如有必要,WebSocket 消息大小会自动调整,以确保它们至少可以携带 16K WebSocket 消息。
以下 example 显示了一个可能的 configuration:
1 |
|
以下 example 显示了前面的 example 的 XML configuration 等效项:
1 | <beans xmlns="http://www.springframework.org/schema/beans" |
关于扩展的一个重要问题涉及使用多个 application 实例。目前,你不能用简单的 broker 做到这一点。但是,当您使用 full-featured broker(例如 RabbitMQ)时,每个 application 实例都连接到 broker,而来自一个 application 实例的消息 broadcast 可以通过 broker broadcast 到通过任何其他 application 实例连接的 WebSocket clients。
监控
当您使用@EnableWebSocketMessageBroker
或<websocket:message-broker>
时, key 基础结构组件会自动收集统计信息和计数器,以便对 application 的内部 state 提供重要的信息。 configuration 还声明一个类型的 bean,它在一个地方收集所有可用信息,默认情况下,每 30 分钟收集一次INFO
level。这个 bean 可以通过 Spring 的MBeanExporter
导出到 JMX,以便在运行时查看(对于 example,通过 JDK 的jconsole
)。以下列表总结了可用信息:
- Client WebSocket 会话
- 当前
- 指示当前有多少个 client 会话,其中计数进一步按 WebSocket 与 HTTP 流式传输和轮询 SockJS 会话进行细分。
- 总
- 表示已建立的会话总数。
- 异常关闭
- 连接失败
- 已建立但在 60 秒内未收到任何消息后关闭的会话。这通常表示代理或网络问题。
- 超出发送限制
- 超过配置的发送超时或发送缓冲区限制后,会话关闭,这可能发生在慢客户端(请参阅上一节)。
- 运输错误
- 传输错误后会话关闭,例如无法读取或写入 WebSocket 连接或 HTTP 请求或响应。
- STOMP 框架
- 处理的 CONNECT,CONNECTED 和 DISCONNECT 帧的总数,表示 STOMP level 上连接了多少个 clients。请注意,当会话异常关闭或 clients 关闭而不发送 DISCONNECT 帧时,DISCONNECT 计数可能会更低。
- STOMP Broker Relay
- TCP 连接
- 表示为 broker 建立了代表 client WebSocket 会话的 TCP 连接数。这应该等于 client WebSocket 会话的数量 1 个额外的共享“系统”连接,用于从 application 中发送消息。
- STOMP 框架
- 代表 clients 转发到 broker 或从 broker 接收的 CONNECT,CONNECTED 和 DISCONNECT 帧的总数。请注意,无论 client WebSocket session 如何关闭,都会向 broker 发送 DISCONNECT 帧。因此,较低的 DISCONNECT 帧计数表示 broker 是 pro-actively 关闭连接(可能是因为心跳未在 time 到达,无效的输入帧或其他问题)。
- Client Inbound Channel
- 来自支持
clientInboundChannel
的线程池的统计信息,用于深入了解传入消息处理的运行状况。在此排队的任务表明 application 可能太慢而无法处理消息。如果有 I/O 绑定任务(对于 example,慢速数据库查询,对第三方 REST API 的 HTTP 请求等),请考虑增加线程池大小。
- 来自支持
- Client Outbound Channel
- 来自支持
clientOutboundChannel
的线程池的统计信息,可以深入了解 broadcasting 消息到 clients 的运行状况。在这里排队的任务表明客户端太慢而无法使用消息。解决此问题的一种方法是增加线程池大小以适应预期的并发慢客户端数。另一种选择是减少发送超时和发送缓冲区大小限制(参见上一节)。
- 来自支持
- SockJS 任务计划程序
- 来自用于发送心跳的 SockJS 任务调度程序的线程池的统计信息。请注意,在 STOMP level 上协商心跳时,将禁用 SockJS 心跳。
测试
当您使用 Spring 的 STOMP-over-WebSocket 支持时,有两种主要的方法来测试 applications。第一种是编写 server-side 测试来验证控制器及其带注释的 message-handling 方法的功能。第二个是编写完整的 end-to-end 测试,涉及运行 client 和服务器。
这两种方法并不相互排斥。相反,每个人都在整体测试策略中占有一席之地。 Server-side 测试更集中,更容易编写和维护。另一方面,End-to-end integration 测试更完整,测试更多,但它们也更多地参与编写和维护。
最简单的 server-side 测试形式是编写控制器单元测试。但是,这还不够用,因为控制器的大部分功能取决于它的注释。纯单元测试根本无法测试。
理想情况下,测试中的控制器应该在运行时调用,就像测试使用 Spring MVC Test framework 处理 HTTP 请求的控制器的方法一样 - 也就是说,没有 running Servlet 容器但是依赖于 Spring Framework 来调用带注释的控制器。与 Spring MVC Test 一样,这里有两个可能的替代方案,使用“context-based”或使用“独立”设置:
- 在 Spring TestContext framework,inject
clientInboundChannel
作为测试字段的帮助下加载实际的 Spring configuration,并使用它来发送要由控制器方法处理的消息。 - 手动设置调用控制器(即
SimpAnnotationMethodMessageHandler
)所需的最小 Spring framework 基础结构,并将控制器的消息直接传递给它。
这两种设置方案都在测试股票投资组合 sample application 中进行了演示。
第二种方法是创建 end-to-end integration 测试。为此,您需要以嵌入模式运行 WebSocket 服务器并将其作为 WebSocket client 连接到它,以发送包含 STOMP 帧的 WebSocket 消息。 测试股票投资组合 sample application 还通过使用 Tomcat 作为嵌入式 WebSocket 服务器和用于测试目的的简单 STOMP client 来演示此方法。
其他Web框架
本章详细介绍了 Spring 与第三方 web 框架的整合。
Spring Framework 的核心 value 命题之一是支持选择。从一般意义上讲,Spring 并不强迫您使用或购买任何特定的 architecture,技术或方法(尽管它肯定会推荐一些其他的)。这种自由选择与开发人员及其开发团队最相关的 architecture,技术或方法可以说是最明显的 web 区域,其中 Spring 提供了自己的 web framework(Spring MVC和Spring WebFlux),而在同一时间,提供与一些流行的第三方 web 框架的整合。
Common配置
在深入研究每个支持的 web framework 的 integration 细节之前,让我们先看一下 Spring configuration,它不是任何一个 web framework 特有的。 (本节同样适用于 Spring 自己的 web framework,Spring MVC.)
Spring 的轻量级 application model 支持的一个概念(想要一个更好的词)是一个分层的 architecture。请记住,在“经典”分层 architecture 中,web 图层只是众多图层中的一个。它充当 server-side application 的入口点之一,并且它委托服务层中定义的服务 objects(Facade)以满足 business-specific(和 presentation-technology 不可知)用例。在 Spring 中,这些服务 objects,任何其他 business-specific objects,data-accessobjects 和其他服务存在于不同的“business context”中,其中不包含 web 或表示层 objects(表示 objects,例如 Spring MVC 控制器,通常以不同的方式配置) “presentation context”)。本节详细介绍了如何配置包含 application 中所有’business beans’的 Spring 容器(WebApplicationContext
)。
转到具体细节,您需要做的就是在 web application 的标准 Java EE servlet web.xml
文件中声明一个ContextLoaderListener并添加一个contextConfigLocation
考虑以下<listener/>
configuration:
1 | <listener> |
进一步考虑以下<context-param/>
configuration:
1 | <context-param> |
如果未指定contextConfigLocation
context 参数,则ContextLoaderListener
将查找要加载的名为/WEB-INF/applicationContext.xml
的文件。加载 context files 后,Spring 会根据 bean 定义创建WebApplicationContext object,并将 store 存储在 web application 的ServletContext
中。
所有 Java web 框架都构建在 Servlet API 之上,因此您可以使用以下 code 代码段来访问ContextLoaderListener
创建的“business context”ApplicationContext
。
以下 example 显示了如何获取WebApplicationContext
:
1 | WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext); |
WebApplicationContextUtils class 是为了方便起见,因此您无需记住ServletContext
属性的 name。如果WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
key 下不存在 object,则getWebApplicationContext()
方法返回null
。而不是冒险在 application 中获取NullPointerExceptions
,最好使用getRequiredWebApplicationContext()
方法。当ApplicationContext
缺失时,此方法抛出 exception。
一旦你的 reference,你可以通过 name 或类型检索 beans。大多数开发人员通过 name 检索 beans,然后将它们转换为其实现的接口之一。
幸运的是,本节中的大多数框架都有更简单的查找 beans 的方法。它们不仅可以轻松地从 Spring 容器中获取 beans,而且还允许您在其控制器上使用依赖注入。每个 web framework 部分都有关于其特定 integration 策略的更多细节。
JSF
JavaServer Faces(JSF)是 JCP 的标准 component-based,event-driven web 用户界面 framework。从 Java EE 5 开始,它是 Java EE 保护伞的官方部分。
对于流行的 JSF 运行时以及流行的 JSF component libraries,请查看Apache MyFaces 项目。 MyFaces 项目还提供了 common JSF extensions,例如MyFaces Orchestra(一个提供丰富的会话范围支持的 Spring-based JSF 扩展)。
Spring Web Flow 2.0 通过其新建立的 Spring Faces 模块提供丰富的 JSF 支持,包括 JSF-centric 用法(如本节所述)和 Spring-centric 用法(使用 Spring MVC 调度程序中的 JSF 视图)。有关详细信息,请参阅Spring Web Flow 网站。
Spring 的 JSF integration 中的 key 元素是 JSF ELResolver
机制。
SpringBeanResolver
SpringBeanFacesELResolver
是符合 JSF 1.2 的ELResolver
implementation,与 JSF 1.2 和 JSP 2.1 使用的标准 Unified EL 集成。作为SpringBeanVariableResolver
,它首先委托 Spring 的“business context”WebApplicationContext
,然后委托给底层 JSF implementation 的默认解析器。
Configuration-wise,您可以在 JSF faces-context.xml
文件中定义SpringBeanFacesELResolver
,如下面的 example 所示:
1 | <faces-config> |
使用FacesContextUtils
在faces-config.xml
中将 properties 映射到 beans 时,自定义VariableResolver
可以正常工作,但有时,您可能需要显式 grab bean。 这时,FacesContextUtils class 使这很容易。它类似于WebApplicationContextUtils
,除了它采用FacesContext
参数而不是ServletContext
参数。
以下 example 显示了如何使用FacesContextUtils
:
1 | ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance()); |
ApacheStruts 2.x
由 Craig McClanahan 发明,支柱是由 Apache 软件基金会主办的 open-source 项目。在 time,它大大简化了 JSP/Servlet 编程范例,赢得了许多使用专有框架的开发人员。它简化了编程 model,它是开源的(因此在啤酒中是免费的),它有一个庞大的社区,让项目在 Java web 开发人员中成长并变得流行。
查看 Struts Spring 插件,了解 Struts 附带的 built-in Spring integration。
ApacheTapestry5.x
Tapestry是一个“Component 为 framework”,用于在 Java 中创建动态,健壮,高度可扩展的 web applications。“
虽然 Spring 有自己的强大的 web 层,但通过使用 Tapestry 用于 web 用户界面和 Spring 容器用于较低层,构建企业 Java 应用程序有许多独特的优势。
有关更多信息,请参阅 Tapestry 的专用Spring 的 integration 模块。
更多资源
以下链接转到有关本章所述的各种 web 框架的更多资源。