Web Servlet

1. Spring Web MVC

Spring Web MVC 是构建在 Servlet API 上的原始 Web 框架,已被包含在内 在 Spring Framework 中。正式名称“Spring Web MVC”, 来自其源模块的名称 (spring-webmvc), 但它更通常被称为 “Spring MVC”。spring-doc.cadn.net.cn

与 Spring Web MVC 并行,Spring Framework 5.0 引入了一个响应式堆栈 Web 框架 其名称“Spring WebFlux”也基于其源模块 (spring-webflux). 本章介绍 Spring Web MVC。下一章将介绍 Spring WebFlux。spring-doc.cadn.net.cn

有关基准信息以及与 Servlet 容器和 Java EE 版本的兼容性 范围,请参阅 Spring Framework Wikispring-doc.cadn.net.cn

1.1. DispatcherServlet

与许多其他 Web 框架一样,Spring MVC 是围绕前端控制器设计的 模式,其中 CENTRALServletDispatcherServlet提供共享算法 用于请求处理,而实际工作则由可配置的委托组件执行。 此模型非常灵活,并支持多种工作流。spring-doc.cadn.net.cn

DispatcherServlet一样,就像任何Servlet,需要根据 到 Servlet 规范中,通过使用 Java 配置或在web.xml. 反过来,DispatcherServlet使用 Spring 配置来发现 请求映射、视图解析、异常所需的委托组件 处理等。spring-doc.cadn.net.cn

下面的 Java 配置示例注册并初始化 这DispatcherServlet,它由 Servlet 容器自动检测 (请参阅 Servlet 配置):spring-doc.cadn.net.cn

Java
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}
Kotlin
class MyWebApplicationInitializer : WebApplicationInitializer {

    override fun onStartup(servletContext: ServletContext) {

        // Load Spring web application configuration
        val context = AnnotationConfigWebApplicationContext()
        context.register(AppConfig::class.java)

        // Create and register the DispatcherServlet
        val servlet = DispatcherServlet(context)
        val registration = servletContext.addServlet("app", servlet)
        registration.setLoadOnStartup(1)
        registration.addMapping("/app/*")
    }
}
除了直接使用 ServletContext API 之外,您还可以扩展AbstractAnnotationConfigDispatcherServletInitializer并覆盖特定方法 (请参阅 Context Hierarchy 下的示例)。
对于编程使用案例,GenericWebApplicationContext可用作 替代AnnotationConfigWebApplicationContext.请参阅GenericWebApplicationContextjavadoc 了解详细信息。

以下示例web.xmlconfiguration 注册并初始化DispatcherServlet:spring-doc.cadn.net.cn

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>
Spring Boot 遵循不同的初始化 Sequences。而不是挂接 Servlet 容器的生命周期中,Spring Boot 使用 Spring 配置来 bootstrap 本身和嵌入式 Servlet 容器。FilterServlet声明 在 Spring 配置中检测到并注册到 Servlet 容器中。 有关更多详细信息,请参阅 Spring Boot 文档

1.1.1. 上下文层次结构

DispatcherServlet期望WebApplicationContext(平原的延伸ApplicationContext) 获取其自己的配置。WebApplicationContext具有指向ServletContextServlet与之关联的。它还绑定到ServletContext,以便应用程序可以在RequestContextUtils要查找WebApplicationContext如果他们需要访问它。spring-doc.cadn.net.cn

对于许多应用程序,如果WebApplicationContext简单而足够。 也可以有一个上下文层次结构,其中有一个根WebApplicationContext在多个DispatcherServlet(或其他Servlet) 实例,每个实例具有 它自己的孩子WebApplicationContext配置。 看的附加功能ApplicationContext了解有关上下文层次结构功能的更多信息。spring-doc.cadn.net.cn

WebApplicationContext通常包含基础设施 Bean,例如数据存储库和 需要在多个Servlet实例。那些豆子 可以有效地继承,并且可以在特定于 Servlet 的 孩子WebApplicationContext,它通常包含给定Servlet. 下图显示了此关系:spring-doc.cadn.net.cn

MVC 上下文层次结构

以下示例将WebApplicationContext等级制度:spring-doc.cadn.net.cn

Java
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}
Kotlin
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    override fun getRootConfigClasses(): Array<Class<*>> {
        return arrayOf(RootConfig::class.java)
    }

    override fun getServletConfigClasses(): Array<Class<*>> {
        return arrayOf(App1Config::class.java)
    }

    override fun getServletMappings(): Array<String> {
        return arrayOf("/app1/*")
    }
}
如果不需要应用程序上下文层次结构,则应用程序可以返回所有 配置getRootConfigClasses()nullgetServletConfigClasses().

以下示例显示了web.xml等效:spring-doc.cadn.net.cn

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>
如果不需要应用程序上下文层次结构,应用程序可以配置 “root” 上下文,并保留contextConfigLocationServlet 参数为空。

1.1.2. 特殊 bean 类型

DispatcherServlet委托给特殊 bean 来处理请求并呈现 适当的回应。我们所说的 “特殊 bean” 是指 Spring 管理的Object实例 实施框架协定。这些通常带有内置合约,但 您可以自定义其属性并扩展或替换它们。spring-doc.cadn.net.cn

下表列出了DispatcherServlet:spring-doc.cadn.net.cn

Bean 类型 解释

HandlerMappingspring-doc.cadn.net.cn

将请求映射到处理程序以及用于预处理和后处理的拦截器列表。 映射基于一些标准,其详细信息因HandlerMapping实现。spring-doc.cadn.net.cn

两个主要的HandlerMappingimplementations 包括RequestMappingHandlerMapping(支持@RequestMappingannotated methods) 和SimpleUrlHandlerMapping(它维护处理程序的 URI 路径模式的显式注册)。spring-doc.cadn.net.cn

HandlerAdapterspring-doc.cadn.net.cn

帮助DispatcherServlet调用映射到请求的处理程序,而不管 处理程序的实际调用方式。例如,调用带注释的控制器 需要解析注释。a 的主要用途HandlerAdapter是 以屏蔽DispatcherServlet从这些细节中。spring-doc.cadn.net.cn

HandlerExceptionResolverspring-doc.cadn.net.cn

解决异常的策略,可能将它们映射到处理程序,再到 HTML 错误 视图或其他目标。请参阅例外spring-doc.cadn.net.cn

ViewResolverspring-doc.cadn.net.cn

解析逻辑String的视图名称View与 which to render to the response.请参阅视图分辨率视图技术spring-doc.cadn.net.cn

LocaleResolverLocaleContextResolverspring-doc.cadn.net.cn

解决Locale客户端正在使用可能他们的时区,以便能够 提供国际化视图。请参阅 区域设置spring-doc.cadn.net.cn

ThemeResolverspring-doc.cadn.net.cn

解决 Web 应用程序可以使用的主题,例如,提供个性化布局。 请参阅主题spring-doc.cadn.net.cn

MultipartResolverspring-doc.cadn.net.cn

用于解析多部分请求(例如,浏览器表单文件上传)的抽象 一些 Multipart 解析库的帮助。请参阅Multipart Resolverspring-doc.cadn.net.cn

FlashMapManagerspring-doc.cadn.net.cn

存储和检索 “input” 和 “output”FlashMap可以用来传递 属性从一个请求到另一个请求,通常通过重定向。 请参阅 Flash 属性spring-doc.cadn.net.cn

1.1.3. Web MVC 配置

应用程序可以声明处理请求所需的 Special Bean Types 中列出的基础结构 Bean。这DispatcherServlet检查WebApplicationContext对于每个特殊 bean。如果没有匹配的 bean 类型,则 它回退到DispatcherServlet.properties.spring-doc.cadn.net.cn

在大多数情况下,MVC 配置是最好的起点。它声明了所需的 bean 中,并提供更高级别的配置回调 API,以 自定义它。spring-doc.cadn.net.cn

Spring Boot 依赖于 MVC Java 配置来配置 Spring MVC 和 提供了许多额外的便捷选项。

1.1.4. Servlet 配置

在 Servlet 3.0+ 环境中,您可以选择配置 Servlet 容器 以编程方式作为替代方案或与web.xml文件。以下内容 example 注册一个DispatcherServlet:spring-doc.cadn.net.cn

Java
import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}
Kotlin
import org.springframework.web.WebApplicationInitializer

class MyWebApplicationInitializer : WebApplicationInitializer {

    override fun onStartup(container: ServletContext) {
        val appContext = XmlWebApplicationContext()
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")

        val registration = container.addServlet("dispatcher", DispatcherServlet(appContext))
        registration.setLoadOnStartup(1)
        registration.addMapping("/")
    }
}

WebApplicationInitializer是 Spring MVC 提供的一个接口,可确保您的 实现,并自动用于初始化任何 Servlet 3 容器。 的抽象基类实现WebApplicationInitializerAbstractDispatcherServletInitializer可以更轻松地注册DispatcherServlet通过覆盖方法来指定 servlet 映射和 的位置DispatcherServlet配置。spring-doc.cadn.net.cn

对于使用基于 Java 的 Spring 配置的应用程序,建议这样做,因为 以下示例显示:spring-doc.cadn.net.cn

Java
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
Kotlin
class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    override fun getRootConfigClasses(): Array<Class<*>>? {
        return null
    }

    override fun getServletConfigClasses(): Array<Class<*>>? {
        return arrayOf(MyWebConfig::class.java)
    }

    override fun getServletMappings(): Array<String> {
        return arrayOf("/")
    }
}

如果使用基于 XML 的 Spring 配置,则应直接从AbstractDispatcherServletInitializer,如下例所示:spring-doc.cadn.net.cn

Java
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}
Kotlin
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {

    override fun createRootApplicationContext(): WebApplicationContext? {
        return null
    }

    override fun createServletApplicationContext(): WebApplicationContext {
        return XmlWebApplicationContext().apply {
            setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
        }
    }

    override fun getServletMappings(): Array<String> {
        return arrayOf("/")
    }
}

AbstractDispatcherServletInitializer还提供了一种便捷的添加方式Filter实例,并让它们自动映射到DispatcherServlet,作为 以下示例显示:spring-doc.cadn.net.cn

Java
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}
Kotlin
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {

    // ...

    override fun getServletFilters(): Array<Filter> {
        return arrayOf(HiddenHttpMethodFilter(), CharacterEncodingFilter())
    }
}

每个过滤器都会根据其具体类型自动添加默认名称 映射到DispatcherServlet.spring-doc.cadn.net.cn

isAsyncSupportedprotected 的AbstractDispatcherServletInitializer提供了一个位置来在DispatcherServlet和所有 映射到它的 filters。默认情况下,此标志设置为true.spring-doc.cadn.net.cn

最后,如果您需要进一步自定义DispatcherServlet本身,您可以 覆盖createDispatcherServlet方法。spring-doc.cadn.net.cn

1.1.5. 处理

DispatcherServlet按如下方式处理请求:spring-doc.cadn.net.cn

  • WebApplicationContext在请求中作为属性进行搜索和绑定 控制器和进程中的其他元素可以使用。默认情况下它是绑定的 在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE钥匙。spring-doc.cadn.net.cn

  • 区域设置解析器绑定到请求,以 let 进程中的元素 解析在处理请求(渲染视图、准备 data 等)。如果不需要 locale 解析,则不需要 locale 解析程序。spring-doc.cadn.net.cn

  • 主题解析程序绑定到请求,让视图等元素确定 使用哪个主题。如果您不使用主题,则可以忽略它。spring-doc.cadn.net.cn

  • 如果指定 multipart 文件解析程序,则会检查请求是否存在 multipart。如果 multipart 的 Fragment 中,请求被包装在MultipartHttpServletRequest为 在此过程中通过其他元素进行进一步处理。有关详细信息,请参阅 Multipart Resolver 有关分段处理的信息。spring-doc.cadn.net.cn

  • 寻找合适的处理程序。如果找到处理程序,则执行链 与处理程序(预处理器、后处理器和控制器)关联的是 run 准备用于渲染的模型。或者,对于带注释的 控制器,响应可以渲染(在HandlerAdapter) 而不是 返回一个视图。spring-doc.cadn.net.cn

  • 如果返回模型,则呈现视图。如果未返回任何模型(可能是由于 拦截请求的预处理器或后处理器,可能是为了安全 reasons),则不会呈现任何视图,因为请求可能已经完成。spring-doc.cadn.net.cn

HandlerExceptionResolverWebApplicationContext习惯于 解决请求处理期间引发的异常。这些异常解析程序允许 自定义逻辑以解决异常。有关更多详细信息,请参阅例外spring-doc.cadn.net.cn

对于 HTTP 缓存支持,处理程序可以使用checkNotModified的方法WebRequest, 以及带注释的控制器的更多选项,如 控制器的 HTTP 缓存中所述。spring-doc.cadn.net.cn

您可以自定义单个DispatcherServlet实例 初始化参数 (init-param元素)添加到web.xml文件。下表列出了支持的参数:spring-doc.cadn.net.cn

表 1.DispatcherServlet 初始化参数
参数 解释

contextClassspring-doc.cadn.net.cn

实现ConfigurableWebApplicationContext,进行实例化,并将 由此 Servlet 在本地配置。默认情况下,XmlWebApplicationContext被使用。spring-doc.cadn.net.cn

contextConfigLocationspring-doc.cadn.net.cn

传递给上下文实例(由contextClass) 更改为 指示可以找到上下文的位置。字符串可能包含多个 字符串(使用逗号作为分隔符)来支持多个上下文。在 具有定义两次的 bean 的多个上下文位置,即最新的位置 优先。spring-doc.cadn.net.cn

namespacespring-doc.cadn.net.cn

的命名空间WebApplicationContext.默认为[servlet-name]-servlet.spring-doc.cadn.net.cn

throwExceptionIfNoHandlerFoundspring-doc.cadn.net.cn

是否抛出NoHandlerFoundException当找不到请求的处理程序时。 然后,可以使用HandlerExceptionResolver(例如,通过使用@ExceptionHandlercontroller 方法)并像任何其他方法一样处理。spring-doc.cadn.net.cn

默认情况下,此设置为false,在这种情况下,DispatcherServlet将 响应状态设置为 404 (NOT_FOUND),而不引发异常。spring-doc.cadn.net.cn

请注意,如果默认 servlet 处理为 此外,未解析的请求始终转发到默认 servlet 并且 404 永远不会提高。spring-doc.cadn.net.cn

1.1.6. 路径匹配

Servlet API 将完整的请求路径公开为requestURI并进一步细分 到contextPath,servletPathpathInfo其值根据 Servlet 已映射。从这些输入中, Spring MVC 需要确定到 用于处理程序映射,即DispatcherServlet本身,不包括contextPath和任何servletMapping前缀(如果存在)。spring-doc.cadn.net.cn

servletPathpathInfo被解码,这使得它们无法比较 直接完整requestURI为了派生 lookupPath,这使得它 解码requestURI.但是,这引入了它自己的问题,因为 path 可能包含编码的保留字符,例如 或 在解码后更改路径的结构,这也可能导致安全性 问题。此外,Servlet 容器可以将"/"";"servletPath到变化 度,这使得它进一步无法执行startsWith比较 这requestURI.spring-doc.cadn.net.cn

这就是为什么最好避免依赖servletPath它附带了 基于前缀servletPathmapping 类型。如果DispatcherServlet映射为 default Servlet 带有或不带有前缀 with,并且 Servlet container 为 4.0+,则 Spring MVC 能够检测 Servlet 映射类型并避免 使用"/""/*"servletPathpathInfo完全。在 3.1 Servlet 容器上, 假设相同的 Servlet 映射类型,则可以通过提供 一个UrlPathHelperalwaysUseFullPath=true通过 路径匹配 MVC 配置。spring-doc.cadn.net.cn

幸运的是,默认的 Servlet 映射是一个不错的选择。然而,仍然有 一个问题,因为"/"requestURI需要解码以使其能够与 controller 映射。这同样是不可取的,因为有可能解码 更改路径结构的保留字符。如果不需要此类字符, 然后你可以拒绝它们(如 Spring Security HTTP 防火墙),或者你可以配置UrlPathHelperurlDecode=false但是控制器映射需要与 编码路径,这可能并不总是正常工作。此外,有时DispatcherServlet需要与另一个 Servlet 共享 URL 空间,并且可能需要 由 prefix 映射。spring-doc.cadn.net.cn

上述问题可以通过从PathMatcher自 解析的PathPattern在 5.3 或更高版本中可用,请参阅模式比较。与AntPathMatcher哪个需要 无论是解码的查找路径还是编码的控制器映射,都会解析PathPattern匹配到名为RequestPath、一个路径段 一次。这允许单独解码和清理 path segment 值,而无需 改变路径结构的风险。解析PathPattern还支持 的使用servletPath前缀映射,只要前缀保持简单并且 没有任何需要编码的字符。spring-doc.cadn.net.cn

1.1.7. 拦截

HandlerMappingimplementations 支持处理程序拦截器,这些拦截器在以下情况下很有用 您希望将特定功能应用于某些请求 — 例如,检查 一个 principal。拦截器必须实现HandlerInterceptororg.springframework.web.servletpackage 中,这三个方法应该提供足够的 灵活地进行各种前处理和后处理:spring-doc.cadn.net.cn

preHandle(..)method 返回一个布尔值。您可以使用此方法 break 或 继续处理执行链。当此方法返回true这 处理程序执行链继续。当它返回 false 时,DispatcherServlet假设拦截器本身已经处理了请求(例如,渲染了一个 appropriate 视图),并且不会继续执行其他拦截器和实际的 处理程序。spring-doc.cadn.net.cn

有关如何 配置拦截器。您也可以通过使用 setter 对单个HandlerMapping实现。spring-doc.cadn.net.cn

postHandlemethod 的@ResponseBodyResponseEntity方法 中写入并提交响应,并在HandlerAdapter和之前postHandle.这意味着现在对响应进行任何更改都为时已晚,例如将 一个额外的标头。对于此类方案,您可以实现ResponseBodyAdvice以及 将其声明为 Controller Advice bean 或直接在RequestMappingHandlerAdapter.spring-doc.cadn.net.cn

1.1.8. 异常

如果在请求映射期间发生异常,或者从请求处理程序(例如 一个@Controller)、DispatcherServletdelegates 到HandlerExceptionResolverbean 来解决异常并提供替代处理,这通常是 error 响应。spring-doc.cadn.net.cn

下表列出了可用的HandlerExceptionResolver实现:spring-doc.cadn.net.cn

表 2.HandlerExceptionResolver 实现
HandlerExceptionResolver 描述

SimpleMappingExceptionResolverspring-doc.cadn.net.cn

异常类名称和错误视图名称之间的映射。用于渲染 浏览器应用程序中的错误页面。spring-doc.cadn.net.cn

DefaultHandlerExceptionResolverspring-doc.cadn.net.cn

解决 Spring MVC 引发的异常并将它们映射到 HTTP 状态代码。 参见 alternativeResponseEntityExceptionHandlerREST API 异常spring-doc.cadn.net.cn

ResponseStatusExceptionResolverspring-doc.cadn.net.cn

使用@ResponseStatus注解并将它们映射到 HTTP 状态 代码。spring-doc.cadn.net.cn

ExceptionHandlerExceptionResolverspring-doc.cadn.net.cn

通过调用@ExceptionHandler方法中的@Controller@ControllerAdvice类。请参阅 @ExceptionHandler方法spring-doc.cadn.net.cn

解析器链

您可以通过声明多个HandlerExceptionResolverbean 并设置其order属性。 order 属性越高,异常解析程序的位置就越晚。spring-doc.cadn.net.cn

的合同HandlerExceptionResolver指定它可以返回:spring-doc.cadn.net.cn

MVC Config 自动为默认 Spring MVC 声明内置解析器 exceptions, 对于@ResponseStatus注解的异常,以及为了支持@ExceptionHandler方法。您可以自定义或替换该列表。spring-doc.cadn.net.cn

容器错误页面

如果任何HandlerExceptionResolver因此, left 进行传播,或者如果响应状态设置为错误状态(即 4xx、5xx), Servlet 容器可以在 HTML 中呈现默认错误页。自定义默认 error page 中,可以在web.xml. 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<error-page>
    <location>/error</location>
</error-page>

在前面的示例中,当异常冒泡或响应具有错误状态时, Servlet 容器在容器内向配置的 URL 发出 ERROR 调度 (例如,/error).然后由DispatcherServlet,可能映射它 更改为@Controller,该 URL 可以实现以返回带有 model 的错误视图名称 或呈现 JSON 响应,如下例所示:spring-doc.cadn.net.cn

Java
@RestController
public class ErrorController {

    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));
        return map;
    }
}
Kotlin
@RestController
class ErrorController {

    @RequestMapping(path = ["/error"])
    fun handle(request: HttpServletRequest): Map<String, Any> {
        val map = HashMap<String, Any>()
        map["status"] = request.getAttribute("javax.servlet.error.status_code")
        map["reason"] = request.getAttribute("javax.servlet.error.message")
        return map
    }
}
Servlet API 不提供在 Java 中创建错误页面映射的方法。您可以 但是,请同时使用WebApplicationInitializer和最小的web.xml.

1.1.9. 视图分辨率

Spring MVC 定义了ViewResolverView允许您渲染 模型,而无需将您绑定到特定的视图技术。ViewResolver提供视图名称和实际视图之间的映射。View解决准备工作 的数据,然后再移交给特定的视图技术。spring-doc.cadn.net.cn

下表提供了有关ViewResolver等级制度:spring-doc.cadn.net.cn

表 3.ViewResolver 实现
视图解析器 描述

AbstractCachingViewResolverspring-doc.cadn.net.cn

的子类AbstractCachingViewResolvercache 视图实例。 缓存可以提高某些视图技术的性能。您可以关闭 cache 通过设置cacheproperty 设置为false.此外,如果您必须刷新 运行时的某个视图(例如,当修改 FreeMarker 模板时), 您可以使用removeFromCache(String viewName, Locale loc)方法。spring-doc.cadn.net.cn

UrlBasedViewResolverspring-doc.cadn.net.cn

简单实现ViewResolver影响直接 将逻辑视图名称解析为没有显式映射定义的 URL。 如果您的逻辑名称与视图资源的名称匹配,则此选项是合适的 以简单的方式,无需任意映射。spring-doc.cadn.net.cn

InternalResourceViewResolverspring-doc.cadn.net.cn

方便的子类UrlBasedViewResolver那个辅助InternalResourceView(在 effect、Servlet 和 JSP)和子类(如JstlViewTilesView.您可以 使用setViewClass(..). 请参阅UrlBasedViewResolverjavadoc 了解详细信息。spring-doc.cadn.net.cn

FreeMarkerViewResolverspring-doc.cadn.net.cn

方便的子类UrlBasedViewResolver那个辅助FreeMarkerView和 它们的自定义子类。spring-doc.cadn.net.cn

ContentNegotiatingViewResolverspring-doc.cadn.net.cn

实现ViewResolver接口,该接口根据 request file name 或Accept页眉。请参阅 内容协商spring-doc.cadn.net.cn

BeanNameViewResolverspring-doc.cadn.net.cn

实现ViewResolver接口将视图名称解释为 Bean 名称。这是一个非常灵活的变体,它 允许根据不同的视图名称混合和匹配不同的视图类型。 每个这样的View可以定义为 bean,例如在 XML 或配置类中。spring-doc.cadn.net.cn

处理

您可以通过声明多个解析器 Bean 来链接视图解析器,如有必要,还可以通过 设置order属性来指定排序。请记住,order 属性越高, View Resolver 在链中的位置越晚。spring-doc.cadn.net.cn

一个ViewResolver指定它可以返回 null,以指示 找不到视图。但是,对于 JSP 和InternalResourceViewResolver, 确定 JSP 是否存在的唯一方法是通过RequestDispatcher.因此,您必须始终配置InternalResourceViewResolver在 View Resolver 的总体顺序中排在最后。spring-doc.cadn.net.cn

配置视图分辨率就像添加ViewResolverbeans 到你的 Spring 配置。MVC Config视图解析器和添加无逻辑视图控制器提供了一个专用的配置 API,这对 HTML 模板很有用 不使用控制器逻辑的渲染。spring-doc.cadn.net.cn

重 定向

特别的redirect:prefix 允许您执行重定向。这UrlBasedViewResolver(及其子类)将此视为一条指令, redirect 是必需的。视图名称的其余部分是重定向 URL。spring-doc.cadn.net.cn

实际效果与控制器返回RedirectView,但现在 控制器本身可以根据 logical view name 进行作。逻辑视图 name(例如redirect:/myapp/some/resource) 相对于当前 Servlet 上下文中,而redirect:https://myhost.com/some/arbitrary/path重定向到绝对 URL。spring-doc.cadn.net.cn

请注意,如果控制器方法带有@ResponseStatus、批注 值优先于 Response Status (响应状态) 由RedirectView.spring-doc.cadn.net.cn

转发

您还可以使用特殊的forward:前缀 (前缀) 表示视图名称 最终由UrlBasedViewResolver和子类。这将创建一个InternalResourceView,它会执行RequestDispatcher.forward(). 因此,此前缀对InternalResourceViewResolverInternalResourceView(对于 JSP),但如果您使用其他视图,则可能会有所帮助 技术,但仍希望强制将资源的 forward 由 Servlet/JSP 引擎。请注意,您也可以链接多个视图解析器。spring-doc.cadn.net.cn

内容协商

ContentNegotiatingViewResolver不解析视图本身,而是解析 delegates 添加到其他视图解析程序,并选择类似于所请求的表示的视图 由客户。表示可以从Accept标头或从 query 参数(例如"/path?format=pdf").spring-doc.cadn.net.cn

ContentNegotiatingViewResolver选择适当的View处理请求 通过将请求媒体类型与媒体类型(也称为Content-Type) 由View与其每个ViewResolvers.这 第一View在列表中,具有兼容的Content-Type返回表示形式 到客户端。如果ViewResolver链 通过DefaultViewsproperty 的这 后一个选项适用于单例Views,这可以呈现适当的 表示当前资源,而不考虑逻辑视图名称。这Acceptheader 可以包含通配符(例如text/*),在这种情况下,会触发View谁的Content-Typetext/xml是兼容的匹配项。spring-doc.cadn.net.cn

有关配置详细信息,请参阅 MVC Config 下的 View Resolversspring-doc.cadn.net.cn

1.1.10. 区域设置

Spring 架构的大部分都支持国际化,因为 Spring Web MVC 框架可以。DispatcherServlet用于自动解决消息 通过使用客户端的区域设置。这是通过LocaleResolver对象。spring-doc.cadn.net.cn

当请求传入时,DispatcherServlet查找 locale 解析程序,如果 找到一个,它会尝试使用它来设置 locale。通过使用RequestContext.getLocale()方法,您始终可以检索由 locale 解析程序解析的区域设置。spring-doc.cadn.net.cn

除了自动 locale 解析之外,您还可以将拦截器附加到 handler 映射(有关 handler 的更多信息,请参阅 Interception mapping interceptor)来更改特定情况下的区域设置(例如 基于请求中的参数)。spring-doc.cadn.net.cn

区域设置解析器和拦截器在org.springframework.web.servlet.i18n包中,并在您的应用程序中进行配置 context 的 intent 中。以下 locale 解析程序选择包含在 Spring。spring-doc.cadn.net.cn

时区

除了获取客户端的区域设置之外,了解其时区通常也很有用。 这LocaleContextResolverinterface 提供对LocaleResolver那让我们 解析器提供更丰富的LocaleContext,其中可能包括时区信息。spring-doc.cadn.net.cn

如果可用,用户的TimeZone可以使用RequestContext.getTimeZone()方法。自动使用时区信息 按任意日期/时间ConverterFormatter对象,这些对象在 Spring 的ConversionService.spring-doc.cadn.net.cn

标头解析程序

此 locale 解析程序检查accept-language标头 通过客户端(例如,Web 浏览器)。通常,此标头字段包含 客户端的作系统。请注意,此解析器不支持时区 信息。spring-doc.cadn.net.cn

此 locale 解析器检查Cookie,以查看LocaleTimeZone。如果是这样,它将使用指定的详细信息。通过使用 属性,您可以指定 Cookie 的名称以及 最大年龄。以下示例定义了一个CookieLocaleResolver:spring-doc.cadn.net.cn

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

    <property name="cookieName" value="clientlanguage"/>

    <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
    <property name="cookieMaxAge" value="100000"/>

</bean>

下表描述了这些属性CookieLocaleResolver:spring-doc.cadn.net.cn

会话解析程序

SessionLocaleResolver让您检索LocaleTimeZone从 会话。与CookieLocaleResolver,此策略将本地选择的区域设置存储在 Servlet 容器的HttpSession.因此,这些设置是临时的 ,因此,在每个会话结束时都会丢失。spring-doc.cadn.net.cn

请注意,它与外部会话管理机制没有直接关系, 例如 Spring Session 项目。这SessionLocaleResolver计算和 修改相应的HttpSession属性与当前HttpServletRequest.spring-doc.cadn.net.cn

区域设置拦截器

您可以通过添加LocaleChangeInterceptor复制到HandlerMapping定义。它检测请求中的参数并更改区域设置 因此,调用setLocale方法上的LocaleResolver在 Dispatcher 的 应用程序上下文。下一个示例显示对*.view资源 ,其中包含一个名为siteLanguage现在更改区域设置。所以,例如, 对 URL 的请求,https://www.sf.net/home.view?siteLanguage=nl,更改站点 语言到荷兰语。以下示例显示如何拦截 locale:spring-doc.cadn.net.cn

<bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

1.1.11. 主题

您可以应用 Spring Web MVC 框架主题来设置 应用程序,从而增强用户体验。主题是 static 的集合 资源(通常是样式表和图像),这些资源会影响 应用。spring-doc.cadn.net.cn

定义主题

要在 Web 应用程序中使用主题,您必须设置org.springframework.ui.context.ThemeSource接口。这WebApplicationContext接口扩展ThemeSource而是将其职责委托给专用的 实现。默认情况下,委托是一个org.springframework.ui.context.support.ResourceBundleThemeSource实现 从 Classpath 的根目录加载属性文件。使用自定义ThemeSource实现,或者配置ResourceBundleThemeSource, 您可以在应用程序上下文中使用保留名称themeSource. Web 应用程序上下文会自动检测具有该名称的 bean 并使用它。spring-doc.cadn.net.cn

当您使用ResourceBundleThemeSource,主题在简单的属性 文件。属性文件列出了构成主题的资源,如下例所示:spring-doc.cadn.net.cn

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg

属性的键是引用视图中主题元素的名称 法典。对于 JSP,您通常使用spring:theme自定义标签,即 与spring:message标记。以下 JSP 片段使用主题 在前面的示例中定义,以自定义外观:spring-doc.cadn.net.cn

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
    <head>
        <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code='background'/>">
        ...
    </body>
</html>

默认情况下,ResourceBundleThemeSource使用空的 base name 前缀。因此, 属性文件是从 Classpath 的根目录加载的。因此,您将cool.propertiestheme 定义(对于 示例,在/WEB-INF/classes).这ResourceBundleThemeSource使用标准的 Java Resource Bundle 加载机制,允许主题完全国际化。为 例如,我们可以有一个/WEB-INF/classes/cool_nl.properties引用一个特殊的 带有荷兰语文本的背景图像。spring-doc.cadn.net.cn

解决主题

定义主题后,如上一节所述, 您可以决定使用哪个主题。这DispatcherServlet查找名为themeResolver了解具体ThemeResolverimplementation 来使用。主题解析程序的工作方式大致相同 方式作为LocaleResolver.它检测要用于特定请求的主题,还可以 更改请求的主题。下表描述了 Spring 提供的主题解析程序:spring-doc.cadn.net.cn

表 5.ThemeResolver 实现
描述

FixedThemeResolverspring-doc.cadn.net.cn

选择固定主题,使用defaultThemeName财产。spring-doc.cadn.net.cn

SessionThemeResolverspring-doc.cadn.net.cn

主题在用户的 HTTP 会话中维护。它只需要为 每个会话,但不会在会话之间保留。spring-doc.cadn.net.cn

CookieThemeResolverspring-doc.cadn.net.cn

所选主题存储在客户端上的 Cookie 中。spring-doc.cadn.net.cn

Spring 还提供了一个ThemeChangeInterceptor这允许主题在每个 request 替换为简单的请求参数。spring-doc.cadn.net.cn

1.1.12. Multipart Resolver (多部分解析程序)

MultipartResolverorg.springframework.web.multipartpackage 是一种策略 用于解析分段请求,包括文件上传。有一个实现 基于 Commons FileUpload 和 另一个基于 Servlet 3.0 多部分请求解析。spring-doc.cadn.net.cn

要启用分段处理,您需要声明一个MultipartResolverbean 中的DispatcherServlet名称为multipartResolver. 这DispatcherServlet检测到它并将其应用于传入请求。当 POST 内容类型为multipart/form-data时,解析程序会解析 content 将当前的HttpServletRequest作为MultipartHttpServletRequest自 除了将部件作为请求参数公开外,还提供对已解析文件的访问。spring-doc.cadn.net.cn

Apache 共享资源FileUpload

使用 Apache CommonsFileUpload中,您可以配置类型为CommonsMultipartResolver替换为 namemultipartResolver.您还需要拥有 这commons-fileuploadjar 作为 Classpath 上的依赖项。spring-doc.cadn.net.cn

此解析程序变体委托给应用程序内的本地库,提供 在 Servlet 容器之间实现最大的可移植性。作为替代方案,请考虑标准 通过容器自己的解析器进行 Servlet 多部分解析,如下所述。spring-doc.cadn.net.cn

Commons FileUpload 传统上仅适用于 POST 请求,但接受任何multipart/内容类型。请参阅CommonsMultipartResolverjavadoc 了解详细信息和配置选项。spring-doc.cadn.net.cn

Servlet 3.0

Servlet 3.0 多部分解析需要通过 Servlet 容器配置来启用。 为此,请执行以下作:spring-doc.cadn.net.cn

以下示例演示如何设置MultipartConfigElement在 Servlet 注册上:spring-doc.cadn.net.cn

Java
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

}
Kotlin
class AppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    // ...

    override fun customizeRegistration(registration: ServletRegistration.Dynamic) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(MultipartConfigElement("/tmp"))
    }

}

一旦 Servlet 3.0 配置就位,您就可以添加一个StandardServletMultipartResolver替换为 namemultipartResolver.spring-doc.cadn.net.cn

此解析器变体按原样使用 Servlet 容器的多部分解析器, 可能会使应用程序面临容器实现差异。 默认情况下,它将尝试解析任何multipart/内容类型替换为任何 HTTP 方法,但并非所有 Servlet 容器都支持此方法。请参阅StandardServletMultipartResolverjavadoc 了解详细信息和配置选项。spring-doc.cadn.net.cn

1.1.13. 日志记录

Spring MVC 中的 DEBUG 级日志记录被设计为紧凑、最小和 人性化。它侧重于有用的高价值信息 over again 与仅在调试特定问题时有用的其他方法进行验证。spring-doc.cadn.net.cn

TRACE 级别的日志记录通常遵循与 DEBUG 相同的原则(例如,还遵循 不应是消防水带),但可用于调试任何问题。此外,一些日志 消息在 TRACE 和 DEBUG 中可能显示不同级别的详细信息。spring-doc.cadn.net.cn

良好的日志记录来自使用日志的经验。如果您发现任何 未达到既定目标,请告诉我们。spring-doc.cadn.net.cn

敏感数据

DEBUG 和 TRACE 日志记录可能会记录敏感信息。这就是为什么请求参数和 默认情况下,标头是屏蔽的,并且必须显式启用其完整日志记录 通过enableLoggingRequestDetails属性DispatcherServlet.spring-doc.cadn.net.cn

以下示例演示如何使用 Java 配置执行此作:spring-doc.cadn.net.cn

Java
public class MyInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return ... ;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return ... ;
    }

    @Override
    protected String[] getServletMappings() {
        return ... ;
    }

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        registration.setInitParameter("enableLoggingRequestDetails", "true");
    }

}
Kotlin
class MyInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {

    override fun getRootConfigClasses(): Array<Class<*>>? {
        return ...
    }

    override fun getServletConfigClasses(): Array<Class<*>>? {
        return ...
    }

    override fun getServletMappings(): Array<String> {
        return ...
    }

    override fun customizeRegistration(registration: ServletRegistration.Dynamic) {
        registration.setInitParameter("enableLoggingRequestDetails", "true")
    }
}

1.2. 过滤器

spring-webmodule 提供了一些有用的过滤器:spring-doc.cadn.net.cn

1.2.1. 表单数据

浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据,但非浏览器客户端也可以 使用 HTTP PUT、PATCH 和 DELETE。Servlet API 需要ServletRequest.getParameter*()方法仅支持 HTTP POST 的表单字段访问。spring-doc.cadn.net.cn

spring-webmodule 提供FormContentFilter拦截 HTTP PUT、PATCH 和 DELETE 内容类型为application/x-www-form-urlencoded中,从 请求正文,并将ServletRequest创建表单数据 可通过ServletRequest.getParameter*()方法系列。spring-doc.cadn.net.cn

1.2.2. 请求头转发

当请求通过代理(例如负载均衡器)时,主机、端口和 方案可能会更改,这使得创建指向正确 Host、Port 和 Scheme。spring-doc.cadn.net.cn

RFC 7239 定义了ForwardedHTTP 标头 代理可用于提供有关原始请求的信息。还有其他 非标准标头,包括X-Forwarded-Host,X-Forwarded-Port,X-Forwarded-Proto,X-Forwarded-SslX-Forwarded-Prefix.spring-doc.cadn.net.cn

ForwardedHeaderFilter是一个 Servlet 过滤器,它修改请求以便 a) 根据Forwarded标头,以及 b) 删除这些 标头以消除进一步的影响。过滤器依赖于包装请求,并且 因此,它必须优先于其他过滤器排序,例如RequestContextFilter那 应该使用修改后的请求,而不是原始请求。spring-doc.cadn.net.cn

转发的 Headers 存在安全注意事项,因为应用程序无法知道 标头是由代理按预期添加的,还是由恶意客户端添加的。这就是为什么 应将信任边界的代理配置为删除 UntrustedForwarded来自外部的标头。您还可以配置ForwardedHeaderFilterremoveOnly=true,在这种情况下,它会删除但不使用标头。spring-doc.cadn.net.cn

为了支持异步请求和错误调度,此 filter 应该映射为DispatcherType.ASYNC以及DispatcherType.ERROR. 如果使用 Spring Framework 的AbstractAnnotationConfigDispatcherServletInitializer(请参阅 Servlet 配置)所有过滤器都自动注册到所有 dispatch 中 类型。但是,如果通过web.xml或在 Spring Boot 中通过FilterRegistrationBean请务必包括DispatcherType.ASYNCDispatcherType.ERROR除了DispatcherType.REQUEST.spring-doc.cadn.net.cn

1.2.3. 浅层 ETag

ShallowEtagHeaderFilterfilter 通过缓存内容来创建 “浅层” ETag 写入响应并从中计算 MD5 哈希。下次客户端发送时, 它执行相同的作,但它也会将计算的值与If-None-Matchrequest 标头,如果两者相等,则返回 304 (NOT_MODIFIED)。spring-doc.cadn.net.cn

此策略可节省网络带宽,但不节省 CPU,因为必须计算完整响应 对于每个请求。前面描述的控制器级别的其他策略可以避免 计算。请参阅 HTTP 缓存spring-doc.cadn.net.cn

此过滤器具有writeWeakETag参数,该参数将过滤器配置为写入弱 ETag 类似于以下内容:W/"02a2d595e6ed9a0b24f027f2b63b134d6"(如 RFC 7232 第 2.3 节中所定义)。spring-doc.cadn.net.cn

为了支持异步请求,必须映射此过滤器 跟DispatcherType.ASYNC以便过滤器可以延迟并成功生成 ETag 添加到最后一个异步调度的末尾。如果使用 Spring Framework 的AbstractAnnotationConfigDispatcherServletInitializer(请参阅 Servlet 配置) 所有过滤器都会自动为所有 Dispatch 类型注册。但是,如果注册 通过web.xml或在 Spring Boot 中通过FilterRegistrationBean请务必包括DispatcherType.ASYNC.spring-doc.cadn.net.cn

1.2.4. CORS

Spring MVC 通过 控制器。但是,当与 Spring Security 一起使用时,我们建议依赖内置的CorsFilter必须在 Spring Security 的过滤器链之前订购。spring-doc.cadn.net.cn

有关更多详细信息,请参阅有关 CORSCORS 筛选器的部分。spring-doc.cadn.net.cn

1.3. 带注解的控制器

Spring MVC 提供了一个基于 Comments 的编程模型,其中@Controller@RestController组件使用注解来表示请求映射、请求输入、 异常处理等。带 Comments 的控制器具有灵活的方法签名和 不必扩展基类,也不必实现特定的接口。 以下示例显示了由 annotations 定义的控制器:spring-doc.cadn.net.cn

Java
@Controller
public class HelloController {

    @GetMapping("/hello")
    public String handle(Model model) {
        model.addAttribute("message", "Hello World!");
        return "index";
    }
}
Kotlin
import org.springframework.ui.set

@Controller
class HelloController {

    @GetMapping("/hello")
    fun handle(model: Model): String {
        model["message"] = "Hello World!"
        return "index"
    }
}

在前面的示例中,该方法接受Model并返回视图名称作为String, 但还存在许多其他选项,本章稍后将对此进行解释。spring-doc.cadn.net.cn

spring.io 上的指南和教程使用基于注释的 编程模型。

1.3.1. 声明

您可以通过在 Servlet 的WebApplicationContext.这@Controllerstereotype 允许自动检测, 与 Spring 对检测的一般支持一致@Component类路径中的 classes 以及为它们自动注册 bean 定义。它还充当 annotated 类,指示其作为 Web 组件的角色。spring-doc.cadn.net.cn

要启用此类@Controllerbeans 中,你可以将组件扫描添加到 您的 Java 配置,如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

    // ...
}
Kotlin
@Configuration
@ComponentScan("org.example.web")
class WebConfig {

    // ...
}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.web"/>

    <!-- ... -->

</beans>

@RestController是一个组合的注释,即 本身使用@Controller@ResponseBody来指示其 每个方法都继承了类型级@ResponseBody注释,因此写入 直接到响应正文与视图解析和使用 HTML 模板进行渲染。spring-doc.cadn.net.cn

AOP 代理

在某些情况下,您可能需要在运行时使用 AOP 代理装饰控制器。 例如,如果您选择@Transactional注解直接放在 控制器。在这种情况下,特别是对于控制器,我们建议 使用基于类的代理。这通常是控制器的默认选择。 但是,如果控制器必须实现不是 Spring Context 的接口 callback(例如InitializingBean,*Aware等),则可能需要显式 配置基于类的代理。例如,使用<tx:annotation-driven/>您可以 更改为<tx:annotation-driven proxy-target-class="true"/>@EnableTransactionManagement您可以更改为@EnableTransactionManagement(proxyTargetClass = true).spring-doc.cadn.net.cn

1.3.2. 请求映射

您可以使用@RequestMapping注解将请求映射到控制器方法。它有 按 URL、HTTP 方法、请求参数、标头和媒体匹配的各种属性 类型。您可以在类级别使用它来表示共享映射,也可以在方法级别使用它 以缩小到特定终端节点映射的范围。spring-doc.cadn.net.cn

还有 HTTP 方法特定的快捷方式变体@RequestMapping:spring-doc.cadn.net.cn

快捷方式是提供的自定义注释,因为: 可以说,大多数控制器方法都应该映射到特定的 HTTP 方法,而不是 用@RequestMapping,默认情况下,它与所有 HTTP 方法匹配。 一个@RequestMapping在类级别仍然需要来表示共享映射。spring-doc.cadn.net.cn

以下示例具有类型和方法级别映射:spring-doc.cadn.net.cn

Java
@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    fun getPerson(@PathVariable id: Long): Person {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun add(@RequestBody person: Person) {
        // ...
    }
}
URI 模式

@RequestMapping方法可以使用 URL 模式进行映射。有两种选择:spring-doc.cadn.net.cn

  • PathPattern— 与 URL 路径匹配的预解析模式,也预先解析为PathContainer.该解决方案专为 Web 使用而设计,可有效地处理编码和 path 参数进行匹配,并有效地进行匹配。spring-doc.cadn.net.cn

  • AntPathMatcher— 将 String 模式与 String 路径匹配。这是原始的 解决方案也用于选择 Classpath 上的资源,在 filesystem 和其他位置。它的效率较低,并且 String path input 是一个 有效处理 URL 的编码和其他问题的挑战。spring-doc.cadn.net.cn

PathPattern是 Web 应用程序的推荐解决方案,也是 Spring WebFlux 的 Web Flux 中。在 5.3 版本之前,AntPathMatcher是 Spring MVC 中的唯一选择 并继续为默认值。然而PathPattern可以在 MVC 配置中启用。spring-doc.cadn.net.cn

PathPattern支持与AntPathMatcher.此外,它还 支持捕获模式,例如{*spring},用于匹配 0 个或多个路径段 在路径的末尾。PathPattern还限制了 for matching multiple 的使用 path segments 的 URL 中,则只允许在 pattern 的末尾使用。这消除了许多 为给定请求选择最佳匹配模式时出现歧义的情况。 有关完整的模式语法,请参阅 PathPatternAntPathMatcher**spring-doc.cadn.net.cn

一些示例模式:spring-doc.cadn.net.cn

捕获的 URI 变量可以使用@PathVariable.例如:spring-doc.cadn.net.cn

Java
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}
Kotlin
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
    // ...
}

您可以在类和方法级别声明 URI 变量,如下例所示:spring-doc.cadn.net.cn

Java
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}
Kotlin
@Controller
@RequestMapping("/owners/{ownerId}")
class OwnerController {

    @GetMapping("/pets/{petId}")
    fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
        // ...
    }
}

URI 变量会自动转换为适当的类型,或者TypeMismatchException被提升。简单类型 (int,long,Date等)默认受支持,您可以 注册对任何其他数据类型的支持。 请参阅 类型转换DataBinder.spring-doc.cadn.net.cn

您可以显式命名 URI 变量(例如@PathVariable("customId")),但您可以 如果名称相同并且您的代码是使用调试编译的,则省略该详细信息 信息或使用-parameterscompiler 标志。spring-doc.cadn.net.cn

语法{varName:regex}声明一个带有正则表达式的 URI 变量,该正则表达式具有 语法{varName:regex}.例如,给定的 URL"/spring-web-3.0.5.jar",则使用以下方法 提取名称、版本和文件扩展名:spring-doc.cadn.net.cn

Java
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
    // ...
}
Kotlin
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable name: String, @PathVariable version: String, @PathVariable ext: String) {
    // ...
}

URI 路径模式也可以嵌入${…​}启动时解析的占位符 通过使用PropertySourcesPlaceholderConfigurer针对本地、系统、环境和 其他属性来源。例如,您可以使用它来根据 一些外部配置。spring-doc.cadn.net.cn

形态比较

当多个模式与一个 URL 匹配时,必须选择最佳匹配项。这是通过 以下之一,具体取决于是否使用 parsedPathPattern是否启用使用:spring-doc.cadn.net.cn

两者都有助于对模式进行排序,其中更具体的模式位于顶部。如果 它具有较少的 URI 变量(计为 1)、单个通配符(计为 1)、 和双通配符(计为 2)。如果得分相等,则选择较长的模式。 在给定相同的分数和长度的情况下,URI 变量多于通配符的模式为 选择。spring-doc.cadn.net.cn

默认映射模式 () 从评分中排除,并且始终 最后排序。此外,前缀模式(例如/**/public/**) 被视为较小 specific than 其他没有双通配符的模式。spring-doc.cadn.net.cn

有关完整详细信息,请点击上述链接进入 Comparators 模式。spring-doc.cadn.net.cn

后缀匹配

从 5.3 开始,默认情况下 Spring MVC 不再执行.*后缀模式 匹配控制器映射到/person也被隐式映射到/person.*.因此,不再使用路径扩展来解释 响应的请求内容类型 — 例如/person.pdf,/person.xml, 等等。spring-doc.cadn.net.cn

当浏览器过去发送Accept头 这很难始终如一地解释。目前,这不再是必需的,并且 使用Acceptheader 应该是首选。spring-doc.cadn.net.cn

随着时间的推移,文件扩展名的使用已被证明以各种方式存在问题。 当与 URI 变量、路径参数和 URI 编码。基于 URL 的授权的推理 安全性(有关更多详细信息,请参阅下一节)也变得更加困难。spring-doc.cadn.net.cn

要在 5.3 之前的版本中完全禁用路径扩展名,请设置以下内容:spring-doc.cadn.net.cn

有一种方法可以请求内容类型,而不是通过"Accept"header 仍然可以 很有用,例如,在浏览器中键入 URL 时。路径扩展的安全替代方案是 以使用 Query Parameter 策略。如果必须使用文件扩展名,请考虑限制 它们通过mediaTypesContentNegotiationConfigurer 的属性。spring-doc.cadn.net.cn

后缀匹配和 RFD

反射文件下载 (RFD) 攻击与 XSS 类似,因为它依赖于请求输入 (例如,查询参数和 URI 变量)反映在响应中。但是,而不是 将 JavaScript 插入 HTML 中,RFD 攻击依赖于浏览器切换来执行 download 并在稍后双击时将响应视为可执行脚本。spring-doc.cadn.net.cn

在 Spring MVC 中,@ResponseBodyResponseEntity方法存在风险,因为 它们可以呈现不同的内容类型,客户端可以通过 URL 路径扩展请求这些内容类型。 禁用后缀模式匹配并使用路径扩展进行内容协商 降低风险,但不足以防止 RFD 攻击。spring-doc.cadn.net.cn

为了防止 RFD 攻击,在渲染响应正文之前, Spring MVC 会添加一个Content-Disposition:inline;filename=f.txt标头以建议固定且安全的下载 文件。仅当 URL 路径包含的文件扩展名既不是 允许作为 SAFE 或明确注册内容协商。但是,它可以 当 URL 直接输入到浏览器中时,可能会产生副作用。spring-doc.cadn.net.cn

默认情况下,许多常见的路径扩展名都是安全的。具有自定义HttpMessageConverter实现可以显式地为内容注册文件扩展名 negotiation 以避免出现Content-Disposition标头。 请参阅内容类型spring-doc.cadn.net.cn

有关其他信息,请参阅 CVE-2015-5211 与 RFD 相关的建议。spring-doc.cadn.net.cn

易耗品介质类型

您可以根据Content-Type请求中, 如下例所示:spring-doc.cadn.net.cn

Java
@PostMapping(path = "/pets", consumes = "application/json") (1)
public void addPet(@RequestBody Pet pet) {
    // ...
}
1 使用consumes属性按内容类型缩小映射范围。
Kotlin
@PostMapping("/pets", consumes = ["application/json"]) (1)
fun addPet(@RequestBody pet: Pet) {
    // ...
}
1 使用consumes属性按内容类型缩小映射范围。

consumesattribute 还支持否定表达式 — 例如!text/plain指任何 内容类型不是text/plain.spring-doc.cadn.net.cn

您可以声明一个共享的consumes属性。与大多数其他 request-mapping 属性,但是,当在类级别使用时,方法级别的consumes属性 覆盖而不是扩展类级声明。spring-doc.cadn.net.cn

MediaType为常用媒体类型提供常量,例如APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE.
可生产的培养基类型

您可以根据Acceptrequest 标头和 控制器方法生成的内容类型,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping(path = "/pets/{petId}", produces = "application/json") (1)
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
1 使用produces属性按内容类型缩小映射范围。
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"]) (1)
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
    // ...
}
1 使用produces属性按内容类型缩小映射范围。

媒体类型可以指定字符集。支持否定表达式 — 例如!text/plain指除 “text/plain” 之外的任何内容类型。spring-doc.cadn.net.cn

您可以声明一个共享的produces属性。与大多数其他 request-mapping 属性,但是,当在类级别使用时,方法级别的produces属性 覆盖而不是扩展类级声明。spring-doc.cadn.net.cn

MediaType为常用媒体类型提供常量,例如APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE.
参数、标头

您可以根据请求参数条件缩小请求映射的范围。您可以测试 存在请求参数 (myParam),如果缺少 (!myParam) 或 特定值 (myParam=myValue).以下示例显示如何测试特定值:spring-doc.cadn.net.cn

Java
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1 测试myParam等于myValue.
Kotlin
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
    // ...
}
1 测试myParam等于myValue.

您还可以将 SAME 与请求标头条件一起使用,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1 测试myHeader等于myValue.
Kotlin
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
    // ...
}
您可以匹配Content-TypeAccept使用 headers 条件,但最好改用 consumesproduces
HTTP 头, 选项

@GetMapping(以及@RequestMapping(method=HttpMethod.GET)) 支持 HTTP HEAD transparently 进行请求映射。控制器方法不需要更改。 响应包装器,应用于javax.servlet.http.HttpServlet,请确保Content-Lengthheader 设置为写入的字节数(不实际写入响应)。spring-doc.cadn.net.cn

@GetMapping(以及@RequestMapping(method=HttpMethod.GET)) 隐式映射到 并支持 HTTP HEAD。HTTP HEAD 请求的处理方式与它是 HTTP GET 一样,但 而不是写入正文,而是计算字节数,并且Content-Lengthheader 的spring-doc.cadn.net.cn

默认情况下,HTTP OPTIONS 是通过设置Allow响应标头的 HTTP 列表 所有@RequestMapping方法。spring-doc.cadn.net.cn

对于@RequestMapping如果没有 HTTP 方法声明,则Allowheader 设置为GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS.控制器方法应始终声明 支持的 HTTP 方法(例如,通过使用特定于 HTTP 方法的变体):@GetMapping,@PostMapping等)。spring-doc.cadn.net.cn

您可以显式映射@RequestMapping方法更改为 HTTP HEAD 和 HTTP OPTIONS,但 在常见情况下不是必需的。spring-doc.cadn.net.cn

自定义注释

Spring MVC 支持使用组合注释进行请求映射。这些注释本身是元注释的@RequestMapping并组合以重新声明@RequestMapping具有更狭窄、更具体目的的属性。spring-doc.cadn.net.cn

@GetMapping,@PostMapping,@PutMapping,@DeleteMapping@PatchMapping是 组合注释的示例。提供这些 API 是因为可以说,大多数 控制器方法应该映射到特定的 HTTP 方法,而不是使用@RequestMapping, 默认情况下,它与所有 HTTP 方法匹配。如果你需要一个组合的示例 annotations 中,看看这些是怎么声明的。spring-doc.cadn.net.cn

Spring MVC 还支持具有自定义请求匹配的自定义请求映射属性 逻辑。这是一个更高级的选项,需要子类化RequestMappingHandlerMapping并覆盖getCustomMethodCondition方法,其中 您可以检查 custom 属性并返回您自己的RequestCondition.spring-doc.cadn.net.cn

显式注册

您可以通过编程方式注册处理程序方法,这些方法可用于动态 registrations 或高级情况,例如同一处理程序的不同实例 在不同的 URL 下。以下示例注册一个处理程序方法:spring-doc.cadn.net.cn

Java
@Configuration
public class MyConfig {

    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) (1)
            throws NoSuchMethodException {

        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); (2)

        Method method = UserHandler.class.getMethod("getUser", Long.class); (3)

        mapping.registerMapping(info, handler, method); (4)
    }
}
1 注入 target handler 和 controller 的处理程序 Map。
2 准备请求映射元数据。
3 获取处理程序方法。
4 添加注册。
Kotlin
@Configuration
class MyConfig {

    @Autowired
    fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { (1)
        val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build() (2)
        val method = UserHandler::class.java.getMethod("getUser", Long::class.java) (3)
        mapping.registerMapping(info, handler, method) (4)
    }
}
1 注入 target handler 和 controller 的处理程序 Map。
2 准备请求映射元数据。
3 获取处理程序方法。
4 添加注册。

1.3.3. 处理程序方法

@RequestMapping处理程序方法具有灵活的签名,可以从一系列 支持的控制器方法参数和返回值。spring-doc.cadn.net.cn

方法参数

下表描述了支持的控制器方法参数。不支持响应式类型 对于任何参数。spring-doc.cadn.net.cn

JDK 8 的java.util.Optional支持作为方法参数与 具有required属性(例如@RequestParam,@RequestHeader, 和其他)并且等效于required=false.spring-doc.cadn.net.cn

控制器方法参数 描述

WebRequest,NativeWebRequestspring-doc.cadn.net.cn

对请求参数以及请求和会话属性的通用访问,无需直接 使用 Servlet API。spring-doc.cadn.net.cn

javax.servlet.ServletRequest,javax.servlet.ServletResponsespring-doc.cadn.net.cn

选择任何特定的请求或响应类型,例如,ServletRequest,HttpServletRequest, 或 Spring 的MultipartRequest,MultipartHttpServletRequest.spring-doc.cadn.net.cn

javax.servlet.http.HttpSessionspring-doc.cadn.net.cn

强制会话的存在。因此,这样的论点从来都不是null. 请注意,会话访问不是线程安全的。考虑设置RequestMappingHandlerAdapter实例的synchronizeOnSessionflag 设置为true如果多个 允许请求并发访问会话。spring-doc.cadn.net.cn

javax.servlet.http.PushBuilderspring-doc.cadn.net.cn

用于编程 HTTP/2 资源推送的 Servlet 4.0 推送生成器 API。 请注意,根据 Servlet 规范,注入的PushBuilder如果客户端 不支持该 HTTP/2 功能。spring-doc.cadn.net.cn

java.security.Principalspring-doc.cadn.net.cn

当前经过身份验证的用户 — 可能是特定的Principalimplementation 类(如果已知)。spring-doc.cadn.net.cn

请注意,如果为了允许自定义解析程序解析此参数而对其进行批注,则不会预先解析此参数 在通过HttpServletRequest#getUserPrincipal. 例如,Spring SecurityAuthentication实现Principal并将通过以下方式注入HttpServletRequest#getUserPrincipal,除非它还使用@AuthenticationPrincipal在这种情况下,它 由自定义 Spring Security 解析器通过Authentication#getPrincipal.spring-doc.cadn.net.cn

HttpMethodspring-doc.cadn.net.cn

请求的 HTTP 方法。spring-doc.cadn.net.cn

java.util.Localespring-doc.cadn.net.cn

当前请求区域设置,由最具体的LocaleResolveravailable (在 effect 中,配置的LocaleResolverLocaleContextResolver).spring-doc.cadn.net.cn

java.util.TimeZone + java.time.ZoneIdspring-doc.cadn.net.cn

与当前请求关联的时区,由LocaleContextResolver.spring-doc.cadn.net.cn

java.io.InputStream,java.io.Readerspring-doc.cadn.net.cn

用于访问 Servlet API 公开的原始请求正文。spring-doc.cadn.net.cn

java.io.OutputStream,java.io.Writerspring-doc.cadn.net.cn

用于访问 Servlet API 公开的原始响应正文。spring-doc.cadn.net.cn

@PathVariablespring-doc.cadn.net.cn

用于访问 URI 模板变量。请参阅 URI 模式spring-doc.cadn.net.cn

@MatrixVariablespring-doc.cadn.net.cn

用于访问 URI 路径段中的名称-值对。请参见矩阵变量spring-doc.cadn.net.cn

@RequestParamspring-doc.cadn.net.cn

用于访问 Servlet 请求参数,包括 multipart 文件。参数值 转换为声明的方法参数类型。看@RequestParam也 替换为 Multipartspring-doc.cadn.net.cn

请注意,使用@RequestParam对于简单参数值是可选的。 请参阅本表末尾的“任何其他参数”。spring-doc.cadn.net.cn

@RequestHeaderspring-doc.cadn.net.cn

用于访问请求标头。标头值将转换为声明的方法参数 类型。看@RequestHeader.spring-doc.cadn.net.cn

@CookieValuespring-doc.cadn.net.cn

用于访问 Cookie。Cookie 值将转换为声明的方法参数 类型。看@CookieValue.spring-doc.cadn.net.cn

@RequestBodyspring-doc.cadn.net.cn

用于访问 HTTP 请求正文。正文内容被转换为声明的方法 参数类型HttpMessageConverter实现。看@RequestBody.spring-doc.cadn.net.cn

HttpEntity<B>spring-doc.cadn.net.cn

用于访问请求标头和正文。body 使用HttpMessageConverter. 请参阅 HttpEntityspring-doc.cadn.net.cn

@RequestPartspring-doc.cadn.net.cn

要访问multipart/form-datarequest, 转换部件的主体 替换为HttpMessageConverter.请参见 Multipartspring-doc.cadn.net.cn

java.util.Map,org.springframework.ui.Model,org.springframework.ui.ModelMapspring-doc.cadn.net.cn

用于访问在 HTML 控制器中使用并作为模板公开的模型 视图渲染的一部分。spring-doc.cadn.net.cn

RedirectAttributesspring-doc.cadn.net.cn

指定要在重定向时使用的属性(即,要附加到查询中) string) 和 flash 属性临时存储,直到重定向后的请求。 请参阅重定向属性Flash 属性spring-doc.cadn.net.cn

@ModelAttributespring-doc.cadn.net.cn

要访问模型中的现有属性(如果不存在,则实例化),请使用 应用了数据绑定和验证。看@ModelAttribute以及 ModelDataBinder.spring-doc.cadn.net.cn

请注意,使用@ModelAttribute是可选的(例如,设置其属性)。 请参阅此表末尾的“任何其他参数”。spring-doc.cadn.net.cn

Errors,BindingResultspring-doc.cadn.net.cn

用于访问命令对象的验证和数据绑定中的错误 (即@ModelAttributeargument) 或验证@RequestBody@RequestPart参数。您必须声明一个ErrorsBindingResult论点 紧跟在 validated method 参数之后。spring-doc.cadn.net.cn

SessionStatus+ 类级别@SessionAttributesspring-doc.cadn.net.cn

用于将表单处理标记为完成,从而触发会话属性的清理 通过类级别@SessionAttributes注解。看@SessionAttributes了解更多详情。spring-doc.cadn.net.cn

UriComponentsBuilderspring-doc.cadn.net.cn

用于准备相对于当前请求的主机、端口、方案、上下文路径和 Servlet 映射的 Literal 部分。请参阅 URI 链接spring-doc.cadn.net.cn

@SessionAttributespring-doc.cadn.net.cn

用于访问任何 session 属性,与存储在 session 中的 model 属性相反 由于类级别@SessionAttributes声明。看@SessionAttribute了解更多详情。spring-doc.cadn.net.cn

@RequestAttributespring-doc.cadn.net.cn

用于访问请求属性。看@RequestAttribute了解更多详情。spring-doc.cadn.net.cn

任何其他参数spring-doc.cadn.net.cn

如果方法参数与此表中的任何先前值不匹配,并且 简单类型(由 BeanUtils#isSimpleProperty 确定), 它解析为@RequestParam.否则,它将解析为@ModelAttribute.spring-doc.cadn.net.cn

返回值

下表描述了支持的控制器方法返回值。响应式类型是 支持所有返回值。spring-doc.cadn.net.cn

控制器方法返回值 描述

@ResponseBodyspring-doc.cadn.net.cn

返回值通过HttpMessageConverter实现并写入 响应。看@ResponseBody.spring-doc.cadn.net.cn

HttpEntity<B>,ResponseEntity<B>spring-doc.cadn.net.cn

需要转换指定完整响应(包括 HTTP 头和正文)的返回值 通过HttpMessageConverterimplementations 并写入响应。 请参阅 ResponseEntityspring-doc.cadn.net.cn

HttpHeadersspring-doc.cadn.net.cn

用于返回带有标头且没有正文的响应。spring-doc.cadn.net.cn

Stringspring-doc.cadn.net.cn

要解析的视图名称ViewResolver实现并与隐式 model — 通过命令对象确定,以及@ModelAttribute方法。处理程序 方法还可以通过声明Model论点 (请参阅 显式注册)。spring-doc.cadn.net.cn

Viewspring-doc.cadn.net.cn

一个View实例,用于与隐式模型一起进行渲染 — determined 通过 Command Objects 和@ModelAttribute方法。handler 方法还可以 通过声明Model论点 (请参阅 显式注册)。spring-doc.cadn.net.cn

java.util.Map,org.springframework.ui.Modelspring-doc.cadn.net.cn

要添加到隐式模型的属性,其中视图名称是隐式确定的 通过RequestToViewNameTranslator.spring-doc.cadn.net.cn

@ModelAttributespring-doc.cadn.net.cn

要添加到模型中的属性,视图名称通过 一个RequestToViewNameTranslator.spring-doc.cadn.net.cn

请注意,@ModelAttribute是可选的。请参阅 末尾的 “任何其他返回值” 这个表。spring-doc.cadn.net.cn

ModelAndView对象spring-doc.cadn.net.cn

要使用的 view 和 model 属性,以及响应状态(可选)。spring-doc.cadn.net.cn

voidspring-doc.cadn.net.cn

具有void返回类型(或null返回值)被视为具有 处理响应时,如果它还具有ServletResponseOutputStream参数或 一@ResponseStatus注解。如果控制器已发出正数ETaglastModified时间戳检查(详见 控制器)。spring-doc.cadn.net.cn

如果以上都不是真的,则void返回类型还可以指示 “无响应正文” REST 控制器或 HTML 控制器的默认视图名称选择。spring-doc.cadn.net.cn

DeferredResult<V>spring-doc.cadn.net.cn

从任何线程异步生成上述任何返回值 — 例如,作为 某些事件或回调的结果。请参阅 异步请求DeferredResult.spring-doc.cadn.net.cn

Callable<V>spring-doc.cadn.net.cn

在 Spring MVC 管理的线程中异步生成上述任何返回值。 请参阅 异步请求Callable.spring-doc.cadn.net.cn

ListenableFuture<V>,java.util.concurrent.CompletionStage<V>,java.util.concurrent.CompletableFuture<V>spring-doc.cadn.net.cn

替代DeferredResult,以便于此使用(例如,当底层服务 返回其中一个)。spring-doc.cadn.net.cn

ResponseBodyEmitter,SseEmitterspring-doc.cadn.net.cn

异步发出对象流,以写入响应HttpMessageConverter实现。还支持作为ResponseEntity. 请参阅 异步请求HTTP 流式处理spring-doc.cadn.net.cn

StreamingResponseBodyspring-doc.cadn.net.cn

写入响应OutputStream异步。还支持作为ResponseEntity.请参阅 异步请求HTTP 流式处理spring-doc.cadn.net.cn

通过ReactiveAdapterRegistryspring-doc.cadn.net.cn

单个值类型,例如Mono等同于返回DeferredResult. 多值类型,例如Flux,可能会被视为流,具体取决于请求的 media 类型,例如 “text/event-stream”、“application/json+stream” 或其他 收集到 List 并呈现为单个值。请参阅 异步请求反应类型spring-doc.cadn.net.cn

其他返回值spring-doc.cadn.net.cn

如果返回值以任何其他方式保持未解析状态,则将其视为模型 属性,除非它是由 BeanUtils#isSimpleProperty 确定的简单类型, 在这种情况下,它仍然未解决。spring-doc.cadn.net.cn

类型转换

一些带注释的控制器方法参数,它们表示String-基于请求的输入(例如@RequestParam,@RequestHeader,@PathVariable,@MatrixVariable@CookieValue) 如果参数声明为String.spring-doc.cadn.net.cn

对于此类情况,将根据配置的转换器自动应用类型转换。 默认情况下,简单类型 (int,long,Date等)支持。您可以自定义 类型转换WebDataBinder(参见DataBinder) 或通过注册Formatters使用FormattingConversionService. 参见 Spring Field Formattingspring-doc.cadn.net.cn

类型转换中的一个实际问题是空 String 源值的处理。 如果该值变为null作为类型转换的结果。 这可能是这种情况Long,UUID和其他目标类型。如果要允许null要注入,请使用requiredflag 的 Token,或声明 参数设置为@Nullable.spring-doc.cadn.net.cn

从 5.3 开始,即使在类型转换之后,也会强制使用非 null 参数。如果您的处理程序 方法也打算接受 null 值,请将您的参数声明为@Nullable或将其标记为required=false在相应的@RequestParam等注释。这是 针对 5.3 升级中遇到的回归的最佳实践和推荐解决方案。spring-doc.cadn.net.cn

或者,您可以专门处理例如生成的MissingPathVariableException在必需的情况下@PathVariable.转换后的 null 值将被视为 一个空的原始值,因此相应的Missing…​Exception变体。spring-doc.cadn.net.cn

矩阵变量

RFC 3986 在 路径段。在 Spring MVC 中,我们根据 Tim Berners-Lee 的“旧帖子”将这些变量称为“矩阵变量”,但它们 也可以称为 URI 路径参数。spring-doc.cadn.net.cn

矩阵变量可以出现在任何路径段中,每个变量用分号和 用逗号分隔的多个值(例如/cars;color=red,green;year=2012).倍数 值也可以通过重复的变量名称来指定(例如color=red;color=green;color=blue).spring-doc.cadn.net.cn

如果 URL 应包含矩阵变量,则控制器的请求映射 方法必须使用 URI 变量来掩盖该变量内容,并确保请求可以 成功匹配,与矩阵变量顺序和存在无关。 以下示例使用 matrix 变量:spring-doc.cadn.net.cn

Java
// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}
Kotlin
// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
fun findPet(@PathVariable petId: String, @MatrixVariable q: Int) {

    // petId == 42
    // q == 11
}

鉴于所有路径段都可能包含矩阵变量,您有时可能需要 消除 matrix 变量预期位于哪个 path 变量的歧义。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}
Kotlin
// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
        @MatrixVariable(name = "q", pathVar = "ownerId") q1: Int,
        @MatrixVariable(name = "q", pathVar = "petId") q2: Int) {

    // q1 == 11
    // q2 == 22
}

矩阵变量可以定义为可选变量,并指定默认值,如 以下示例显示:spring-doc.cadn.net.cn

Java
// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}
Kotlin
// GET /pets/42

@GetMapping("/pets/{petId}")
fun findPet(@MatrixVariable(required = false, defaultValue = "1") q: Int) {

    // q == 1
}

要获取所有矩阵变量,您可以使用MultiValueMap,如下例所示:spring-doc.cadn.net.cn

Java
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}
Kotlin
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
        @MatrixVariable matrixVars: MultiValueMap<String, String>,
        @MatrixVariable(pathVar="petId") petMatrixVars: MultiValueMap<String, String>) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

请注意,您需要启用矩阵变量的使用。在 MVC Java 配置中, 您需要设置UrlPathHelperremoveSemicolonContent=false通过 Path Matching。在 MVC XML 命名空间中,您可以设置<mvc:annotation-driven enable-matrix-variables="true"/>.spring-doc.cadn.net.cn

@RequestParam

您可以使用@RequestParam注解绑定 Servlet 请求参数(即 查询参数或表单数据)添加到控制器中的方法参数。spring-doc.cadn.net.cn

以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { (1)
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}
1 @RequestParam绑定petId.
Kotlin
import org.springframework.ui.set

@Controller
@RequestMapping("/pets")
class EditPetForm {

    // ...

    @GetMapping
    fun setupForm(@RequestParam("petId") petId: Int, model: Model): String { (1)
        val pet = this.clinic.loadPet(petId);
        model["pet"] = pet
        return "petForm"
    }

    // ...

}
1 @RequestParam绑定petId.

默认情况下,使用此 Comments 的方法参数是必需的,但您可以指定 method 参数是可选的,只需将@RequestParam注解的requiredflag 设置为false或者使用java.util.Optional包装纸。spring-doc.cadn.net.cn

如果目标方法参数 type 不是 type,则会自动应用类型转换String.请参阅类型转换spring-doc.cadn.net.cn

将参数类型声明为数组或列表允许解析多个参数 值。spring-doc.cadn.net.cn

@RequestParam注解声明为Map<String, String>MultiValueMap<String, String>,没有在注解中指定参数名称, 然后,映射中填充每个给定参数名称的请求参数值。spring-doc.cadn.net.cn

请注意,使用@RequestParam是可选的(例如,设置其属性)。 默认情况下,任何作为简单值类型的参数(由 BeanUtils#isSimpleProperty 确定) 并且不由任何其他参数解析程序解析,则被视为已批注 跟@RequestParam.spring-doc.cadn.net.cn

@RequestHeader

您可以使用@RequestHeader注解将请求标头绑定到 控制器。spring-doc.cadn.net.cn

请考虑以下带有 headers 的请求:spring-doc.cadn.net.cn

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

以下示例获取Accept-EncodingKeep-Alive头:spring-doc.cadn.net.cn

Java
@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, (1)
        @RequestHeader("Keep-Alive") long keepAlive) { (2)
    //...
}
1 获取Accept-Encoding页眉。
2 获取Keep-Alive页眉。
Kotlin
@GetMapping("/demo")
fun handle(
        @RequestHeader("Accept-Encoding") encoding: String, (1)
        @RequestHeader("Keep-Alive") keepAlive: Long) { (2)
    //...
}
1 获取Accept-Encoding页眉。
2 获取Keep-Alive页眉。

如果目标方法参数类型不是String,则会自动应用类型转换。请参阅类型转换spring-doc.cadn.net.cn

@RequestHeader注解用于Map<String, String>,MultiValueMap<String, String>HttpHeaders参数,则 Map 会被填充 替换为所有标头值。spring-doc.cadn.net.cn

内置支持可用于将逗号分隔的字符串转换为 字符串的数组或集合,或者类型转换系统已知的其他类型的集合。为 example,一个带有@RequestHeader("Accept")可以是String而且还String[]List<String>.
@CookieValue

您可以使用@CookieValue注解将 HTTP Cookie 的值绑定到方法参数 在控制器中。spring-doc.cadn.net.cn

考虑具有以下 Cookie 的请求:spring-doc.cadn.net.cn

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下示例演示如何获取 Cookie 值:spring-doc.cadn.net.cn

Java
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { (1)
    //...
}
1 获取JSESSIONID饼干。
Kotlin
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) { (1)
    //...
}
1 获取JSESSIONID饼干。

如果目标方法参数类型不是String,则会自动应用类型转换。 请参阅类型转换spring-doc.cadn.net.cn

@ModelAttribute

您可以使用@ModelAttribute对 method 参数的注释来访问属性 模型,如果不存在,则对其进行实例化。model 属性还覆盖了 其名称与字段名称匹配的 HTTP Servlet 请求参数的值。这是指 to 作为数据绑定,并且它使您不必处理解析和转换单个 查询参数和表单字段。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
    // method logic...
}
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String {
    // method logic...
}

Pet上述实例通过以下方式之一获取:spring-doc.cadn.net.cn

使用 @ModelAttribute 方法的一种替代方法 提供它或依赖框架来创建 model 属性,就是要有一个Converter<String, T>以提供实例。这在 model 属性 name 与请求值的名称匹配,例如 path 变量或 request 参数,并且有一个ConverterString设置为 model 属性类型。 在以下示例中,模型属性名称为account与 URI 匹配 path 变量account,并且有一个已注册的Converter<String, Account>哪 可以加载Account从数据存储:spring-doc.cadn.net.cn

Java
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}
Kotlin
@PutMapping("/accounts/{account}")
fun save(@ModelAttribute("account") account: Account): String {
    // ...
}

获取 model 属性实例后,将应用数据绑定。这WebDataBinderclass 匹配 Servlet 请求参数名称(查询参数和表单 fields) 添加到目标上的字段名称Object.匹配字段在 type 之后填充 必要时应用 conversion。有关数据绑定(和验证)的更多信息,请参阅验证。有关自定义数据绑定的更多信息,请参阅DataBinder.spring-doc.cadn.net.cn

数据绑定可能会导致错误。默认情况下,BindException被提升。但是,要检查 对于 controller 方法中的此类错误,您可以添加BindingResult紧接着的参数 到@ModelAttribute,如下例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { (1)
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
1 添加BindingResult@ModelAttribute.
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
1 添加BindingResult@ModelAttribute.

在某些情况下,您可能希望访问没有数据绑定的 model 属性。对于这样的 cases,您可以注入Model放入控制器并直接访问它,或者, 或者,将@ModelAttribute(binding=false),如下例所示:spring-doc.cadn.net.cn

Java
@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) { (1)
    // ...
}
1 设置@ModelAttribute(binding=false).
Kotlin
@ModelAttribute
fun setUpForm(): AccountForm {
    return AccountForm()
}

@ModelAttribute
fun findAccount(@PathVariable accountId: String): Account {
    return accountRepository.findOne(accountId)
}

@PostMapping("update")
fun update(@Valid form: AccountForm, result: BindingResult,
           @ModelAttribute(binding = false) account: Account): String { (1)
    // ...
}
1 设置@ModelAttribute(binding=false).

您可以通过在数据绑定后自动应用验证,方法是添加javax.validation.Valid注解或 Spring 的@Validated注解 (Bean 验证Spring 验证)。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { (1)
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}
1 验证Pet实例。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}

请注意,使用@ModelAttribute是可选的(例如,设置其属性)。 默认情况下,任何不是简单值类型的参数(由 BeanUtils#isSimpleProperty 确定) 并且未由任何其他参数解析 resolver 被视为已批注 跟@ModelAttribute.spring-doc.cadn.net.cn

@SessionAttributes

@SessionAttributes用于将模型属性存储在 HTTP Servlet 会话中 请求。它是一个类型级注释,声明 特定控制器。这通常列出模型属性的名称或 model 属性,这些属性应该透明地存储在会话中以供后续使用 请求访问。spring-doc.cadn.net.cn

以下示例使用@SessionAttributes注解:spring-doc.cadn.net.cn

Java
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {
    // ...
}
1 使用@SessionAttributes注解。
Kotlin
@Controller
@SessionAttributes("pet") (1)
class EditPetForm {
    // ...
}
1 使用@SessionAttributes注解。

在第一个请求中,当名称为pet添加到模型中, 它会自动提升并保存在 HTTP Servlet 会话中。它仍然在那里 直到另一个控制器方法使用SessionStatusmethod 参数清除 storage,如下例所示:spring-doc.cadn.net.cn

Java
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
        status.setComplete(); (2)
        // ...
    }
}
1 存储Pet值。
2 清除Pet值。
Kotlin
@Controller
@SessionAttributes("pet") (1)
class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String {
        if (errors.hasErrors()) {
            // ...
        }
        status.setComplete() (2)
        // ...
    }
}
1 存储Pet值。
2 清除Pet值。
@SessionAttribute

如果您需要访问全局管理的预先存在的会话属性 (即,在控制器外部 — 例如,通过过滤器),并且可能存在也可能不存在, 您可以使用@SessionAttribute方法参数上的注释, 如下例所示:spring-doc.cadn.net.cn

Java
@RequestMapping("/")
public String handle(@SessionAttribute User user) { (1)
    // ...
}
1 使用@SessionAttribute注解。
Kotlin
@RequestMapping("/")
fun handle(@SessionAttribute user: User): String { (1)
    // ...
}

对于需要添加或删除会话属性的使用案例,请考虑注入org.springframework.web.context.request.WebRequestjavax.servlet.http.HttpSession到 controller 方法中。spring-doc.cadn.net.cn

作为控制器的一部分,在会话中临时存储模型属性 workflow,请考虑使用@SessionAttributes@SessionAttributes.spring-doc.cadn.net.cn

@RequestAttribute

@SessionAttribute中,您可以使用@RequestAttributeannotations 添加到 访问之前创建的预先存在的请求属性(例如,由 Servlet 创建的FilterHandlerInterceptor):spring-doc.cadn.net.cn

Java
@GetMapping("/")
public String handle(@RequestAttribute Client client) { (1)
    // ...
}
1 使用@RequestAttribute注解。
Kotlin
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String { (1)
    // ...
}
1 使用@RequestAttribute注解。
重定向属性

默认情况下,所有模型属性都被视为在 重定向 URL。在其余属性中,那些是原始类型或 基元类型的集合或数组会自动附加为查询参数。spring-doc.cadn.net.cn

如果 model 实例是专门为重定向准备的。但是,在 annotated 控制器,则模型可以包含为渲染目的而添加的其他属性(例如 下拉字段值)。为避免此类属性出现在 URL、@RequestMappingmethod 可以声明RedirectAttributes和 使用它来指定要提供给的确切属性RedirectView.如果方法 重定向时,会RedirectAttributes被使用。否则,将 model 的spring-doc.cadn.net.cn

RequestMappingHandlerAdapter提供了一个名为ignoreDefaultModelOnRedirect,可用于指示默认Model如果控制器方法重定向,则永远不应使用。相反,控制器 method 应声明一个RedirectAttributes或者,如果它没有这样做, 不应将任何属性传递给RedirectView.MVC 命名空间和 MVC Java 配置将此标志设置为false,以保持向后兼容性。 但是,对于新应用程序,我们建议将其设置为true.spring-doc.cadn.net.cn

请注意,当前请求中的 URI 模板变量是自动创建的 在展开重定向 URL 时可用,并且您不需要显式添加它们 通过ModelRedirectAttributes.以下示例显示如何定义重定向:spring-doc.cadn.net.cn

Java
@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}
Kotlin
@PostMapping("/files/{path}")
fun upload(...): String {
    // ...
    return "redirect:files/{path}"
}

将数据传递到重定向目标的另一种方法是使用 flash 属性。与 其他重定向属性,则 Flash 属性将保存在 HTTP 会话中(因此,确实如此 未显示在 URL 中)。有关详细信息,请参阅 Flash 属性spring-doc.cadn.net.cn

Flash 属性

Flash 属性为一个请求提供了一种方法来存储旨在在 另一个。这在重定向时最常用 — 例如, Post-Redirect-Get 模式。Flash 属性在 redirect(通常在会话中)提供给请求,以便在 redirect 并立即删除。spring-doc.cadn.net.cn

Spring MVC 有两个主要的抽象来支持 flash 属性。FlashMap已使用 以保留 Flash 属性,而FlashMapManager用于存储、检索和管理FlashMap实例。spring-doc.cadn.net.cn

Flash 属性支持始终处于“打开”状态,不需要显式启用。 但是,如果不使用,则永远不会导致 HTTP 会话创建。在每个请求中,都有一个 “输入”FlashMap替换为从前一个请求传递的属性(如果有)和 “输出”FlashMap替换为保存以供后续请求使用的属性。双FlashMap实例可以通过RequestContextUtils.spring-doc.cadn.net.cn

带注释的控制器通常不需要使用FlashMap径直。相反,一个@RequestMappingmethod 可以接受RedirectAttributes并使用它 为重定向方案添加 Flash 属性。通过RedirectAttributes会自动传播到“输出”FlashMap。同样地 重定向后,来自 “input” 的属性FlashMap会自动添加到Model提供目标 URL 的控制器。spring-doc.cadn.net.cn

将请求与 flash 属性匹配

flash 属性的概念存在于许多其他 Web 框架中,并且有时已被证明是 面临并发问题。这是因为,根据定义,flash 属性 将存储到下一个请求。但是,“下一个”请求可能不是 预期接收者,但另一个异步请求(例如,轮询或资源请求), 在这种情况下,flash 属性会过早删除。spring-doc.cadn.net.cn

为了减少此类问题的可能性,RedirectView自动 “stamps”FlashMap实例,其中包含目标重定向 URL 的 path 和 query 参数。在 turn 的FlashMapManager在以下情况下,将该信息与传入请求匹配 它查找 “input”FlashMap.spring-doc.cadn.net.cn

这并不能完全消除并发问题的可能性,但 使用重定向 URL 中已有的信息大大减少了它。 因此,我们建议您主要将 flash 属性用于重定向方案。spring-doc.cadn.net.cn

多部分

MultipartResolver已启用,则 POST 的内容 请求替换为multipart/form-data作为常规请求进行解析和访问 参数。以下示例访问一个常规表单字段和一个已上传的表单字段 文件:spring-doc.cadn.net.cn

Java
@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}
Kotlin
@Controller
class FileUploadController {

    @PostMapping("/form")
    fun handleFormUpload(@RequestParam("name") name: String,
                        @RequestParam("file") file: MultipartFile): String {

        if (!file.isEmpty) {
            val bytes = file.bytes
            // store the bytes somewhere
            return "redirect:uploadSuccess"
        }
        return "redirect:uploadFailure"
    }
}

将参数类型声明为List<MultipartFile>允许解析多个 文件。spring-doc.cadn.net.cn

@RequestParam注解声明为Map<String, MultipartFile>MultiValueMap<String, MultipartFile>,没有在注解中指定参数名称, 然后,地图中将填充每个给定参数名称的 Multipart 文件。spring-doc.cadn.net.cn

使用 Servlet 3.0 多部分解析,您还可以声明javax.servlet.http.Part而不是 Spring 的MultipartFile作为方法参数或集合值类型。

您还可以将多部分内容用作数据绑定到命令对象的一部分。例如,表单域 和前面示例中的 file 可以是表单对象上的字段, 如下例所示:spring-doc.cadn.net.cn

Java
class MyForm {

    private String name;

    private MultipartFile file;

    // ...
}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        if (!form.getFile().isEmpty()) {
            byte[] bytes = form.getFile().getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}
Kotlin
class MyForm(val name: String, val file: MultipartFile, ...)

@Controller
class FileUploadController {

    @PostMapping("/form")
    fun handleFormUpload(form: MyForm, errors: BindingResult): String {
        if (!form.file.isEmpty) {
            val bytes = form.file.bytes
            // store the bytes somewhere
            return "redirect:uploadSuccess"
        }
        return "redirect:uploadFailure"
    }
}

也可以从 RESTful 服务中的非浏览器客户端提交分段请求 场景。以下示例显示了一个包含 JSON 的文件:spring-doc.cadn.net.cn

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

您可以使用@RequestParam作为String但你会 可能希望从 JSON 反序列化它(类似于@RequestBody).使用@RequestPart注解来访问一个 Multipart,然后在使用 HttpMessageConverter 转换它之后访问它:spring-doc.cadn.net.cn

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {
    // ...
}
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData,
        @RequestPart("file-data") file: MultipartFile): String {
    // ...
}

您可以使用@RequestPartjavax.validation.Valid或使用 Spring 的@Validated注解,这两者都会导致应用 Standard Bean Validation。 默认情况下,验证错误会导致MethodArgumentNotValidException,该 转换为 400 (BAD_REQUEST) 响应。或者,您可以在本地处理验证错误 在控制器中,通过ErrorsBindingResult论点 如下例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
        BindingResult result) {
    // ...
}
Kotlin
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData,
        result: BindingResult): String {
    // ...
}
@RequestBody

您可以使用@RequestBody注解来读取请求正文并将其反序列化为Object通过HttpMessageConverter. 以下示例使用@RequestBody论点:spring-doc.cadn.net.cn

Java
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}
Kotlin
@PostMapping("/accounts")
fun handle(@RequestBody account: Account) {
    // ...
}

您可以使用 MVC ConfigMessage Converters 选项来 配置或自定义消息转换。spring-doc.cadn.net.cn

您可以使用@RequestBodyjavax.validation.Valid或 Spring 的@Validated注解,这两者都会导致应用 Standard Bean Validation。 默认情况下,验证错误会导致MethodArgumentNotValidException,该 转换为 400 (BAD_REQUEST) 响应。或者,您可以在本地处理验证错误 在控制器中,通过ErrorsBindingResult论点 如下例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
    // ...
}
Kotlin
@PostMapping("/accounts")
fun handle(@Valid @RequestBody account: Account, result: BindingResult) {
    // ...
}
HttpEntity

HttpEntity与使用@RequestBody但基于 Container 对象。下面的清单显示了一个示例:spring-doc.cadn.net.cn

Java
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}
Kotlin
@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
    // ...
}
@ResponseBody

您可以使用@ResponseBody对方法进行注释以序列化返回 传递给响应正文。 下面的清单显示了一个示例:spring-doc.cadn.net.cn

Java
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}
Kotlin
@GetMapping("/accounts/{id}")
@ResponseBody
fun handle(): Account {
    // ...
}

@ResponseBody在类级别也受支持,在这种情况下,它由 所有控制器方法。这是@RestController,仅此而已 比标有@Controller@ResponseBody.spring-doc.cadn.net.cn

您可以使用@ResponseBody替换为响应式类型。 有关更多详细信息,请参阅 异步请求反应类型spring-doc.cadn.net.cn

您可以使用 MVC ConfigMessage Converters 选项来 配置或自定义消息转换。spring-doc.cadn.net.cn

您可以组合@ResponseBody方法。 有关详细信息,请参阅 Jackson JSONspring-doc.cadn.net.cn

ResponseEntity

ResponseEntity就像@ResponseBody但有 status 和 headers。例如:spring-doc.cadn.net.cn

Java
@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).body(body);
}
Kotlin
@GetMapping("/something")
fun handle(): ResponseEntity<String> {
    val body = ...
    val etag = ...
    return ResponseEntity.ok().eTag(etag).build(body)
}

Spring MVC 支持使用单值响应式类型来生成ResponseEntity异步和/或单值和多值响应式 body 的类型。这允许以下类型的异步响应:spring-doc.cadn.net.cn

  • ResponseEntity<Mono<T>>ResponseEntity<Flux<T>>将响应状态设为 标头,而正文在稍后异步提供。 用Mono如果主体由 0..1 值组成,或者Flux如果它可以产生多个值。spring-doc.cadn.net.cn

  • Mono<ResponseEntity<T>>提供所有三个 — 响应状态、标头和正文, 异步的。这允许响应状态和标头发生变化 取决于异步请求处理的结果。spring-doc.cadn.net.cn

Jackson JSON

Spring 提供对 Jackson JSON 库的支持。spring-doc.cadn.net.cn

JSON 视图

Spring MVC 为 Jackson 的序列化视图提供了内置支持, ,它们只允许在Object.若要将其与@ResponseBodyResponseEntitycontroller 方法,你可以使用 Jackson 的@JsonView注解来激活序列化视图类,如下例所示:spring-doc.cadn.net.cn

Java
@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}
Kotlin
@RestController
class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView::class)
    fun getUser() = User("eric", "7!jd#h23")
}

class User(
        @JsonView(WithoutPasswordView::class) val username: String,
        @JsonView(WithPasswordView::class) val password: String) {

    interface WithoutPasswordView
    interface WithPasswordView : WithoutPasswordView
}
@JsonView允许视图类数组,但您只能为每个 controller 方法。如果需要激活多个视图,可以使用复合界面。

如果您想以编程方式执行上述作,而不是声明@JsonView注解 将返回值用MappingJacksonValue并使用它来提供序列化视图:spring-doc.cadn.net.cn

Java
@RestController
public class UserController {

    @GetMapping("/user")
    public MappingJacksonValue getUser() {
        User user = new User("eric", "7!jd#h23");
        MappingJacksonValue value = new MappingJacksonValue(user);
        value.setSerializationView(User.WithoutPasswordView.class);
        return value;
    }
}
Kotlin
@RestController
class UserController {

    @GetMapping("/user")
    fun getUser(): MappingJacksonValue {
        val value = MappingJacksonValue(User("eric", "7!jd#h23"))
        value.serializationView = User.WithoutPasswordView::class.java
        return value
    }
}

对于依赖视图分辨率的控制器,您可以添加序列化视图类 添加到模型中,如下例所示:spring-doc.cadn.net.cn

Java
@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}
Kotlin
import org.springframework.ui.set

@Controller
class UserController : AbstractController() {

    @GetMapping("/user")
    fun getUser(model: Model): String {
        model["user"] = User("eric", "7!jd#h23")
        model[JsonView::class.qualifiedName] = User.WithoutPasswordView::class.java
        return "userView"
    }
}

1.3.4. 模型

您可以使用@ModelAttribute注解:spring-doc.cadn.net.cn

本节讨论@ModelAttributemethods — 前面列表中的第二项。 控制器可以具有任意数量的@ModelAttribute方法。所有这些方法都是 之前调用@RequestMapping方法。一个@ModelAttribute方法也可以通过@ControllerAdvice.有关更多详细信息,请参阅 Controller Advice 部分。spring-doc.cadn.net.cn

@ModelAttribute方法具有灵活的方法签名。它们支持许多相同的 arguments 设置为@RequestMapping方法,除了@ModelAttribute本身或任何事物 与请求正文相关。spring-doc.cadn.net.cn

以下示例显示了@ModelAttribute方法:spring-doc.cadn.net.cn

Java
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // add more ...
}
Kotlin
@ModelAttribute
fun populateModel(@RequestParam number: String, model: Model) {
    model.addAttribute(accountRepository.findAccount(number))
    // add more ...
}

以下示例仅添加一个属性:spring-doc.cadn.net.cn

Java
@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}
Kotlin
@ModelAttribute
fun addAccount(@RequestParam number: String): Account {
    return accountRepository.findAccount(number)
}
如果未明确指定名称,则根据Object类型,如 Javadoc 中的Conventions. 您始终可以使用重载的addAttributemethod 或 通过name属性@ModelAttribute(对于返回值)。

您还可以使用@ModelAttribute作为@RequestMapping方法 在这种情况下,@RequestMappingmethod 被解释为模型 属性。这通常不是必需的,因为它是 HTML 控制器中的默认行为。 除非返回值是String否则,该名称将被解释为 View Name。@ModelAttribute还可以自定义 Model 属性名称,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}
Kotlin
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
fun handle(): Account {
    // ...
    return account
}

1.3.5.DataBinder

@Controller@ControllerAdvice类可以具有@InitBinder方法 初始化 的实例WebDataBinder,而这些 API 又可以:spring-doc.cadn.net.cn

@InitBinder方法可以注册特定于控制器的java.beans.PropertyEditor或 SpringConverterFormatter组件。此外,您还可以使用 MVC 配置来注册ConverterFormatter全局共享FormattingConversionService.spring-doc.cadn.net.cn

@InitBinder方法支持许多相同的参数@RequestMapping方法 do 的@ModelAttribute(command 对象) 参数。通常,它们被声明 替换为WebDataBinder参数(用于注册)和void返回值。 下面的清单显示了一个示例:spring-doc.cadn.net.cn

Java
@Controller
public class FormController {

    @InitBinder (1)
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}
1 定义@InitBinder方法。
Kotlin
@Controller
class FormController {

    @InitBinder (1)
    fun initBinder(binder: WebDataBinder) {
        val dateFormat = SimpleDateFormat("yyyy-MM-dd")
        dateFormat.isLenient = false
        binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
    }

    // ...
}
1 定义@InitBinder方法。

或者,当您使用Formatter的设置,通过共享的FormattingConversionService,您可以重复使用相同的方法并注册 控制器特定Formatterimplementations,如下例所示:spring-doc.cadn.net.cn

Java
@Controller
public class FormController {

    @InitBinder (1)
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}
1 定义@InitBinder方法。
Kotlin
@Controller
class FormController {

    @InitBinder (1)
    protected fun initBinder(binder: WebDataBinder) {
        binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
    }

    // ...
}
1 定义@InitBinder方法。
模型设计

在 Web 应用程序的上下文中,数据绑定涉及 HTTP 请求的绑定 parameters ((即表单数据或查询参数) ) 添加到模型对象中的属性,以及 其嵌套对象。spring-doc.cadn.net.cn

public遵循 JavaBeans 命名约定的属性将公开用于数据绑定,例如public String getFirstName()public void setFirstName(String)方法firstName财产。spring-doc.cadn.net.cn

模型对象及其嵌套对象图有时也称为命令对象表单支持对象POJO (Plain Old Java Object)。

默认情况下, Spring 允许绑定到模型对象图中的所有公共属性。 这意味着您需要仔细考虑模型具有哪些公共属性,因为 客户端可以面向任何公共属性路径,甚至是一些不需要的公共属性路径 针对给定的使用案例。spring-doc.cadn.net.cn

例如,给定一个 HTTP 表单数据端点,恶意客户端可能会提供 存在于模型对象图中但不属于 HTML 表单的属性 在浏览器中显示。这可能会导致在模型对象上设置数据,并且任何 的嵌套对象中。spring-doc.cadn.net.cn

推荐的方法是使用仅公开 与表单提交相关的属性。例如,在用于更改 用户的电子邮件地址,则 Model 对象应声明一组最小属性,例如 如下所示ChangeEmailForm.spring-doc.cadn.net.cn

public class ChangeEmailForm {

    private String oldEmailAddress;
    private String newEmailAddress;

    public void setOldEmailAddress(String oldEmailAddress) {
        this.oldEmailAddress = oldEmailAddress;
    }

    public String getOldEmailAddress() {
        return this.oldEmailAddress;
    }

    public void setNewEmailAddress(String newEmailAddress) {
        this.newEmailAddress = newEmailAddress;
    }

    public String getNewEmailAddress() {
        return this.newEmailAddress;
    }

}

如果您不能或不想为每个数据使用专用模型对象 binding 用例中,您必须限制允许进行数据绑定的属性。 理想情况下,您可以通过setAllowedFields()method 开启WebDataBinder.spring-doc.cadn.net.cn

例如,要在应用程序中注册允许的字段模式,您可以实现@InitBinder方法中的@Controller@ControllerAdvice组件,如下所示:spring-doc.cadn.net.cn

@Controller
public class ChangeEmailController {

    @InitBinder
    void initBinder(WebDataBinder binder) {
        binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
    }

    // @RequestMapping methods, etc.

}

除了注册允许的模式外,还可以注册不允许的 字段模式通过setDisallowedFields()method 中DataBinder及其子类。 但请注意,“允许列表”比“拒绝列表”更安全。因此setAllowedFields()应该被青睐setDisallowedFields().spring-doc.cadn.net.cn

请注意,与允许的字段模式匹配区分大小写;鉴于匹配 Against Disallowed 字段模式不区分大小写。此外,匹配 不允许的模式将不被接受,即使它也恰好与 允许列表。spring-doc.cadn.net.cn

正确配置允许和不允许的字段模式非常重要 直接公开域模型以进行数据绑定时。否则,它是一个 安全风险大。spring-doc.cadn.net.cn

此外,强烈建议您不要使用域中的类型 模型(如 JPA 或 Hibernate 实体)作为数据绑定场景中的模型对象。spring-doc.cadn.net.cn

1.3.6. 异常

@Controller@ControllerAdvice类可以具有@ExceptionHandler方法处理来自 Controller 方法的异常,如下例所示:spring-doc.cadn.net.cn

Java
@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}
Kotlin
@Controller
class SimpleController {

    // ...

    @ExceptionHandler
    fun handle(ex: IOException): ResponseEntity<String> {
        // ...
    }
}

该异常可能与正在传播的顶级异常(例如,直接的IOException被抛出)或针对包装器异常中的嵌套原因(例如 一IOException包装在IllegalStateException).从 5.3 开始,这可以匹配 在任意原因水平上,而以前只考虑直接原因。spring-doc.cadn.net.cn

对于匹配的异常类型,最好将目标异常声明为方法参数, 如前面的示例所示。当多个异常方法匹配时,根异常匹配为 通常优先于 Cause 异常匹配。更具体地说,ExceptionDepthComparator用于根据异常从引发的异常类型中对异常的深度对异常进行排序。spring-doc.cadn.net.cn

或者,注释声明可以缩小异常类型的范围以匹配 如下例所示:spring-doc.cadn.net.cn

Java
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
    // ...
}
Kotlin
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handle(ex: IOException): ResponseEntity<String> {
    // ...
}

您甚至可以使用具有非常通用参数签名的特定异常类型列表 如下例所示:spring-doc.cadn.net.cn

Java
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
    // ...
}
Kotlin
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handle(ex: Exception): ResponseEntity<String> {
    // ...
}

根异常匹配和原因异常匹配之间的区别可能令人惊讶。spring-doc.cadn.net.cn

IOException变体,该方法通常使用 实际的FileSystemExceptionRemoteExceptioninstance 作为参数, 由于它们都从IOException.但是,如果有任何此类匹配 exception 在包装器 exception 中传播,该 exception 本身是一个IOException, 传入的 Exception 实例是该包装器 Exception。spring-doc.cadn.net.cn

handle(Exception)变体。这是 always 调用时,在 wrapping 场景中使用 wrapper 异常调用,并且 实际匹配异常ex.getCause()在那种情况下。 传入的异常是实际的FileSystemExceptionRemoteException实例。spring-doc.cadn.net.cn

我们通常建议您在参数签名中尽可能具体。 减少 root 和 cause 异常类型之间不匹配的可能性。 考虑将多重匹配方法拆分为单独的@ExceptionHandler方法,每个方法都通过其签名匹配单个特定的异常类型。spring-doc.cadn.net.cn

在多@ControllerAdvice安排中,我们建议声明您的主根例外 mappings 在@ControllerAdviceprioritizeed 和相应的顺序。虽然 root exception match 优先于 cause,这是在给定 controller 或@ControllerAdvice类。这意味着优先级更高的原因匹配@ControllerAdvicebean 优先于优先级较低的任何匹配项(例如 root)@ControllerAdvice豆。spring-doc.cadn.net.cn

最后但并非最不重要的一点是,一个@ExceptionHandler方法实现可以选择 back 通过以原始形式重新引发给定的异常实例来处理该实例。 这在您只对根级别匹配或 无法静态确定的特定上下文中的匹配项。一个 rethrown exception 通过剩余的解析链传播,就像 给定的@ExceptionHandlermethod 一开始就不匹配。spring-doc.cadn.net.cn

支持@ExceptionHandler方法构建在DispatcherServlet级别、HandlerExceptionResolver 机制。spring-doc.cadn.net.cn

方法参数

@ExceptionHandler方法支持以下参数:spring-doc.cadn.net.cn

Method 参数 描述

异常类型spring-doc.cadn.net.cn

用于访问引发的异常。spring-doc.cadn.net.cn

HandlerMethodspring-doc.cadn.net.cn

用于访问引发异常的 controller 方法。spring-doc.cadn.net.cn

WebRequest,NativeWebRequestspring-doc.cadn.net.cn

对请求参数以及请求和会话属性的通用访问,无需直接 使用 Servlet API。spring-doc.cadn.net.cn

javax.servlet.ServletRequest,javax.servlet.ServletResponsespring-doc.cadn.net.cn

选择任何特定的请求或响应类型(例如ServletRequestHttpServletRequest或 Spring 的MultipartRequestMultipartHttpServletRequest).spring-doc.cadn.net.cn

javax.servlet.http.HttpSessionspring-doc.cadn.net.cn

强制会话的存在。因此,这样的论点从来都不是null.
请注意,会话访问不是线程安全的。考虑设置
RequestMappingHandlerAdapter实例的synchronizeOnSessionflag 设置为true如果多个 允许请求并发访问会话。spring-doc.cadn.net.cn

java.security.Principalspring-doc.cadn.net.cn

当前经过身份验证的用户 — 可能是特定的Principalimplementation 类(如果已知)。spring-doc.cadn.net.cn

HttpMethodspring-doc.cadn.net.cn

请求的 HTTP 方法。spring-doc.cadn.net.cn

java.util.Localespring-doc.cadn.net.cn

当前请求区域设置,由最具体的LocaleResolveravailable — 在 effect 中,配置的LocaleResolverLocaleContextResolver.spring-doc.cadn.net.cn

java.util.TimeZone,java.time.ZoneIdspring-doc.cadn.net.cn

与当前请求关联的时区,由LocaleContextResolver.spring-doc.cadn.net.cn

java.io.OutputStream,java.io.Writerspring-doc.cadn.net.cn

用于访问 Servlet API 公开的原始响应正文。spring-doc.cadn.net.cn

java.util.Map,org.springframework.ui.Model,org.springframework.ui.ModelMapspring-doc.cadn.net.cn

用于访问错误响应的模型。永远是空的。spring-doc.cadn.net.cn

RedirectAttributesspring-doc.cadn.net.cn

指定在重定向时使用的属性 — (即要附加到查询中 string) 和 flash 属性临时存储,直到重定向后的请求。 请参阅重定向属性Flash 属性spring-doc.cadn.net.cn

@SessionAttributespring-doc.cadn.net.cn

对于任何 session 属性的访问,与存储在 session 作为类级别@SessionAttributes声明。 看@SessionAttribute了解更多详情。spring-doc.cadn.net.cn

@RequestAttributespring-doc.cadn.net.cn

用于访问请求属性。看@RequestAttribute了解更多详情。spring-doc.cadn.net.cn

返回值

@ExceptionHandler方法支持以下返回值:spring-doc.cadn.net.cn

返回值 描述

@ResponseBodyspring-doc.cadn.net.cn

返回值通过HttpMessageConverter实例并写入 响应。看@ResponseBody.spring-doc.cadn.net.cn

HttpEntity<B>,ResponseEntity<B>spring-doc.cadn.net.cn

返回值指定完整响应(包括 HTTP 标头和正文) 通过以下方式进行转换HttpMessageConverter实例并写入响应。 请参阅 ResponseEntityspring-doc.cadn.net.cn

Stringspring-doc.cadn.net.cn

要解析的视图名称ViewResolver实现中,并与 隐式模型 — 通过命令对象确定,并且@ModelAttribute方法。 处理程序方法还可以通过声明Model参数(如前所述)。spring-doc.cadn.net.cn

Viewspring-doc.cadn.net.cn

一个View实例,用于与隐式模型一起进行渲染 — determined 通过 Command Objects 和@ModelAttribute方法。handler 方法还可以 通过声明Model参数(前面描述过)。spring-doc.cadn.net.cn

java.util.Map,org.springframework.ui.Modelspring-doc.cadn.net.cn

要添加到隐式模型中且视图名称隐式确定的属性 通过RequestToViewNameTranslator.spring-doc.cadn.net.cn

@ModelAttributespring-doc.cadn.net.cn

要添加到模型中的属性,其视图名称通过 一个RequestToViewNameTranslator.spring-doc.cadn.net.cn

请注意,@ModelAttribute是可选的。请参阅 末尾的 “任何其他返回值” 这个表。spring-doc.cadn.net.cn

ModelAndView对象spring-doc.cadn.net.cn

要使用的 view 和 model 属性,以及响应状态(可选)。spring-doc.cadn.net.cn

voidspring-doc.cadn.net.cn

具有void返回类型(或null返回值)被视为具有 处理响应时,如果它还具有ServletResponseOutputStream参数或 一个@ResponseStatus注解。如果控制器已发出正数ETaglastModified时间戳检查(详见 控制器)。spring-doc.cadn.net.cn

如果以上都不是真的,则void返回类型还可以指示 “无响应正文” REST 控制器或 HTML 控制器的默认视图名称选择。spring-doc.cadn.net.cn

任何其他返回值spring-doc.cadn.net.cn

如果返回值与上述任何内容都不匹配,并且不是简单类型(由 BeanUtils#isSimpleProperty 确定), 默认情况下,它被视为要添加到模型的 model 属性。如果是简单类型, 它仍然没有解决。spring-doc.cadn.net.cn

REST API 异常

REST 服务的一个常见要求是在 响应。Spring 框架不会自动执行此作,因为表示 of error details 是特定于应用程序的。但是,@RestController可以使用@ExceptionHandler方法中带有ResponseEntity返回 值以设置响应的状态和正文。此类方法也可以声明 在@ControllerAdvice类来全局应用它们。spring-doc.cadn.net.cn

在响应中实现全局异常处理且错误详细信息的应用程序 body 应考虑扩展ResponseEntityExceptionHandler, 它为 Spring MVC 引发的异常提供处理,并为 自定义响应正文。要使用此功能,请创建一个ResponseEntityExceptionHandler,用@ControllerAdvice,覆盖 必要的方法,并将其声明为 Spring bean。spring-doc.cadn.net.cn

1.3.7. 控制器建议

@ExceptionHandler,@InitBinder@ModelAttribute方法仅适用于@Controllerclass 或类层次结构,它们在其中声明。相反,如果他们 在@ControllerAdvice@RestControllerAdvice类,然后他们申请 到任何控制器。此外,从 5.3 开始,@ExceptionHandlermethods 中的@ControllerAdvice可用于处理来自任何@Controller或任何其他处理程序。spring-doc.cadn.net.cn

@ControllerAdvice使用@Component因此可以注册为 通过组件扫描的 Spring Bean。@RestControllerAdvice使用@ControllerAdvice@ResponseBody,这意味着@ExceptionHandler方法将返回 值通过响应正文消息转换呈现,而不是通过 HTML 视图呈现。spring-doc.cadn.net.cn

启动时,RequestMappingHandlerMappingExceptionHandlerExceptionResolver检测 controller 建议 bean 并在运行时应用它们。全球@ExceptionHandler方法 从@ControllerAdvice将应用于本地 Import,从@Controller. 相比之下,全球@ModelAttribute@InitBinder方法于 local 方法应用。spring-doc.cadn.net.cn

@ControllerAdviceannotation 具有允许您缩小控制器集的属性 以及它们应用于的处理程序。例如:spring-doc.cadn.net.cn

Java
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
Kotlin
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = [RestController::class])
class ExampleAdvice1

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
class ExampleAdvice2

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class])
class ExampleAdvice3

前面示例中的选择器在运行时进行评估,可能会产生负面影响 性能。请参阅@ControllerAdvicejavadoc 了解更多详情。spring-doc.cadn.net.cn

1.4. 功能端点

Spring Web MVC 包括 WebMvc.fn,这是一个轻量级函数式编程模型,其中函数 用于路由和处理请求,而 Contract 旨在实现不变性。 它是基于 Comments 的编程模型的替代方案,但在其他方面运行 相同的 DispatcherServletspring-doc.cadn.net.cn

1.4.1. 概述

在 WebMvc.fn 中,HTTP 请求使用HandlerFunction:一个函数,该函数采用ServerRequest并返回一个ServerResponse. 请求和响应对象都有不可变的契约,这些契约提供对 JDK 8 友好的 访问 HTTP 请求和响应。HandlerFunction等价于@RequestMapping方法中的 基于注释的编程模型。spring-doc.cadn.net.cn

传入请求被路由到具有RouterFunction:一个函数,该函数 需要ServerRequest并返回一个可选的HandlerFunction(即Optional<HandlerFunction>). 当 router 函数匹配时,将返回一个处理程序函数;否则为空 Optional。RouterFunction等价于@RequestMapping注解,但带有 major 不同之处在于 router 函数不仅提供数据,还提供行为。spring-doc.cadn.net.cn

RouterFunctions.route()提供便于创建路由器的 Router 构建器, 如下例所示:spring-doc.cadn.net.cn

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .build();


public class PersonHandler {

    // ...

    public ServerResponse listPeople(ServerRequest request) {
        // ...
    }

    public ServerResponse createPerson(ServerRequest request) {
        // ...
    }

    public ServerResponse getPerson(ServerRequest request) {
        // ...
    }
}
Kotlin
import org.springframework.web.servlet.function.router

val repository: PersonRepository = ...
val handler = PersonHandler(repository)

val route = router { (1)
    accept(APPLICATION_JSON).nest {
        GET("/person/{id}", handler::getPerson)
        GET("/person", handler::listPeople)
    }
    POST("/person", handler::createPerson)
}


class PersonHandler(private val repository: PersonRepository) {

    // ...

    fun listPeople(request: ServerRequest): ServerResponse {
        // ...
    }

    fun createPerson(request: ServerRequest): ServerResponse {
        // ...
    }

    fun getPerson(request: ServerRequest): ServerResponse {
        // ...
    }
}
1 使用路由器 DSL 创建路由器。

如果您注册了RouterFunction作为 Bean 进行 Bean 处理,例如,通过在@Configuration类中,它将由 Servlet 自动检测,如 运行服务器中所述。spring-doc.cadn.net.cn

1.4.2. HandlerFunction 函数

ServerRequestServerResponse是提供 JDK 8 友好的不可变接口 访问 HTTP 请求和响应,包括标头、正文、方法和状态代码。spring-doc.cadn.net.cn

服务器请求

ServerRequest提供对 HTTP 方法、URI、标头和查询参数的访问, 而对身体的访问是通过body方法。spring-doc.cadn.net.cn

以下示例将请求正文提取为String:spring-doc.cadn.net.cn

Java
String string = request.body(String.class);
Kotlin
val string = request.body<String>()

以下示例将主体提取到List<Person>, 哪里Person对象从序列化形式(如 JSON 或 XML)解码:spring-doc.cadn.net.cn

Java
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
Kotlin
val people = request.body<Person>()

以下示例显示如何访问参数:spring-doc.cadn.net.cn

Java
MultiValueMap<String, String> params = request.params();
Kotlin
val map = request.params()
服务器响应

ServerResponse提供对 HTTP 响应的访问,并且由于它是不可变的,因此您可以使用 一个build方法创建它。您可以使用生成器设置响应状态,以添加响应 headers 或提供正文。以下示例使用 JSON 创建 200 (OK) 响应 内容:spring-doc.cadn.net.cn

Java
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
Kotlin
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)

以下示例显示了如何使用Locationheader 且没有正文:spring-doc.cadn.net.cn

Java
URI location = ...
ServerResponse.created(location).build();
Kotlin
val location: URI = ...
ServerResponse.created(location).build()

您还可以使用异步结果作为正文,采用CompletableFuture,PublisherReactiveAdapterRegistry.例如:spring-doc.cadn.net.cn

Java
Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
Kotlin
val person = webClient.get().retrieve().awaitBody<Person>()
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person)

如果不仅 body 基于 body,而且 status 或 headers 都基于异步类型, 您可以使用 staticasyncmethod 开启ServerResponse哪 接受CompletableFuture<ServerResponse>,Publisher<ServerResponse>或 支持ReactiveAdapterRegistry.例如:spring-doc.cadn.net.cn

Java
Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
  .map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
ServerResponse.async(asyncResponse);

Server-Sent Events 可以通过 静态的ssemethod 开启ServerResponse.该方法提供的生成器 允许您以 JSON 格式发送字符串或其他对象。例如:spring-doc.cadn.net.cn

Java
public RouterFunction<ServerResponse> sse() {
    return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
                // Save the sseBuilder object somewhere..
            }));
}

// In some other thread, sending a String
sseBuilder.send("Hello world");

// Or an object, which will be transformed into JSON
Person person = ...
sseBuilder.send(person);

// Customize the event by using the other methods
sseBuilder.id("42")
        .event("sse event")
        .data(person);

// and done at some point
sseBuilder.complete();
Kotlin
fun sse(): RouterFunction<ServerResponse> = router {
    GET("/sse") { request -> ServerResponse.sse { sseBuilder ->
        // Save the sseBuilder object somewhere..
    }
}

// In some other thread, sending a String
sseBuilder.send("Hello world")

// Or an object, which will be transformed into JSON
val person = ...
sseBuilder.send(person)

// Customize the event by using the other methods
sseBuilder.id("42")
        .event("sse event")
        .data(person)

// and done at some point
sseBuilder.complete()
处理程序类

我们可以将处理程序函数编写为 lambda,如下例所示:spring-doc.cadn.net.cn

Java
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().body("Hello World");
Kotlin
val helloWorld: (ServerRequest) -> ServerResponse =
  { ServerResponse.ok().body("Hello World") }

这很方便,但在应用程序中,我们需要多个函数和多个内联 Lambda 可能会变得混乱。 因此,将相关的处理程序函数一起分组到一个处理程序类中是很有用的,该 具有与@Controller在基于注释的应用程序中。 例如,下面的类公开了一个响应式Person存储 库:spring-doc.cadn.net.cn

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

public class PersonHandler {

    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }

    public ServerResponse listPeople(ServerRequest request) { (1)
        List<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people);
    }

    public ServerResponse createPerson(ServerRequest request) throws Exception { (2)
        Person person = request.body(Person.class);
        repository.savePerson(person);
        return ok().build();
    }

    public ServerResponse getPerson(ServerRequest request) { (3)
        int personId = Integer.parseInt(request.pathVariable("id"));
        Person person = repository.getPerson(personId);
        if (person != null) {
            return ok().contentType(APPLICATION_JSON).body(person);
        }
        else {
            return ServerResponse.notFound().build();
        }
    }

}
1 listPeople是一个处理函数,它返回所有Person对象作为 JSON 的 JSON 格式。
2 createPerson是一个处理程序函数,它将新的Person包含在请求正文中。
3 getPerson是一个处理程序函数,它返回一个 person,由id路径 变量。我们检索该Person并创建 JSON 响应(如果是 发现。如果未找到,我们将返回 404 Not Found 响应。
Kotlin
class PersonHandler(private val repository: PersonRepository) {

    fun listPeople(request: ServerRequest): ServerResponse { (1)
        val people: List<Person> = repository.allPeople()
        return ok().contentType(APPLICATION_JSON).body(people);
    }

    fun createPerson(request: ServerRequest): ServerResponse { (2)
        val person = request.body<Person>()
        repository.savePerson(person)
        return ok().build()
    }

    fun getPerson(request: ServerRequest): ServerResponse { (3)
        val personId = request.pathVariable("id").toInt()
        return repository.getPerson(personId)?.let { ok().contentType(APPLICATION_JSON).body(it) }
                ?: ServerResponse.notFound().build()

    }
}
1 listPeople是一个处理函数,它返回所有Person对象作为 JSON 的 JSON 格式。
2 createPerson是一个处理程序函数,它将新的Person包含在请求正文中。
3 getPerson是一个处理程序函数,它返回一个 person,由id路径 变量。我们检索该Person并创建 JSON 响应(如果是 发现。如果未找到,我们将返回 404 Not Found 响应。
验证

功能性端点可以使用 Spring 的验证工具来 对请求正文应用验证。例如,给定一个自定义的 Spring Validator 实现Person:spring-doc.cadn.net.cn

Java
public class PersonHandler {

    private final Validator validator = new PersonValidator(); (1)

    // ...

    public ServerResponse createPerson(ServerRequest request) {
        Person person = request.body(Person.class);
        validate(person); (2)
        repository.savePerson(person);
        return ok().build();
    }

    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            throw new ServerWebInputException(errors.toString()); (3)
        }
    }
}
1 创造Validator实例。
2 应用验证。
3 引发 400 响应的异常。
Kotlin
class PersonHandler(private val repository: PersonRepository) {

    private val validator = PersonValidator() (1)

    // ...

    fun createPerson(request: ServerRequest): ServerResponse {
        val person = request.body<Person>()
        validate(person) (2)
        repository.savePerson(person)
        return ok().build()
    }

    private fun validate(person: Person) {
        val errors: Errors = BeanPropertyBindingResult(person, "person")
        validator.validate(person, errors)
        if (errors.hasErrors()) {
            throw ServerWebInputException(errors.toString()) (3)
        }
    }
}
1 创造Validator实例。
2 应用验证。
3 引发 400 响应的异常。

处理程序还可以通过创建和注入来使用标准 bean 验证 API (JSR-303) 一个全局Validator实例基于LocalValidatorFactoryBean. 参见 Spring Validationspring-doc.cadn.net.cn

1.4.3.RouterFunction

Router 函数用于将请求路由到相应的HandlerFunction. 通常,您不会自己编写 router 函数,而是在RouterFunctionsUtility 类来创建一个。RouterFunctions.route()(无参数)为您提供用于创建路由器的 Fluent 构建器 函数,而RouterFunctions.route(RequestPredicate, HandlerFunction)提供直接方式 创建路由器。spring-doc.cadn.net.cn

通常建议使用route()builder,因为它提供了 适用于典型映射场景的便捷捷径,无需难以发现 static imports。 例如,router 函数构建器提供了GET(String, HandlerFunction)为 GET 请求创建映射;和POST(String, HandlerFunction)用于 POST。spring-doc.cadn.net.cn

除了基于 HTTP 方法的映射之外,路由构建器还提供了一种引入其他 谓词。 对于每个 HTTP 方法,都有一个重载的变体,它采用RequestPredicate作为 参数,通过该参数可以表示其他约束。spring-doc.cadn.net.cn

谓词

您可以编写自己的RequestPredicate,但RequestPredicatesUtility 类 提供常用的实现,基于请求路径、HTTP 方法、内容类型、 等等。 以下示例使用请求谓词基于Accept页眉:spring-doc.cadn.net.cn

Java
RouterFunction<ServerResponse> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> ServerResponse.ok().body("Hello World")).build();
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    GET("/hello-world", accept(TEXT_PLAIN)) {
        ServerResponse.ok().body("Hello World")
    }
}

您可以使用以下方法将多个请求谓词组合在一起:spring-doc.cadn.net.cn

许多来自RequestPredicates组成。 例如RequestPredicates.GET(String)RequestPredicates.method(HttpMethod)RequestPredicates.path(String). 上面显示的示例还使用了两个请求谓词,因为生成器使用RequestPredicates.GET内部,并使用accept谓语。spring-doc.cadn.net.cn

路线

路由器功能按顺序评估:如果第一个路由不匹配,则 second 被评估,依此类推。 因此,在一般路由之前声明更具体的路由是有意义的。 在将路由器函数注册为 Spring bean 时,这一点也很重要,也是如此 稍后描述。 请注意,此行为与基于 Comments 的编程模型不同,其中 “最具体”控制器方法会自动选取。spring-doc.cadn.net.cn

当使用 router 函数构建器时,所有定义的路由都组合成一个RouterFunction即从build(). 还有其他方法可以将多个 router 功能组合在一起:spring-doc.cadn.net.cn

以下示例显示了四个路由的组合:spring-doc.cadn.net.cn

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> otherRoute = ...

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2)
    .POST("/person", handler::createPerson) (3)
    .add(otherRoute) (4)
    .build();
1 GET /person/{id}替换为Accept匹配 JSON 的标头将路由到PersonHandler.getPerson
2 GET /person替换为Accept匹配 JSON 的标头将路由到PersonHandler.listPeople
3 POST /person没有其他谓词映射到PersonHandler.createPerson
4 otherRoute是在其他地方创建并添加到 Route built 的 router 函数。
Kotlin
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.web.servlet.function.router

val repository: PersonRepository = ...
val handler = PersonHandler(repository);

val otherRoute = router {  }

val route = router {
    GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1)
    GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2)
    POST("/person", handler::createPerson) (3)
}.and(otherRoute) (4)
1 GET /person/{id}替换为Accept匹配 JSON 的标头将路由到PersonHandler.getPerson
2 GET /person替换为Accept匹配 JSON 的标头将路由到PersonHandler.listPeople
3 POST /person没有其他谓词映射到PersonHandler.createPerson
4 otherRoute是在其他地方创建并添加到 Route built 的 router 函数。
嵌套路由

一组 router 函数通常具有共享谓词,例如共享的 路径。 在上面的示例中,共享谓词将是匹配的路径谓词/person, 由其中 3 条路由使用。 使用注释时,可以使用类型级@RequestMapping注解,映射到/person. 在 WebMvc.fn 中,可以通过path方法。 例如,通过使用嵌套路由,可以通过以下方式改进上面示例的最后几行:spring-doc.cadn.net.cn

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", builder -> builder (1)
        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        .GET(accept(APPLICATION_JSON), handler::listPeople)
        .POST(handler::createPerson))
    .build();
1 请注意,第二个参数path是采用 router builder 的 consumer。
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    "/person".nest {
        GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        GET(accept(APPLICATION_JSON), handler::listPeople)
        POST(handler::createPerson)
    }
}

尽管基于路径的嵌套是最常见的,但你可以使用 这nest方法。 以上仍然包含一些共享Accept-header 谓词。 我们可以通过使用nest方法与accept:spring-doc.cadn.net.cn

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople))
        .POST(handler::createPerson))
    .build();
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    "/person".nest {
        accept(APPLICATION_JSON).nest {
            GET("/{id}", handler::getPerson)
            GET("", handler::listPeople)
            POST(handler::createPerson)
        }
    }
}

1.4.4. 运行服务器

通常在DispatcherHandler-based 设置,它使用 Spring 配置来声明 处理请求所需的组件。MVC Java 配置声明以下内容 支持功能终端节点的基础设施组件:spring-doc.cadn.net.cn

  • RouterFunctionMapping:检测到一个或多个RouterFunction<?>Spring的咖啡豆 配置、排序、组合RouterFunction.andOther,并将请求路由到生成的 composeRouterFunction.spring-doc.cadn.net.cn

  • HandlerFunctionAdapter:简单的适配器,让DispatcherHandler调用 一个HandlerFunction该请求已映射到一个请求。spring-doc.cadn.net.cn

前面的组件允许功能端点适应DispatcherServlet请求 处理生命周期,并且(可能)与带注释的控制器并行运行,如果 任何 (any) 都已声明。这也是 Spring Boot Web 启用功能端点的方式 起动机。spring-doc.cadn.net.cn

以下示例显示了 WebFlux Java 配置:spring-doc.cadn.net.cn

Java
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public RouterFunction<?> routerFunctionA() {
        // ...
    }

    @Bean
    public RouterFunction<?> routerFunctionB() {
        // ...
    }

    // ...

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // configure message conversion...
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // configure CORS...
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configure view resolution for HTML rendering...
    }
}
Kotlin
@Configuration
@EnableMvc
class WebConfig : WebMvcConfigurer {

    @Bean
    fun routerFunctionA(): RouterFunction<*> {
        // ...
    }

    @Bean
    fun routerFunctionB(): RouterFunction<*> {
        // ...
    }

    // ...

    override fun configureMessageConverters(converters: List<HttpMessageConverter<*>>) {
        // configure message conversion...
    }

    override fun addCorsMappings(registry: CorsRegistry) {
        // configure CORS...
    }

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        // configure view resolution for HTML rendering...
    }
}

1.4.5. 过滤处理程序函数

您可以使用before,afterfilter路由上的 methods 函数构建器。 通过 annotations,您可以通过使用@ControllerAdvice一个ServletFilter和/或两者。 该筛选条件将应用于构建器构建的所有路由。 这意味着嵌套路由中定义的筛选条件不适用于 “top-level” 路由。 例如,请考虑以下示例:spring-doc.cadn.net.cn

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople)
            .before(request -> ServerRequest.from(request) (1)
                .header("X-RequestHeader", "Value")
                .build()))
        .POST(handler::createPerson))
    .after((request, response) -> logResponse(response)) (2)
    .build();
1 before添加自定义请求标头的 filter 仅适用于两个 GET 路由。
2 afterfilter 记录响应的 filter 应用于所有路由,包括嵌套路由。
Kotlin
import org.springframework.web.servlet.function.router

val route = router {
    "/person".nest {
        GET("/{id}", handler::getPerson)
        GET(handler::listPeople)
        before { (1)
            ServerRequest.from(it)
                    .header("X-RequestHeader", "Value").build()
        }
    }
    POST(handler::createPerson)
    after { _, response -> (2)
        logResponse(response)
    }
}
1 before添加自定义请求标头的 filter 仅适用于两个 GET 路由。
2 afterfilter 记录响应的 filter 应用于所有路由,包括嵌套路由。

filter方法采用HandlerFilterFunction:一个 函数,该函数采用ServerRequestHandlerFunction并返回一个ServerResponse. handler 函数参数表示链中的下一个元素。 这通常是路由到的处理程序,但也可以是另一个 filter (如果应用了多个)。spring-doc.cadn.net.cn

现在我们可以向路由添加一个简单的安全过滤器,假设我们有一个SecurityManager那 可以确定是否允许特定路径。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
SecurityManager securityManager = ...

RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople))
        .POST(handler::createPerson))
    .filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
    })
    .build();
Kotlin
import org.springframework.web.servlet.function.router

val securityManager: SecurityManager = ...

val route = router {
    ("/person" and accept(APPLICATION_JSON)).nest {
        GET("/{id}", handler::getPerson)
        GET("", handler::listPeople)
        POST(handler::createPerson)
        filter { request, next ->
            if (securityManager.allowAccessTo(request.path())) {
                next(request)
            }
            else {
                status(UNAUTHORIZED).build();
            }
        }
    }
}

前面的示例演示了调用next.handle(ServerRequest)是可选的。 我们只允许在允许访问时运行处理程序函数。spring-doc.cadn.net.cn

除了使用filter方法,则可以应用 过滤到现有的 router 函数RouterFunction.filter(HandlerFilterFunction).spring-doc.cadn.net.cn

对功能端点的 CORS 支持通过专用的CorsFilter.

1.5. URI 链接

本节描述了 Spring Framework 中可用于 URI 的各种选项。spring-doc.cadn.net.cn

1.5.1. UriComponents

Spring MVC 和 Spring WebFluxspring-doc.cadn.net.cn

UriComponentsBuilder有助于使用变量从 URI 模板构建 URI,如下例所示:spring-doc.cadn.net.cn

Java
UriComponents uriComponents = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")  (1)
        .queryParam("q", "{q}")  (2)
        .encode() (3)
        .build(); (4)

URI uri = uriComponents.expand("Westin", "123").toUri();  (5)
1 具有 URI 模板的静态工厂方法。
2 添加或替换 URI 组件。
3 请求对 URI 模板和 URI 变量进行编码。
4 构建一个UriComponents.
5 展开变量并获取URI.
Kotlin
val uriComponents = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")  (1)
        .queryParam("q", "{q}")  (2)
        .encode() (3)
        .build() (4)

val uri = uriComponents.expand("Westin", "123").toUri()  (5)
1 具有 URI 模板的静态工厂方法。
2 添加或替换 URI 组件。
3 请求对 URI 模板和 URI 变量进行编码。
4 构建一个UriComponents.
5 展开变量并获取URI.

前面的示例可以合并为一个链,并使用buildAndExpand, 如下例所示:spring-doc.cadn.net.cn

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri();
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri()

您可以通过直接转到 URI(这意味着编码)来进一步缩短它, 如下例所示:spring-doc.cadn.net.cn

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123")

您可以使用完整的 URI 模板进一步缩短它,如下例所示:spring-doc.cadn.net.cn

Java
URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123");
Kotlin
val uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123")

1.5.2. Uri生成器

Spring MVC 和 Spring WebFluxspring-doc.cadn.net.cn

UriComponentsBuilder实现UriBuilder.您可以创建一个UriBuilder,反过来,使用UriBuilderFactory.一起UriBuilderFactoryUriBuilder提供一种可插拔的机制来从 URI 模板构建 URI,基于 共享配置,例如基本 URL、编码首选项和其他详细信息。spring-doc.cadn.net.cn

您可以配置RestTemplateWebClient替换为UriBuilderFactory以自定义 URI 的准备工作。DefaultUriBuilderFactory是默认值 实现UriBuilderFactory使用UriComponentsBuilderinternally 和 公开共享配置选项。spring-doc.cadn.net.cn

以下示例显示如何配置RestTemplate:spring-doc.cadn.net.cn

Java
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
Kotlin
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode

val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES

val restTemplate = RestTemplate()
restTemplate.uriTemplateHandler = factory

以下示例将WebClient:spring-doc.cadn.net.cn

Java
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
Kotlin
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode

val baseUrl = "https://example.org"
val factory = DefaultUriBuilderFactory(baseUrl)
factory.encodingMode = EncodingMode.TEMPLATE_AND_VALUES

val client = WebClient.builder().uriBuilderFactory(factory).build()

此外,您还可以使用DefaultUriBuilderFactory径直。它类似于使用UriComponentsBuilder但是,它不是静态工厂方法,而是一个实际实例 ,其中包含 configuration 和 preferences,如下例所示:spring-doc.cadn.net.cn

Java
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");
Kotlin
val baseUrl = "https://example.com"
val uriBuilderFactory = DefaultUriBuilderFactory(baseUrl)

val uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123")

1.5.3. URI 编码

Spring MVC 和 Spring WebFluxspring-doc.cadn.net.cn

UriComponentsBuilder在两个级别公开编码选项:spring-doc.cadn.net.cn

这两个选项都用转义的八位字节替换非 ASCII 字符和非法字符。但是,第一个选项 还会替换 URI 变量中出现的具有保留含义的字符。spring-doc.cadn.net.cn

请考虑 “;”,它在 path 中是合法的,但具有保留的含义。第一个选项将 “;” 在 URI 变量中带有 “%3B”,但在 URI 模板中没有。相比之下,第二个选项永远不会 替换 “;”,因为它是路径中的合法字符。

在大多数情况下,第一个选项可能会给出预期的结果,因为它将 URI 变量作为不透明数据进行完全编码,而第二个选项在 URI 变量确实有意包含保留字符。第二个选项也很有用 当根本不扩展 URI 变量时,因为这也将编码任何 顺便说一句,看起来像一个 URI 变量。spring-doc.cadn.net.cn

以下示例使用第一个选项:spring-doc.cadn.net.cn

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("New York", "foo+bar")
        .toUri();

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("New York", "foo+bar")
        .toUri()

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"

您可以通过直接转到 URI(这意味着编码)来缩短前面的示例, 如下例所示:spring-doc.cadn.net.cn

Java
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .build("New York", "foo+bar");
Kotlin
val uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .build("New York", "foo+bar")

您可以使用完整的 URI 模板进一步缩短它,如下例所示:spring-doc.cadn.net.cn

Java
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
        .build("New York", "foo+bar");
Kotlin
val uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
        .build("New York", "foo+bar")

WebClientRestTemplate通过 这UriBuilderFactory策略。两者都可以使用自定义策略 如下例所示:spring-doc.cadn.net.cn

Java
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
Kotlin
val baseUrl = "https://example.com"
val factory = DefaultUriBuilderFactory(baseUrl).apply {
    encodingMode = EncodingMode.TEMPLATE_AND_VALUES
}

// Customize the RestTemplate..
val restTemplate = RestTemplate().apply {
    uriTemplateHandler = factory
}

// Customize the WebClient..
val client = WebClient.builder().uriBuilderFactory(factory).build()

DefaultUriBuilderFactoryimplementation usesUriComponentsBuilderinternally 到 展开并编码 URI 模板。作为工厂,它提供了一个配置位置 编码方法,基于以下编码模式之一:spring-doc.cadn.net.cn

  • TEMPLATE_AND_VALUES:使用UriComponentsBuilder#encode(),对应于 前面列表中的第一个选项,用于预编码 URI 模板并在 扩大。spring-doc.cadn.net.cn

  • VALUES_ONLY:不对 URI 模板进行编码,而是应用严格编码 到 URI 变量UriUtils#encodeUriVariables在将它们扩展到 模板。spring-doc.cadn.net.cn

  • URI_COMPONENT:使用UriComponents#encode(),对应于前面列表中的第二个选项,更改为 在 URI 变量展开对 URI 组件值进行编码。spring-doc.cadn.net.cn

  • NONE:不应用编码。spring-doc.cadn.net.cn

RestTemplate设置为EncodingMode.URI_COMPONENT对于历史 原因和向后兼容性。这WebClient依赖于默认值 在DefaultUriBuilderFactory,它已从EncodingMode.URI_COMPONENT在 5.0.x 更改为EncodingMode.TEMPLATE_AND_VALUES在 5.1 中。spring-doc.cadn.net.cn

1.5.4. 相对 Servlet 请求

您可以使用ServletUriComponentsBuilder创建相对于当前请求的 URI, 如下例所示:spring-doc.cadn.net.cn

Java
HttpServletRequest request = ...

// Re-uses scheme, host, port, path, and query string...

URI uri = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}")
        .build("123");
Kotlin
val request: HttpServletRequest = ...

// Re-uses scheme, host, port, path, and query string...

val uri = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}")
        .build("123")

您可以创建相对于上下文路径的 URI,如下例所示:spring-doc.cadn.net.cn

Java
HttpServletRequest request = ...

// Re-uses scheme, host, port, and context path...

URI uri = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts")
        .build()
        .toUri();
Kotlin
val request: HttpServletRequest = ...

// Re-uses scheme, host, port, and context path...

val uri = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts")
        .build()
        .toUri()

您可以创建相对于 Servlet 的 URI(例如/main/*), 如下例所示:spring-doc.cadn.net.cn

Java
HttpServletRequest request = ...

// Re-uses scheme, host, port, context path, and Servlet mapping prefix...

URI uri = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts")
        .build()
        .toUri();
Kotlin
val request: HttpServletRequest = ...

// Re-uses scheme, host, port, context path, and Servlet mapping prefix...

val uri = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts")
        .build()
        .toUri()
从 5.1 开始,ServletUriComponentsBuilder忽略来自ForwardedX-Forwarded-*headers,用于指定客户端发起的地址。考虑使用ForwardedHeaderFilter提取并使用或丢弃 这样的标头。

Spring MVC 提供了一种机制来准备指向控制器方法的链接。例如 以下 MVC 控制器允许创建链接:spring-doc.cadn.net.cn

Java
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {

    @GetMapping("/bookings/{booking}")
    public ModelAndView getBooking(@PathVariable Long booking) {
        // ...
    }
}
Kotlin
@Controller
@RequestMapping("/hotels/{hotel}")
class BookingController {

    @GetMapping("/bookings/{booking}")
    fun getBooking(@PathVariable booking: Long): ModelAndView {
        // ...
    }
}

您可以通过按名称引用方法来准备链接,如下例所示:spring-doc.cadn.net.cn

Java
UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();
Kotlin
val uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController::class.java, "getBooking", 21).buildAndExpand(42)

val uri = uriComponents.encode().toUri()

在前面的示例中,我们提供了实际的方法参数值(在本例中为 long 值:21) 用作路径变量并插入到 URL 中。此外,我们还提供 价值42来填写任何剩余的 URI 变量,例如hotel继承的变量 从类型级请求映射。如果该方法有更多的参数,我们可以为 URL 不需要参数。一般来说,只有@PathVariable@RequestParam参数 与构造 URL 相关。spring-doc.cadn.net.cn

还有其他使用MvcUriComponentsBuilder.例如,您可以使用一种技术 类似于通过代理进行模拟测试,以避免通过名称引用控制器方法,如下例所示 (该示例假定 static importMvcUriComponentsBuilder.on):spring-doc.cadn.net.cn

Java
UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();
Kotlin
val uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)

val uri = uriComponents.encode().toUri()
控制器方法签名在设计上受到限制,而它们应该可用于 链接创建fromMethodCall.除了需要适当的参数签名之外, 返回类型存在技术限制(即生成运行时代理 ),因此返回类型不得为final.特别 公共String视图名称的返回类型在此处不起作用。您应该使用ModelAndView甚至普通Object(使用Stringreturn value)来代替。

前面的示例在MvcUriComponentsBuilder.在内部,他们依赖于 上ServletUriComponentsBuilder要从 scheme、host、port 准备基本 URL, 当前请求的 context path 和 servlet path 的 servlet 路径。这在大多数情况下效果很好。 但是,有时,它可能不够。例如,您可能位于 请求(例如准备链接的批处理),或者可能需要插入路径 前缀(例如已从请求路径中删除的区域设置前缀,需要 重新插入到链接中)。spring-doc.cadn.net.cn

对于这种情况,您可以使用 staticfromXxx接受UriComponentsBuilder以使用基 URL。或者,您可以创建一个MvcUriComponentsBuilder替换为基本 URL,然后使用基于实例的withXxx方法。例如, 以下列出用途withMethodCall:spring-doc.cadn.net.cn

Java
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();
Kotlin
val base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en")
val builder = MvcUriComponentsBuilder.relativeTo(base)
builder.withMethodCall(on(BookingController::class.java).getBooking(21)).buildAndExpand(42)

val uri = uriComponents.encode().toUri()
从 5.1 开始,MvcUriComponentsBuilder忽略来自ForwardedX-Forwarded-*headers,用于指定客户端发起的地址。考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃 这样的标头。

在 Thymeleaf、FreeMarker 或 JSP 等视图中,您可以构建指向带注释的控制器的链接 通过引用每个请求映射的隐式或显式分配的名称。spring-doc.cadn.net.cn

请考虑以下示例:spring-doc.cadn.net.cn

Java
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {

    @RequestMapping("/{country}")
    public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}
Kotlin
@RequestMapping("/people/{id}/addresses")
class PersonAddressController {

    @RequestMapping("/{country}")
    fun getAddress(@PathVariable country: String): HttpEntity<PersonAddress> { ... }
}

给定前面的控制器,您可以从 JSP 准备一个链接,如下所示:spring-doc.cadn.net.cn

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>

前面的示例依赖于mvcUrl在 Spring 标记库中声明的函数 (即 META-INF/spring.tld),但定义自己的函数或准备 其他模板技术类似。spring-doc.cadn.net.cn

这是它的工作原理。启动时,每个@RequestMapping被分配了默认名称 通过HandlerMethodMappingNamingStrategy,其默认实现使用 类的大写字母和方法名称(例如,getThingmethod 中ThingController变为 “TC#getThing”)。如果存在名称冲突,您可以使用@RequestMapping(name="..")分配显式名称或实现自己的名称HandlerMethodMappingNamingStrategy.spring-doc.cadn.net.cn

1.6. 异步请求

Spring MVC 与 Servlet 3.0 异步请求处理进行了广泛的集成:spring-doc.cadn.net.cn

1.6.1.DeferredResult

在 Servlet 容器中启用异步请求处理功能后,控制器方法可以包装任何受支持的控制器方法 return value 替换为DeferredResult,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(result);
Kotlin
@GetMapping("/quotes")
@ResponseBody
fun quotes(): DeferredResult<String> {
    val deferredResult = DeferredResult<String>()
    // Save the deferredResult somewhere..
    return deferredResult
}

// From some other thread...
deferredResult.setResult(result)

控制器可以从不同的线程异步生成返回值 — 对于 例如,响应外部事件 (JMS 消息)、计划任务或其他事件。spring-doc.cadn.net.cn

1.6.2.Callable

控制器可以用java.util.concurrent.Callable, 如下例所示:spring-doc.cadn.net.cn

Java
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}
Kotlin
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
    // ...
    "someView"
}

然后,可以通过配置的 TaskExecutor.spring-doc.cadn.net.cn

1.6.3. 处理

以下是 Servlet 异步请求处理的非常简洁的概述:spring-doc.cadn.net.cn

  • 一个ServletRequest可以通过调用request.startAsync(). 这样做的主要效果是 Servlet(以及任何过滤器)可以退出,但 响应将保持打开状态,以便稍后完成处理。spring-doc.cadn.net.cn

  • request.startAsync()返回AsyncContext,您可以将其用于 进一步控制异步处理。例如,它提供dispatch方法 它类似于 Servlet API 的 forward,不同之处在于它允许 应用程序在 Servlet 容器线程上恢复请求处理。spring-doc.cadn.net.cn

  • ServletRequest提供对当前DispatcherType,您可以 用于区分处理初始请求、异步 dispatch、a forward 和其他 Dispatcher 类型。spring-doc.cadn.net.cn

DeferredResult处理工作如下:spring-doc.cadn.net.cn

  • 控制器返回一个DeferredResult并将其保存在内存中 queue 或 list 中。spring-doc.cadn.net.cn

  • Spring MVC 调用request.startAsync().spring-doc.cadn.net.cn

  • 与此同时,DispatcherServlet,并且所有已配置的过滤器都会退出请求 processing 线程,但响应保持打开状态。spring-doc.cadn.net.cn

  • 应用程序将DeferredResult来自某个线程,以及 Spring MVC 将请求分派回 Servlet 容器。spring-doc.cadn.net.cn

  • DispatcherServlet,并且处理会恢复,并显示 异步生成的返回值。spring-doc.cadn.net.cn

Callable处理工作如下:spring-doc.cadn.net.cn

  • 控制器返回一个Callable.spring-doc.cadn.net.cn

  • Spring MVC 调用request.startAsync()并提交Callable自 一个TaskExecutor以便在单独的线程中进行处理。spring-doc.cadn.net.cn

  • 与此同时,DispatcherServlet并且所有过滤器都退出 Servlet 容器线程, 但回应仍然开放。spring-doc.cadn.net.cn

  • 最终,Callable产生一个结果,Spring MVC 将请求分派回来 添加到 Servlet 容器中以完成处理。spring-doc.cadn.net.cn

  • DispatcherServlet,并且处理会恢复,并显示 从Callable.spring-doc.cadn.net.cn

有关更多背景和上下文,您还可以阅读 在 Spring MVC 3.2 中引入异步请求处理支持的博客文章。spring-doc.cadn.net.cn

异常处理

当您使用DeferredResult,您可以选择是否调用setResultsetErrorResult但有一个例外。在这两种情况下,Spring MVC 都会将请求分派回来 添加到 Servlet 容器中以完成处理。然后,它被视为 controller 方法返回给定的值,或者就像它产生了给定的异常一样。 然后,异常会通过常规的异常处理机制(例如,调用@ExceptionHandler方法)。spring-doc.cadn.net.cn

当您使用Callable,则会出现类似的处理逻辑,主要区别在于 结果从Callable或由它引发异常。spring-doc.cadn.net.cn

拦截

HandlerInterceptor实例可以是AsyncHandlerInterceptor,以接收afterConcurrentHandlingStarted对启动异步的初始请求的 callback 正在处理(而不是postHandleafterCompletion).spring-doc.cadn.net.cn

HandlerInterceptor实现还可以注册一个CallableProcessingInterceptorDeferredResultProcessingInterceptor,以便与 异步请求的生命周期(例如,处理超时事件)。看AsyncHandlerInterceptor了解更多详情。spring-doc.cadn.net.cn

DeferredResult提供onTimeout(Runnable)onCompletion(Runnable)回调。 请参阅javadoc 的DeferredResult了解更多详情。Callable可以替代WebAsyncTask这会暴露额外的 timeout和completion回调的方法。spring-doc.cadn.net.cn

与 WebFlux 相比

Servlet API 最初是为通过 Filter-Servlet 进行一次传递而构建的 链。Servlet 3.0 中添加的异步请求处理允许应用程序退出 Filter-Servlet 链,但将响应保持开放状态以供进一步处理。The Spring MVC 异步支持是围绕该机制构建的。当控制器返回DeferredResult, 退出 Filter-Servlet 链,并释放 Servlet 容器线程。稍后,当 这DeferredResult设置后,会触发一个ASYNCdispatch(到同一个 URL),在此期间, controller 再次映射,但DeferredResultvalue 被使用 (就像控制器返回一样)以恢复处理。spring-doc.cadn.net.cn

相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的 asynchronous request processing 功能,因为它在设计上是异步的。异步 Handling 内置于所有框架 Contract 中,并且通过 ALL 请求处理阶段。spring-doc.cadn.net.cn

从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持 asynchronous 和 Reactive Types 作为控制器方法中的返回值。 Spring MVC 甚至支持流,包括响应式背压。但是,单个 对响应的写入仍然是阻塞的(并且在单独的线程上执行),这与 WebFlux 不同, 它依赖于非阻塞 I/O,并且每次写入不需要额外的线程。spring-doc.cadn.net.cn

另一个根本区别是 Spring MVC 不支持异步或响应式 控制器方法参数中的类型(例如@RequestBody,@RequestPart等)、 它也没有任何明确支持异步和反应类型作为模型属性。 Spring WebFlux 确实支持所有这些。spring-doc.cadn.net.cn

1.6.4. HTTP 流

您可以使用DeferredResultCallable对于单个异步返回值。 如果您想生成多个异步值,并将这些值写入 响应?本节介绍如何执行此作。spring-doc.cadn.net.cn

对象

您可以使用ResponseBodyEmitterreturn 值来生成对象流,其中 每个对象都使用HttpMessageConverter并写入 响应,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();
Kotlin
@GetMapping("/events")
fun handle() = ResponseBodyEmitter().apply {
    // Save the emitter somewhere..
}

// In some other thread
emitter.send("Hello once")

// and again later on
emitter.send("Hello again")

// and done at some point
emitter.complete()

您还可以使用ResponseBodyEmitter作为ResponseEntity,让您 自定义响应的状态和标头。spring-doc.cadn.net.cn

emitter抛出一个IOException(例如,如果远程客户端消失)、应用程序 不负责清理连接,不应调用emitter.completeemitter.completeWithError.相反,Servlet 容器会自动启动AsyncListener错误通知,其中 Spring MVC 会创建一个completeWithError叫。 此调用反过来会执行最后一个ASYNCdispatch 到应用程序,在此期间 Spring MVC 调用配置的异常解析程序并完成请求。spring-doc.cadn.net.cn

上交所

SseEmitterResponseBodyEmitter) 提供对 Server-Sent Events 的支持,其中从服务器发送的事件 根据 W3C SSE 规范进行格式设置。生成 SSE stream 中,返回SseEmitter,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();
Kotlin
@GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun handle() = SseEmitter().apply {
    // Save the emitter somewhere..
}

// In some other thread
emitter.send("Hello once")

// and again later on
emitter.send("Hello again")

// and done at some point
emitter.complete()

虽然 SSE 是流式传输到浏览器的主要选项,但请注意,Internet Explorer 不支持 Server-Sent Events。考虑将 Spring 的 WebSocket 消息传递Sockjs 回退传输(包括 SSE)一起使用,该传输将 广泛的浏览器。spring-doc.cadn.net.cn

有关异常处理的说明,另请参阅上一节spring-doc.cadn.net.cn

原始数据

有时,绕过消息转换并直接流式传输到响应很有用OutputStream(例如,对于文件下载)。您可以使用StreamingResponseBodyreturn value 类型来执行此作,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}
Kotlin
@GetMapping("/download")
fun handle() = StreamingResponseBody {
    // write...
}

您可以使用StreamingResponseBody作为ResponseEntity自 自定义响应的状态和标头。spring-doc.cadn.net.cn

1.6.5. 响应式类型

Spring MVC 支持在控制器中使用响应式 Client 端库(另请阅读 WebFlux 部分中的反应式库)。 这包括WebClientspring-webflux和其他 响应式数据存储库。在这种情况下,能够返回很方便 来自 controller 方法的 reactive 类型。spring-doc.cadn.net.cn

响应式返回值的处理方式如下:spring-doc.cadn.net.cn

  • 单值 promise 适应,类似于使用DeferredResult.例子 包括Mono(Reactor) 或Single(RxJava) 的 Java 版本。spring-doc.cadn.net.cn

  • 具有流媒体类型(如application/x-ndjsontext/event-stream) 适应于,类似于使用ResponseBodyEmitterSseEmitter.示例包括Flux(Reactor) 或Observable(RxJava) 的 Java 版本。 应用程序也可以返回Flux<ServerSentEvent>Observable<ServerSentEvent>.spring-doc.cadn.net.cn

  • 具有任何其他媒体类型(例如application/json) 已适应 to 的 API API 中,类似于使用DeferredResult<List<?>>.spring-doc.cadn.net.cn

Spring MVC 通过ReactiveAdapterRegistryspring-core,这允许它适应多个响应式库。

对于流式传输到响应,支持响应式背压,但会写入 响应仍然阻塞,并通过配置的 TaskExecutor,以避免 阻止上游源(例如Flux返回自WebClient). 默认情况下,SimpleAsyncTaskExecutor用于阻塞写入,但这不是 适合在负载下使用。如果您计划使用反应类型进行流式传输,则应使用 MVC 配置来配置任务执行程序。spring-doc.cadn.net.cn

1.6.6. 断开连接

当远程客户端消失时,Servlet API 不提供任何通知。 因此,在流式传输到响应时,无论是通过 SseEmitter 还是响应式类型,定期发送数据都很重要。 因为如果客户端已断开连接,则写入失败。发送可以采用 空(仅评论)SSE 事件或另一方必须解释的任何其他数据 作为心跳并忽略。spring-doc.cadn.net.cn

或者,考虑使用 Web 消息传递解决方案(例如基于 WebSocket 的 STOMP 或带有 SockJS 的 WebSocket) 具有内置心跳机制的 S S S 的 S S S 的 S Tspring-doc.cadn.net.cn

1.6.7. 配置

必须在 Servlet 容器级别启用异步请求处理功能。 MVC 配置还为异步请求公开了几个选项。spring-doc.cadn.net.cn

Servlet 容器

Filter 和 Servlet 声明具有asyncSupported标志,需要设置为true以启用异步请求处理。此外,Filter 映射应为 declared 来处理ASYNC javax.servlet.DispatchType.spring-doc.cadn.net.cn

在 Java 配置中,当您使用AbstractAnnotationConfigDispatcherServletInitializer初始化 Servlet 容器,此作会自动完成。spring-doc.cadn.net.cn

web.xmlconfiguration 中,您可以添加<async-supported>true</async-supported>DispatcherServlet以及Filter声明并添加<dispatcher>ASYNC</dispatcher>以筛选映射。spring-doc.cadn.net.cn

Spring MVC

MVC 配置公开了以下与异步请求处理相关的选项:spring-doc.cadn.net.cn

您可以配置以下内容:spring-doc.cadn.net.cn

  • 异步请求的默认超时值(如果未设置),则取决于 在底层 Servlet 容器上。spring-doc.cadn.net.cn

  • AsyncTaskExecutor用于在使用 Reactive Types 进行流式处理时阻止写入和执行Callable从 controller 方法。如果您满足以下条件,我们强烈建议您配置此属性 stream 或具有返回Callable因为 默认情况下,它是一个SimpleAsyncTaskExecutor.spring-doc.cadn.net.cn

  • DeferredResultProcessingInterceptorimplementations 和CallableProcessingInterceptor实现。spring-doc.cadn.net.cn

请注意,您还可以在DeferredResult, 一个ResponseBodyEmitterSseEmitter.对于Callable,您可以使用WebAsyncTask以提供超时值。spring-doc.cadn.net.cn

1.7. CORS

Spring MVC 允许你处理 CORS(跨域资源共享)。本节 介绍如何执行此作。spring-doc.cadn.net.cn

1.7.1. 简介

出于安全原因,浏览器禁止对当前源之外的资源进行 AJAX 调用。 例如,您可以将银行账户放在一个选项卡中,而 evil.com 放在另一个选项卡中。脚本 evil.com 中,应该无法使用 凭证 — 例如,从您的账户中取款!spring-doc.cadn.net.cn

跨域资源共享 (CORS) 是大多数浏览器实现的 W3C 规范,允许您指定 授权什么样的跨域请求,而不是使用安全性较低且较少 基于 IFRAME 或 JSONP 的强大解决方法。spring-doc.cadn.net.cn

1.7.2. 处理

CORS 规范区分了印前检查请求、简单请求和实际请求。 要了解 CORS 的工作原理,您可以阅读这篇文章,其中 许多其他方法,或者参见 规范 了解更多详情。spring-doc.cadn.net.cn

Spring MVCHandlerMappingimplementations 提供对 CORS 的内置支持。成功后 将请求映射到处理程序,HandlerMappingimplementations 检查 CORS 配置的 given request 和 handler 并采取进一步的作。处理印前检查请求 直接,而简单和实际的 CORS 请求被拦截、验证,并且具有 所需的 CORS 响应标头集。spring-doc.cadn.net.cn

为了启用跨域请求(即Originheader 存在且 与请求的主机不同),您需要有一些显式声明的 CORS 配置。如果未找到匹配的 CORS 配置,则印前 Backup 请求为 拒绝。没有 CORS 标头添加到简单和实际 CORS 请求的响应中 因此,浏览器会拒绝它们。spring-doc.cadn.net.cn

HandlerMapping可以使用基于 URL 模式的 URL 进行单独配置CorsConfiguration映射。在大多数情况下,应用程序 使用 MVC Java 配置或 XML 命名空间来声明此类映射,这将产生 在传递给所有HandlerMapping实例。spring-doc.cadn.net.cn

您可以在HandlerMappingLevel with More (更多级别) 精细的处理程序级 CORS 配置。例如,带注解的控制器可以使用 类级或方法级@CrossOrigin注解(其他处理程序可以实现CorsConfigurationSource).spring-doc.cadn.net.cn

组合全局配置和本地配置的规则通常是累加的 — 例如, 所有全球和所有本地源。对于只能使用单个值 accepted,例如allowCredentialsmaxAge,则 local 将覆盖 global 值。看CorsConfiguration#combine(CorsConfiguration)了解更多详情。spring-doc.cadn.net.cn

要从源代码中了解更多信息或进行高级自定义,请检查背后的代码:spring-doc.cadn.net.cn

1.7.3. 凭证请求

将 CORS 与凭证请求一起使用需要启用allowedCredentials.请注意, 此选项与配置的域建立高级别的信任,并且还会增加 通过公开敏感的用户特定信息,Web 应用程序的攻击面 例如 Cookie 和 CSRF Tokens。spring-doc.cadn.net.cn

启用凭证还会影响已配置 CORS 通配符的处理方式:"*"spring-doc.cadn.net.cn

  • 通配符在 中未获得授权allowOrigins,但也可以 这allowOriginPatterns属性可用于匹配一组动态的源。spring-doc.cadn.net.cn

  • 当设置为allowedHeadersallowedMethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Methods响应标头的处理方法是将相关的 标头和方法。spring-doc.cadn.net.cn

  • 当设置为exposedHeaders,Access-Control-Expose-Headers响应标头 添加到配置的标头列表或通配符。虽然 CORS 规范 不允许使用通配符Access-Control-Allow-Credentials设置为true,则大多数浏览器都支持它,并且响应标头在 CORS 处理,因此通配符是 指定,而不管allowCredentials财产。spring-doc.cadn.net.cn

虽然这种通配符配置可能很方便,但建议在可能的情况下配置 一组有限的值,以提供更高级的安全性。

1.7.4.@CrossOrigin

@CrossOriginannotation 支持对带注解的控制器方法进行跨域请求, 如下例所示:spring-doc.cadn.net.cn

Java
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    fun remove(@PathVariable id: Long) {
        // ...
    }
}

默认情况下,@CrossOrigin允许:spring-doc.cadn.net.cn

allowCredentials默认情况下不启用,因为这会建立信任级别 公开敏感的用户特定信息(例如 cookie 和 CSRF Tokens)和 应仅在适当的情况下使用。启用时allowOrigins必须是 设置为一个或多个特定域(但不是 Special Value )或 这"*"allowOriginPatterns属性可用于匹配一组动态的源。spring-doc.cadn.net.cn

maxAge设置为 30 分钟。spring-doc.cadn.net.cn

@CrossOrigin在类级别也受支持,并且被所有方法继承。 如下例所示:spring-doc.cadn.net.cn

Java
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

    @GetMapping("/{id}")
    fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    fun remove(@PathVariable id: Long) {
        // ...
    }

您可以使用@CrossOrigin在类级别和方法级别, 如下例所示:spring-doc.cadn.net.cn

Java
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("https://domain2.com")
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin("https://domain2.com")
    @GetMapping("/{id}")
    fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    fun remove(@PathVariable id: Long) {
        // ...
    }
}

1.7.5. 全局配置

除了细粒度的 controller 方法级配置之外,您可能还希望 也定义一些全局 CORS 配置。您可以设置基于 URLCorsConfiguration映射HandlerMapping.但是,大多数应用程序都使用 MVC Java 配置或 MVC XML 命名空间来执行此作。spring-doc.cadn.net.cn

默认情况下,全局配置将启用以下功能:spring-doc.cadn.net.cn

allowCredentials默认情况下不启用,因为这会建立信任级别 公开敏感的用户特定信息(例如 cookie 和 CSRF Tokens)和 应仅在适当的情况下使用。启用时allowOrigins必须是 设置为一个或多个特定域(但不是 Special Value )或 这"*"allowOriginPatterns属性可用于匹配一组动态的源。spring-doc.cadn.net.cn

maxAge设置为 30 分钟。spring-doc.cadn.net.cn

Java 配置

要在 MVC Java 配置中启用 CORS,您可以使用CorsRegistry回调 如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/api/**")
            .allowedOrigins("https://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(true).maxAge(3600);

        // Add more mappings...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addCorsMappings(registry: CorsRegistry) {

        registry.addMapping("/api/**")
                .allowedOrigins("https://domain2.com")
                .allowedMethods("PUT", "DELETE")
                .allowedHeaders("header1", "header2", "header3")
                .exposedHeaders("header1", "header2")
                .allowCredentials(true).maxAge(3600)

        // Add more mappings...
    }
}
XML 配置

要在 XML 命名空间中启用 CORS,您可以使用<mvc:cors>元素 如下例所示:spring-doc.cadn.net.cn

<mvc:cors>

    <mvc:mapping path="/api/**"
        allowed-origins="https://domain1.com, https://domain2.com"
        allowed-methods="GET, PUT"
        allowed-headers="header1, header2, header3"
        exposed-headers="header1, header2" allow-credentials="true"
        max-age="123" />

    <mvc:mapping path="/resources/**"
        allowed-origins="https://domain1.com" />

</mvc:cors>

1.7.6. CORS 过滤器

您可以通过内置的CorsFilter.spring-doc.cadn.net.cn

如果您尝试使用CorsFilter使用 Spring Security,请记住 Spring 安全性内置了对 CORS 的。

要配置过滤器,请传递CorsConfigurationSource设置为其构造函数,作为 以下示例显示:spring-doc.cadn.net.cn

Java
CorsConfiguration config = new CorsConfiguration();

// Possibly...
// config.applyPermitDefaultValues()

config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

CorsFilter filter = new CorsFilter(source);
Kotlin
val config = CorsConfiguration()

// Possibly...
// config.applyPermitDefaultValues()

config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")

val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", config)

val filter = CorsFilter(source)

1.8. Web 安全

Spring Security 项目提供支持 用于保护 Web 应用程序免受恶意攻击。请参阅 Spring Security 参考文档,包括:spring-doc.cadn.net.cn

HDIV 是另一个与 Spring MVC 集成的 Web 安全框架。spring-doc.cadn.net.cn

1.9. HTTP 缓存

HTTP 缓存可以显著提高 Web 应用程序的性能。HTTP 缓存 围绕Cache-Control响应标头,然后是条件请求 标头(例如Last-ModifiedETag).Cache-Control建议私有(例如,浏览器) 以及有关如何缓存和重用响应的公共(例如代理)缓存。一ETagheader 的 要发出可能导致 304 (NOT_MODIFIED) 没有正文的条件请求, 如果内容未更改。ETag可以看作是 这Last-Modified页眉。spring-doc.cadn.net.cn

本节描述了 Spring Web MVC 中可用的 HTTP 缓存相关选项。spring-doc.cadn.net.cn

1.9.1.CacheControl

CacheControl提供支持 配置与Cache-Controlheader 并被接受为参数 在许多地方:spring-doc.cadn.net.cn

虽然 RFC 7234 描述了所有可能的 指令的Cache-Controlresponse 标头中,CacheControltype 接受 面向用例的方法,侧重于常见场景:spring-doc.cadn.net.cn

Java
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
Kotlin
// Cache for an hour - "Cache-Control: max-age=3600"
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)

// Prevent caching - "Cache-Control: no-store"
val ccNoStore = CacheControl.noStore()

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()

WebContentGenerator也接受 SimplercachePeriod属性(以秒为单位定义),该属性 工作原理如下:spring-doc.cadn.net.cn

1.9.2. 控制器

控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为lastModifiedETag需要先计算资源的值,然后才能进行比较 针对条件请求标头。控制器可以添加ETagheader 和Cache-Controlsettings 设置为ResponseEntity,如下例所示:spring-doc.cadn.net.cn

Java
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book);
}
Kotlin
@GetMapping("/book/{id}")
fun showBook(@PathVariable id: Long): ResponseEntity<Book> {

    val book = findBook(id);
    val version = book.getVersion()

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book)
}

前面的示例发送一个 304 (NOT_MODIFIED) 响应,如果比较 添加到条件请求标头中,表示内容未更改。否则,ETagCache-Control标头将添加到响应中。spring-doc.cadn.net.cn

你也可以在控制器中对条件请求头进行检查, 如下例所示:spring-doc.cadn.net.cn

Java
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {

    long eTag = ... (1)

    if (request.checkNotModified(eTag)) {
        return null; (2)
    }

    model.addAttribute(...); (3)
    return "myViewName";
}
1 特定于应用程序的计算。
2 响应已设置为 304 (NOT_MODIFIED) — 不再进一步处理。
3 继续进行请求处理。
Kotlin
@RequestMapping
fun myHandleMethod(request: WebRequest, model: Model): String? {

    val eTag: Long = ... (1)

    if (request.checkNotModified(eTag)) {
        return null (2)
    }

    model[...] = ... (3)
    return "myViewName"
}
1 特定于应用程序的计算。
2 响应已设置为 304 (NOT_MODIFIED) — 不再进一步处理。
3 继续进行请求处理。

有三种变体可用于检查条件请求eTaglastModified值,或两者兼而有之。对于有条件的GETHEADrequests 中,您可以将响应设置为 304 (NOT_MODIFIED)。对于有条件的POST,PUTDELETE中,您可以改为设置响应 设置为 412 (PRECONDITION_FAILED),以防止并发修改。spring-doc.cadn.net.cn

1.9.3. 静态资源

您应该使用Cache-Control和条件响应标头 以获得最佳性能。请参阅有关配置静态资源的部分。spring-doc.cadn.net.cn

1.9.4.ETagFilter

您可以使用ShallowEtagHeaderFilter添加 “浅”eTag值,这些值是从 响应内容,因此可以节省带宽,但不能节省 CPU 时间。参见浅层 ETagspring-doc.cadn.net.cn

1.10. 查看技术

在 Spring MVC 中使用视图技术是可插拔的。无论您决定使用 Thymeleaf、Groovy 标记模板、JSP 或其他技术主要是一个问题 配置更改。本章介绍与 Spring MVC 集成的视图技术。 我们假设您已经熟悉 View Resolutionspring-doc.cadn.net.cn

Spring MVC 应用程序的视图位于内部信任边界内 该应用程序。视图可以访问应用程序上下文的所有 bean。如 因此,不建议在满足以下条件的应用程序中使用 Spring MVC 的模板支持 模板可由外部源编辑,因为这可能会产生安全隐患。

1.10.1. 百里香叶

Thymeleaf 是一个现代的服务器端 Java 模板引擎,强调自然 HTML 可以通过双击在浏览器中预览的模板,这非常有用 用于独立处理 UI 模板(例如,由设计人员),而无需 正在运行的服务器。如果您想替换 JSP,Thymeleaf 提供了最 广泛的功能集,使这种过渡更容易。Thymeleaf 正在积极地 开发和维护。有关更完整的介绍,请参阅 Thymeleaf 项目主页。spring-doc.cadn.net.cn

Thymeleaf 与 Spring MVC 的集成由 Thymeleaf 项目管理。 该配置涉及一些 bean 声明,例如ServletContextTemplateResolver,SpringTemplateEngineThymeleafViewResolver. 有关更多详细信息,请参见 Thymeleaf+Springspring-doc.cadn.net.cn

1.10.2. 自由标记

Apache FreeMarker 是一个模板引擎,用于生成任何 从 HTML 到电子邮件等的文本输出类型。Spring Framework 内置了 用于将 Spring MVC 与 FreeMarker 模板一起使用的集成。spring-doc.cadn.net.cn

View 配置

以下示例显示了如何将 FreeMarker 配置为视图技术:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();
    }

    // Configure FreeMarker...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()
    }

    // Configure FreeMarker...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("/WEB-INF/freemarker")
    }
}

以下示例显示了如何在 XML 中配置相同的内容:spring-doc.cadn.net.cn

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:freemarker/>
</mvc:view-resolvers>

<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>

或者,您也可以声明FreeMarkerConfigurerbean 用于完全控制所有 属性,如下例所示:spring-doc.cadn.net.cn

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>

您的模板需要存储在FreeMarkerConfigurer如前面的示例所示。给定上述配置,如果您的控制器 返回视图名称welcome中,解析程序会查找/WEB-INF/freemarker/welcome.ftl模板。spring-doc.cadn.net.cn

FreeMarker 配置

您可以将 FreeMarker 的 'Settings' 和 'SharedVariables' 直接传递给 FreeMarkerConfiguration对象(由 Spring 管理)通过设置适当的 bean 属性FreeMarkerConfigurer豆。这freemarkerSettingsproperty 需要 一个java.util.Propertiesobject 和freemarkerVariables属性需要java.util.Map.以下示例演示如何使用FreeMarkerConfigurer:spring-doc.cadn.net.cn

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
    <property name="freemarkerVariables">
        <map>
            <entry key="xml_escape" value-ref="fmXmlEscape"/>
        </map>
    </property>
</bean>

<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>

请参阅 FreeMarker 文档,了解设置和变量的详细信息,因为它们适用于这些设置 这Configuration对象。spring-doc.cadn.net.cn

表单处理

Spring 提供了一个用于 JSP 的标记库,其中包括一个<spring:bind/>元素。此元素主要允许表单显示来自 表单支持对象,并显示来自Validator在 Web 或业务层。Spring 在 FreeMarker 中也支持相同的功能, 具有用于生成表单输入元素本身的附加便捷宏。spring-doc.cadn.net.cn

Bind 宏

一组标准的宏在spring-webmvc.jar文件 FreeMarker,因此它们始终可用于适当配置的应用程序。spring-doc.cadn.net.cn

Spring 模板库中定义的一些宏被认为是内部的 (私有),但宏定义中不存在此类范围,因此所有宏都可见 调用代码和用户模板。以下各节仅重点介绍宏 您需要直接从模板中调用。如果您想查看宏代码 直接调用该文件spring.ftl,并且位于org.springframework.web.servlet.view.freemarker包。spring-doc.cadn.net.cn

简单绑定

在基于 FreeMarker 模板的 HTML 表单中,这些模板充当 Spring MVC 的表单视图 controller 中,您可以使用类似于以下示例的代码绑定到字段值和 显示每个输入字段的错误消息的方式与 JSP 等效项类似。这 以下示例显示了personForm视图:spring-doc.cadn.net.cn

<!-- FreeMarker macros have to be imported into a namespace.
    We strongly recommend sticking to 'spring'. -->
<#import "/spring.ftl" as spring/>
<html>
    ...
    <form action="" method="POST">
        Name:
        <@spring.bind "personForm.name"/>
        <input type="text"
            name="${spring.status.expression}"
            value="${spring.status.value?html}"/><br />
        <#list spring.status.errorMessages as error> <b>${error}</b> <br /> </#list>
        <br />
        ...
        <input type="submit" value="submit"/>
    </form>
    ...
</html>

<@spring.bind>需要一个 'path' 参数,该参数由命令的名称组成 对象(它是 'command',除非您在控制器配置中更改了它) 按句点和要绑定到的 Command 对象上的字段名称。你 也可以使用嵌套字段,例如command.address.street.这bindmacro 假定 默认 HTML 转义行为,由ServletContext参数defaultHtmlEscapeweb.xml.spring-doc.cadn.net.cn

宏的另一种形式称为<@spring.bindEscaped>采用第二个参数 显式指定是否应在状态错误中使用 HTML 转义 消息或值。您可以将其设置为truefalse根据需要。附加表格 处理宏简化了 HTML 转义的使用,您应该使用这些宏 尽可能。它们将在下一节中解释。spring-doc.cadn.net.cn

输入宏

FreeMarker 的其他便捷宏简化了装订和表单生成 (包括验证错误显示)。永远没有必要使用这些宏来 生成表单输入字段,并且您可以使用简单的 HTML 或直接将它们混合和匹配 调用我们之前突出显示的 Spring 绑定宏。spring-doc.cadn.net.cn

下表显示了 FreeMarker 模板 (FTL) 定义 以及每个参数的参数列表:spring-doc.cadn.net.cn

表 6.宏定义表
FTL 定义

message(根据 code 参数从资源包中输出一个字符串)spring-doc.cadn.net.cn

<@spring.message code/>spring-doc.cadn.net.cn

messageText(根据 code 参数从资源包中输出一个字符串, 回退到 default 参数的值)spring-doc.cadn.net.cn

<@spring.message文本代码、文本/>spring-doc.cadn.net.cn

url(在相对 URL 前面加上应用程序的上下文根)spring-doc.cadn.net.cn

<@spring.url relativeUrl/>spring-doc.cadn.net.cn

formInput(用于收集用户输入的标准输入字段)spring-doc.cadn.net.cn

<@spring.form输入路径、属性、fieldType/>spring-doc.cadn.net.cn

formHiddenInput(用于提交非用户输入的隐藏输入字段)spring-doc.cadn.net.cn

<@spring.formHiddenInput 路径,attributes/>spring-doc.cadn.net.cn

formPasswordInput(用于收集密码的标准输入字段。请注意,没有 value 会填充到此类型的字段中。spring-doc.cadn.net.cn

<@spring.formPasswordInput 路径、attributes/>spring-doc.cadn.net.cn

formTextarea(用于收集较长的自由格式文本输入的大文本字段)spring-doc.cadn.net.cn

<@spring.formText区域路径、attributes/>spring-doc.cadn.net.cn

formSingleSelect(选项的下拉框,让单个 required 值为 selected)spring-doc.cadn.net.cn

<@spring.formSingleSelect 路径、选项、属性/>spring-doc.cadn.net.cn

formMultiSelect(允许用户选择 0 个或多个值的选项列表框)spring-doc.cadn.net.cn

<@spring.formMultiSelect path, options, attributes/>spring-doc.cadn.net.cn

formRadioButtons(一组用于进行单个选择的单选按钮 从可用选项中)spring-doc.cadn.net.cn

<@spring.formRadioButtons 路径、选项分隔符、attributes/>spring-doc.cadn.net.cn

formCheckboxes(一组允许选择 0 或多个值的复选框)spring-doc.cadn.net.cn

<@spring.formCheckboxes 路径、选项、分隔符、attributes/>spring-doc.cadn.net.cn

formCheckbox(单个复选框)spring-doc.cadn.net.cn

<@spring.formCheckbox 路径,attributes/>spring-doc.cadn.net.cn

showErrors(简化绑定字段的验证错误的显示)spring-doc.cadn.net.cn

<@spring.showErrors 分隔符 classOrStyle/>spring-doc.cadn.net.cn

在 FreeMarker 模板中,formHiddenInputformPasswordInput实际上并非 required,因为您可以使用正常的formInput宏, 指定hiddenpassword作为fieldType参数。

上述任何宏的参数都具有一致的含义:spring-doc.cadn.net.cn

  • path:要绑定到的字段的名称(即 “command.name”)spring-doc.cadn.net.cn

  • options:一个Map可以在输入中选择的所有可用值 田。映射的键表示从表单中 POST 回来的值 并绑定到 Command 对象。针对键存储的 Map 对象是标签 在表单上显示给用户,并且可能与相应的值不同 通过表单发回。通常,此类映射由 控制器。您可以使用任何Mapimplementation 的 Instant,具体取决于所需的行为。 对于严格排序的地图,您可以使用SortedMap(例如TreeMap) 替换为 合适Comparator并且,对于应在插入中返回值的任意 Map order 中使用LinkedHashMapLinkedMapcommons-collections.spring-doc.cadn.net.cn

  • separator:其中多个选项可用作隐蔽元素(单选按钮 或复选框)中,则用于分隔列表中每个字符的字符序列 (例如<br>).spring-doc.cadn.net.cn

  • attributes:要包含在其中的任意标签或文本的附加字符串 HTML 标签本身。此字符串由宏逐字面回显。例如,在textarea字段,您可以提供属性(例如 'rows=“5” cols=“60”'),或者您 可以传递样式信息,例如 'style=“border:1px solid silver”'。spring-doc.cadn.net.cn

  • classOrStyle:对于showErrorsmacro 的 CSS 类的名称,该类的span元素。如果未提供任何信息(或者值为 empty),则错误将包含在<b></b>标签。spring-doc.cadn.net.cn

以下各节概述了宏的示例。spring-doc.cadn.net.cn

输入字段

formInputmacro 将path参数 (command.name) 和额外的attributes参数(在即将到来的示例中为空)。宏以及所有其他形式 generation 宏,对 path 参数执行隐式 Spring 绑定。绑定 在发生新绑定之前保持有效,因此showErrors宏不需要传递 path 参数 — 它对上次为其创建绑定的字段进行作。spring-doc.cadn.net.cn

showErrorsmacro 接受一个 separator 参数(用于 分隔给定字段上的多个错误),并且还接受第二个参数 — this time、类名或 style 属性。请注意,FreeMarker 可以指定 default attributes 参数的值。以下示例演示如何使用formInputshowErrors宏:spring-doc.cadn.net.cn

<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>

下一个示例显示了表单片段的输出,生成 name 字段并显示 提交表单后出现验证错误,但字段中没有值。验证 通过 Spring 的 Validation 框架发生。spring-doc.cadn.net.cn

生成的 HTML 类似于以下示例:spring-doc.cadn.net.cn

Name:
<input type="text" name="name" value="">
<br>
    <b>required</b>
<br>
<br>

formTextarea宏的工作方式与formInput宏并接受相同的 parameter 列表。通常,第二个参数 (attributes) 用于传递样式 information 或rowscols属性textarea.spring-doc.cadn.net.cn

选择字段

您可以使用四个选择字段宏在 您的 HTML 表单:spring-doc.cadn.net.cn

四个宏中的每一个都接受一个Mapof 选项,其中包含表单的值 field 和与该值对应的标签。值和标签可以是 相同。spring-doc.cadn.net.cn

下一个示例是 FTL 中的单选按钮。表单支持对象指定默认值 值为 'London' ,因此无需验证。当表单为 呈现时,可供选择的整个城市列表将作为参考数据提供在 model 替换为 'cityMap' 的下面的清单显示了该示例:spring-doc.cadn.net.cn

...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>

前面的清单呈现了一行单选按钮,每个按钮对应cityMap,并使用 separator 的 。没有提供其他属性(宏的最后一个参数是 missing)。这""cityMap使用相同的String对于映射中的每个键值对。地图的 键是表单实际提交的POSTrequest 参数。map 值是 标签。在前面的示例中,给定三个知名城市的列表 和表单支持对象中的默认值,则 HTML 类似于以下内容:spring-doc.cadn.net.cn

Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>

如果您的应用程序希望通过内部代码处理城市(例如),则可以创建 代码,如下例所示:spring-doc.cadn.net.cn

Java
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
    Map<String, String> cityMap = new LinkedHashMap<>();
    cityMap.put("LDN", "London");
    cityMap.put("PRS", "Paris");
    cityMap.put("NYC", "New York");

    Map<String, Object> model = new HashMap<>();
    model.put("cityMap", cityMap);
    return model;
}
Kotlin
protected fun referenceData(request: HttpServletRequest): Map<String, *> {
    val cityMap = linkedMapOf(
            "LDN" to "London",
            "PRS" to "Paris",
            "NYC" to "New York"
    )
    return hashMapOf("cityMap" to cityMap)
}

现在,该代码会生成输出,其中 radio 值是相关代码,但 用户仍然会看到对用户更友好的城市名称,如下所示:spring-doc.cadn.net.cn

Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>
HTML 转义

前面描述的表单宏的默认用法会导致 HTML 元素为 HTML 4.01 compliant 的 v.web.xml文件,作为 由 Spring 的 bind 支持使用。使元素符合 XHTML 或覆盖 默认的 HTML 转义值,您可以在模板中指定两个变量(或在 您的模型,它们对您的模板可见)。指定 它们在模板中是可以稍后在 模板处理为表单中的不同字段提供不同的行为。spring-doc.cadn.net.cn

要切换到标记的 XHTML 合规性,请指定true对于 模型或上下文变量xhtmlCompliant,如下例所示:spring-doc.cadn.net.cn

<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>

处理此指令后,Spring 宏生成的任何元素现在都是 XHTML 顺从的。spring-doc.cadn.net.cn

以类似的方式,您可以为每个字段指定 HTML 转义,如下例所示:spring-doc.cadn.net.cn

<#-- until this point, default HTML escaping is used -->

<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name"/>

<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->

1.10.3. Groovy 标记

Groovy 标记模板引擎主要旨在生成类似 XML 的标记(XML、XHTML、HTML5 等),但您可以 使用它来生成任何基于文本的内容。Spring Framework 有一个内置的 将 Spring MVC 与 Groovy Markup 结合使用的集成。spring-doc.cadn.net.cn

Groovy Markup Template 引擎需要 Groovy 2.3.1+。
配置

下面的示例演示如何配置 Groovy 标记模板引擎:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.groovy();
    }

    // Configure the Groovy Markup Template Engine...

    @Bean
    public GroovyMarkupConfigurer groovyMarkupConfigurer() {
        GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
        configurer.setResourceLoaderPath("/WEB-INF/");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.groovy()
    }

    // Configure the Groovy Markup Template Engine...

    @Bean
    fun groovyMarkupConfigurer() = GroovyMarkupConfigurer().apply {
        resourceLoaderPath = "/WEB-INF/"
    }
}

以下示例显示了如何在 XML 中配置相同的内容:spring-doc.cadn.net.cn

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:groovy/>
</mvc:view-resolvers>

<!-- Configure the Groovy Markup Template Engine... -->
<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
示例

与传统的模板引擎不同,Groovy Markup 依赖于使用构建器的 DSL 语法。以下示例显示了 HTML 页面的示例模板:spring-doc.cadn.net.cn

yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
    head {
        meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
        title('My page')
    }
    body {
        p('This is an example of HTML contents')
    }
}

1.10.4. 脚本视图

Spring Framework 有一个内置的集成,用于将 Spring MVC 与任何 可以在 JSR-223 Java 脚本引擎上运行的模板库。我们测试了以下内容 在不同脚本引擎上模板化库:spring-doc.cadn.net.cn

脚本库 脚本引擎

车把spring-doc.cadn.net.cn

纳斯霍恩spring-doc.cadn.net.cn

胡子spring-doc.cadn.net.cn

纳斯霍恩spring-doc.cadn.net.cn

反应spring-doc.cadn.net.cn

纳斯霍恩spring-doc.cadn.net.cn

EJSspring-doc.cadn.net.cn

纳斯霍恩spring-doc.cadn.net.cn

雇员再培训局spring-doc.cadn.net.cn

JRubyspring-doc.cadn.net.cn

字符串模板spring-doc.cadn.net.cn

杰顿spring-doc.cadn.net.cn

Kotlin 脚本模板spring-doc.cadn.net.cn

Kotlinspring-doc.cadn.net.cn

集成任何其他脚本引擎的基本规则是,它必须实现ScriptEngineInvocable接口。
要求

您需要在 Classpath 上具有脚本引擎,其详细信息因脚本引擎而异:spring-doc.cadn.net.cn

您需要有脚本模板库。对 JavaScript 执行此作的一种方法是 通过 WebJars 进行。spring-doc.cadn.net.cn

脚本模板

您可以声明ScriptTemplateConfigurerbean 指定要使用的脚本引擎, 要加载的脚本文件、要调用的函数来渲染模板,等等。 以下示例使用 Mustache 模板和 Nashorn JavaScript 引擎:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.scriptTemplate();
    }

    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("mustache.js");
        configurer.setRenderObject("Mustache");
        configurer.setRenderFunction("render");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }

    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("mustache.js")
        renderObject = "Mustache"
        renderFunction = "render"
    }
}

下面的示例显示了 XML 中的相同排列:spring-doc.cadn.net.cn

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:script-template/>
</mvc:view-resolvers>

<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
    <mvc:script location="mustache.js"/>
</mvc:script-template-configurer>

对于 Java 和 XML 配置,控制器看起来没有什么不同,如下例所示:spring-doc.cadn.net.cn

Java
@Controller
public class SampleController {

    @GetMapping("/sample")
    public String test(Model model) {
        model.addAttribute("title", "Sample title");
        model.addAttribute("body", "Sample body");
        return "template";
    }
}
Kotlin
@Controller
class SampleController {

    @GetMapping("/sample")
    fun test(model: Model): String {
        model["title"] = "Sample title"
        model["body"] = "Sample body"
        return "template"
    }
}

以下示例显示了 Mustache 模板:spring-doc.cadn.net.cn

<html>
    <head>
        <title>{{title}}</title>
    </head>
    <body>
        <p>{{body}}</p>
    </body>
</html>

使用以下参数调用 render 函数:spring-doc.cadn.net.cn

Mustache.render()与此签名本机兼容,因此您可以直接调用它。spring-doc.cadn.net.cn

如果您的模板技术需要一些自定义,则可以提供一个脚本,该脚本 实现自定义 render 函数。例如,Handlerbars 需要在使用模板之前编译模板,并且需要一个 polyfill 来模拟一些 服务器端脚本引擎中不可用的浏览器工具。spring-doc.cadn.net.cn

以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.scriptTemplate();
    }

    @Bean
    public ScriptTemplateConfigurer configurer() {
        ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
        configurer.setEngineName("nashorn");
        configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
        configurer.setRenderFunction("render");
        configurer.setSharedEngine(false);
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }

    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("polyfill.js", "handlebars.js", "render.js")
        renderFunction = "render"
        isSharedEngine = false
    }
}
设置sharedEngineproperty 设置为false在使用非线程安全时是必需的 具有非并发性模板库的脚本引擎,例如 Handlebars 或 React 在 Nashorn 上运行。在这种情况下,由于此错误,需要 Java SE 8 Update 60,但通常是 建议在任何情况下使用最新的 Java SE 补丁版本。

polyfill.js仅定义window对象才能正常运行,如下所示:spring-doc.cadn.net.cn

var window = {};

这个基本render.jsimplementation 在使用模板之前对其进行编译。生产就绪型 implementation 还应存储任何重复使用的缓存模板或预编译的模板。 您可以在脚本端执行此作(并处理您需要的任何自定义 — 管理 模板引擎配置)。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

function render(template, model) {
    var compiledTemplate = Handlebars.compile(template);
    return compiledTemplate(model);
}

查看 Spring Framework 单元测试、Java资源。 了解更多配置示例。spring-doc.cadn.net.cn

1.10.5. JSP 和 JSTL

Spring Framework 具有用于将 Spring MVC 与 JSP 和 JSTL 一起使用的内置集成。spring-doc.cadn.net.cn

View 解析程序

使用 JSP 进行开发时,通常会声明InternalResourceViewResolver豆。spring-doc.cadn.net.cn

InternalResourceViewResolver可用于分派到任何 Servlet 资源,但在 特别是对于 JSP 来说。作为最佳实践,我们强烈建议将 JSP 文件放在 在'WEB-INF'目录中,因此客户端无法直接访问。spring-doc.cadn.net.cn

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
JSP 与 JSTL

使用 JSP 标准标记库 (JSTL) 时,必须使用特殊的视图类JstlView,因为 JSTL 需要先做一些准备,然后才能使用 I18N 功能 工作。spring-doc.cadn.net.cn

Spring 的 JSP 标记库

Spring 提供请求参数到命令对象的数据绑定,如 前面的章节。促进 JSP 页面的开发 data binding 功能,Spring 提供了一些标签,使事情变得更加容易。都 Spring 标签具有 HTML 转义功能,用于启用或禁用字符转义。spring-doc.cadn.net.cn

spring.tld标记库描述符 (TLD) 包含在spring-webmvc.jar. 有关各个标记的全面参考,请浏览 API 参考或查看标记库描述。spring-doc.cadn.net.cn

Spring 的表单标记库

从版本 2.0 开始, Spring 提供了一组全面的数据绑定感知标签,用于 在使用 JSP 和 Spring Web MVC 时处理表单元素。每个标签都支持 其对应的 HTML 标签对应的属性集,使标签 熟悉且直观易用。标记生成的 HTML 符合 HTML 4.01/XHTML 1.0 标准。spring-doc.cadn.net.cn

与其他表单/输入标记库不同, Spring 的表单标记库与 Spring Web MVC,为标签提供对命令对象和引用数据的 控制器处理。正如我们在以下示例中所示,表单标记使 JSP 更易于开发、阅读和维护。spring-doc.cadn.net.cn

我们将浏览 form 标记,并查看如何使用每个标记的示例。我们有 包含生成的 HTML 片段,其中某些标记需要进一步注释。spring-doc.cadn.net.cn

配置

表单标记库捆绑在spring-webmvc.jar.库描述符为 叫spring-form.tld.spring-doc.cadn.net.cn

要使用此库中的标记,请将以下指令添加到 JSP 的顶部 页:spring-doc.cadn.net.cn

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

哪里form是要用于此库中的标签的标签名称前缀。spring-doc.cadn.net.cn

表单标签

此标签呈现一个 HTML“form”元素,并公开一个指向内部标签的绑定路径 捆绑。它将命令对象放在PageContext以便 Command 对象可以 通过 inner 标签访问。此库中的所有其他标签都是form标记。spring-doc.cadn.net.cn

假设我们有一个名为User.它是一个具有属性 如firstNamelastName.我们可以将其用作 form 控制器,该控制器返回form.jsp.以下示例显示了form.jsp能 肖:spring-doc.cadn.net.cn

<form:form>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

firstNamelastName值是从放置在 这PageContext通过页面控制器。继续阅读以查看更复杂的示例 如何将 inner 标记与form标记。spring-doc.cadn.net.cn

下面的清单显示了生成的 HTML,它看起来像一个标准表单:spring-doc.cadn.net.cn

<form method="POST">
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value="Harry"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value="Potter"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

前面的 JSP 假定表单支持对象的变量名称为command.如果您已将 form-backing 对象以其他名称放入模型中 (绝对是最佳实践),您可以将表单绑定到命名变量,因为 以下示例显示:spring-doc.cadn.net.cn

<form:form modelAttribute="user">
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>
input标记

此标签呈现 HTMLinput元素,其绑定值和type='text'默认情况下。 有关此标签的示例,请参阅 Form 标签。您还可以使用 HTML5 特定的类型,例如email,tel,date等。spring-doc.cadn.net.cn

checkbox标记

此标签呈现 HTMLinput标签与type设置为checkbox.spring-doc.cadn.net.cn

假设我们的User具有首选项,例如新闻通讯订阅和 爱好。以下示例显示了Preferences类:spring-doc.cadn.net.cn

Java
public class Preferences {

    private boolean receiveNewsletter;
    private String[] interests;
    private String favouriteWord;

    public boolean isReceiveNewsletter() {
        return receiveNewsletter;
    }

    public void setReceiveNewsletter(boolean receiveNewsletter) {
        this.receiveNewsletter = receiveNewsletter;
    }

    public String[] getInterests() {
        return interests;
    }

    public void setInterests(String[] interests) {
        this.interests = interests;
    }

    public String getFavouriteWord() {
        return favouriteWord;
    }

    public void setFavouriteWord(String favouriteWord) {
        this.favouriteWord = favouriteWord;
    }
}
Kotlin
class Preferences(
        var receiveNewsletter: Boolean,
        var interests: StringArray,
        var favouriteWord: String
)

相应的form.jsp可能类似于以下内容:spring-doc.cadn.net.cn

<form:form>
    <table>
        <tr>
            <td>Subscribe to newsletter?:</td>
            <%-- Approach 1: Property is of type java.lang.Boolean --%>
            <td><form:checkbox path="preferences.receiveNewsletter"/></td>
        </tr>

        <tr>
            <td>Interests:</td>
            <%-- Approach 2: Property is of an array or of type java.util.Collection --%>
            <td>
                Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
                Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
                Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
            </td>
        </tr>

        <tr>
            <td>Favourite Word:</td>
            <%-- Approach 3: Property is of type java.lang.Object --%>
            <td>
                Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
            </td>
        </tr>
    </table>
</form:form>

有三种方法可以实现checkbox标签,它应该可以满足您的所有复选框需求。spring-doc.cadn.net.cn

  • 方法一:当绑定值为java.lang.Booleaninput(checkbox)标记为checked如果 bound 值为true.这value属性对应于setValue(Object)value 属性。spring-doc.cadn.net.cn

  • 方法二:当绑定值为arrayjava.util.Collectioninput(checkbox)标记为checked如果配置的setValue(Object)value 为 存在于 Bound 中Collection.spring-doc.cadn.net.cn

  • 方法三:对于任何其他绑定值类型,input(checkbox)标记为checked如果配置的setValue(Object)等于 bound 值。spring-doc.cadn.net.cn

请注意,无论采用哪种方法,都会生成相同的 HTML 结构。以下内容 HTML 代码段定义了一些复选框:spring-doc.cadn.net.cn

<tr>
    <td>Interests:</td>
    <td>
        Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
        Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
        Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
        <input type="hidden" value="1" name="_preferences.interests"/>
    </td>
</tr>

您可能不希望在每个复选框后看到额外的隐藏字段。 如果未选中 HTML 页面中的复选框,则其值不会发送到 server 作为 HTTP 请求参数的一部分,因此我们需要一个 HTML 中此怪癖的解决方法,以便 Spring 表单数据绑定正常工作。这checkbox标签遵循现有的 Spring 约定,即包含一个隐藏的参数 为每个复选框添加下划线 () 前缀。通过这样做,您可以有效地 告诉 Spring “复选框在表单中可见,我希望我的对象 无论如何,表单数据都会绑定以反映复选框的状态。_spring-doc.cadn.net.cn

checkboxes标记

此标签呈现多个 HTMLinput标签中带有type设置为checkbox.spring-doc.cadn.net.cn

本节基于前面的示例。checkboxtag 部分。有时,您更喜欢 不必在 JSP 页中列出所有可能的爱好。您宁愿提供 一个可用选项的列表,并将其传递给标签。那就是 目的checkboxes标记。您可以传入Array一个ListMap包含 的items财产。通常,绑定属性是 集合,以便它可以保存用户选择的多个值。以下示例 显示了使用此标记的 JSP:spring-doc.cadn.net.cn

<form:form>
    <table>
        <tr>
            <td>Interests:</td>
            <td>
                <%-- Property is of an array or of type java.util.Collection --%>
                <form:checkboxes path="preferences.interests" items="${interestList}"/>
            </td>
        </tr>
    </table>
</form:form>

此示例假定interestList是一个List可用作 Model 属性 ,其中包含要从中选择的值的字符串。如果您使用Map, 映射条目键用作值,映射条目的值用作 要显示的标签。您还可以使用自定义对象,您可以在其中提供 value 的属性名称itemValue和标签itemLabel.spring-doc.cadn.net.cn

radiobutton标记

此标签呈现 HTMLinput元素替换为type设置为radio.spring-doc.cadn.net.cn

典型的使用模式涉及绑定到同一属性的多个标记实例 但具有不同的值,如下例所示:spring-doc.cadn.net.cn

<tr>
    <td>Sex:</td>
    <td>
        Male: <form:radiobutton path="sex" value="M"/> <br/>
        Female: <form:radiobutton path="sex" value="F"/>
    </td>
</tr>
radiobuttons标记

此标签呈现多个 HTMLinput元素替换为type设置为radio.spring-doc.cadn.net.cn

checkboxes标记,您可能希望 将 available options 作为运行时变量传入。对于此用法,您可以使用radiobuttons标记。您传入一个Array一个ListMap,其中包含 的items财产。如果您使用Map,则映射入口键为 used as the value 和 map entry 的值用作要显示的标签。 您还可以使用自定义对象,您可以在其中为值提供属性名称 通过使用itemValue和标签itemLabel,如下例所示:spring-doc.cadn.net.cn

<tr>
    <td>Sex:</td>
    <td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>
password标记

此标签呈现 HTMLinput标记中,并将类型设置为password替换为绑定值。spring-doc.cadn.net.cn

<tr>
    <td>Password:</td>
    <td>
        <form:password path="password"/>
    </td>
</tr>

请注意,默认情况下,不会显示 password 值。如果您确实希望使用 password 值,则可以设置showPassword属性设置为true,如下例所示:spring-doc.cadn.net.cn

<tr>
    <td>Password:</td>
    <td>
        <form:password path="password" value="^76525bvHGq" showPassword="true"/>
    </td>
</tr>
select标记

此标签呈现 HTML 'select' 元素。它支持将数据绑定到选定的 选项以及使用嵌套的optionoptions标签。spring-doc.cadn.net.cn

假设User有一个技能列表。相应的 HTML 可能如下所示:spring-doc.cadn.net.cn

<tr>
    <td>Skills:</td>
    <td><form:select path="skills" items="${skills}"/></td>
</tr>

如果User’sskill 在 Herbology 中,则 'Skills' 行的 HTML 源可以是 如下:spring-doc.cadn.net.cn

<tr>
    <td>Skills:</td>
    <td>
        <select name="skills" multiple="true">
            <option value="Potions">Potions</option>
            <option value="Herbology" selected="selected">Herbology</option>
            <option value="Quidditch">Quidditch</option>
        </select>
    </td>
</tr>
option标记

此标签呈现 HTMLoption元素。它设置selected,基于边界 价值。以下 HTML 显示了它的典型输出:spring-doc.cadn.net.cn

<tr>
    <td>House:</td>
    <td>
        <form:select path="house">
            <form:option value="Gryffindor"/>
            <form:option value="Hufflepuff"/>
            <form:option value="Ravenclaw"/>
            <form:option value="Slytherin"/>
        </form:select>
    </td>
</tr>

如果User’shouse 位于格兰芬多中,则 'House' 行的 HTML 源将为 如下:spring-doc.cadn.net.cn

<tr>
    <td>House:</td>
    <td>
        <select name="house">
            <option value="Gryffindor" selected="selected">Gryffindor</option> (1)
            <option value="Hufflepuff">Hufflepuff</option>
            <option value="Ravenclaw">Ravenclaw</option>
            <option value="Slytherin">Slytherin</option>
        </select>
    </td>
</tr>
1 请注意,添加了selected属性。
options标记

此标签呈现 HTMLoption元素。它将selected属性 基于 Bound 值。以下 HTML 显示了它的典型输出:spring-doc.cadn.net.cn

<tr>
    <td>Country:</td>
    <td>
        <form:select path="country">
            <form:option value="-" label="--Please Select"/>
            <form:options items="${countryList}" itemValue="code" itemLabel="name"/>
        </form:select>
    </td>
</tr>

如果User居住在英国,则“Country”行的 HTML 源如下所示:spring-doc.cadn.net.cn

<tr>
    <td>Country:</td>
    <td>
        <select name="country">
            <option value="-">--Please Select</option>
            <option value="AT">Austria</option>
            <option value="UK" selected="selected">United Kingdom</option> (1)
            <option value="US">United States</option>
        </select>
    </td>
</tr>
1 请注意,添加了selected属性。

如前面的示例所示,option标签与options标记 生成相同的标准 HTML,但允许您在 仅用于显示(属于它的位置)的 JSP,例如 示例:“-- 请选择”。spring-doc.cadn.net.cn

items属性通常填充 item 对象的集合或数组。itemValueitemLabel引用这些 Item 对象的 bean 属性,如果 指定。否则,item 对象本身将转换为字符串。或者 您可以指定Mapof items,在这种情况下,映射键被解释为 option values 和 map 值对应于选项标签。如果itemValueitemLabel(或两者兼而有之) 恰好也被指定,则 item 值属性适用于 map 键,并且 item label 属性适用于 map 值。spring-doc.cadn.net.cn

textarea标记

此标签呈现 HTMLtextarea元素。以下 HTML 显示了它的典型输出:spring-doc.cadn.net.cn

<tr>
    <td>Notes:</td>
    <td><form:textarea path="notes" rows="3" cols="20"/></td>
    <td><form:errors path="notes"/></td>
</tr>
hidden标记

此标签呈现 HTMLinput标签与type设置为hidden替换为绑定值。提交 未绑定的隐藏值,请使用 HTMLinput标签与type设置为hidden. 以下 HTML 显示了它的典型输出:spring-doc.cadn.net.cn

<form:hidden path="house"/>

如果我们选择提交house值设置为隐藏值,则 HTML 将如下所示:spring-doc.cadn.net.cn

<input name="house" type="hidden" value="Gryffindor"/>
errors标记

此标签在 HTML 中呈现字段错误span元素。它提供对错误的访问 在您的控制器中创建的,或者是由与 您的控制器。spring-doc.cadn.net.cn

假设我们想要显示firstNamelastName字段。我们有一个 Validator 用于User类 叫UserValidator,如下例所示:spring-doc.cadn.net.cn

Java
public class UserValidator implements Validator {

    public boolean supports(Class candidate) {
        return User.class.isAssignableFrom(candidate);
    }

    public void validate(Object obj, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
    }
}
Kotlin
class UserValidator : Validator {

    override fun supports(candidate: Class<*>): Boolean {
        return User::class.java.isAssignableFrom(candidate)
    }

    override fun validate(obj: Any, errors: Errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.")
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.")
    }
}

form.jsp可能如下所示:spring-doc.cadn.net.cn

<form:form>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
            <%-- Show errors for firstName field --%>
            <td><form:errors path="firstName"/></td>
        </tr>

        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
            <%-- Show errors for lastName field --%>
            <td><form:errors path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

如果我们提交的表单在firstNamelastName领域 HTML 将如下所示:spring-doc.cadn.net.cn

<form method="POST">
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value=""/></td>
            <%-- Associated errors to firstName field displayed --%>
            <td><span name="firstName.errors">Field is required.</span></td>
        </tr>

        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value=""/></td>
            <%-- Associated errors to lastName field displayed --%>
            <td><span name="lastName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

如果我们想显示给定页面的整个错误列表怎么办?下一个例子 显示errors标签还支持一些基本的通配符功能。spring-doc.cadn.net.cn

以下示例在页面顶部显示错误列表,后跟 字段旁边的特定于字段的错误:spring-doc.cadn.net.cn

<form:form>
    <form:errors path="*" cssClass="errorBox"/>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
            <td><form:errors path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
            <td><form:errors path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

HTML 将如下所示:spring-doc.cadn.net.cn

<form method="POST">
    <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value=""/></td>
            <td><span name="firstName.errors">Field is required.</span></td>
        </tr>

        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value=""/></td>
            <td><span name="lastName.errors">Field is required.</span></td>
        </tr>
        <tr>
            <td colspan="3">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

spring-form.tld标记库描述符 (TLD) 包含在spring-webmvc.jar. 有关各个标记的全面参考,请浏览 API 参考或查看标记库描述。spring-doc.cadn.net.cn

HTTP 方法转换

REST 的一个关键原则是使用“统一接口”。这意味着所有 可以使用相同的四种 HTTP 方法作资源 (URL):GET、PUT、POST、 和 DELETE 的 DELETE 命令。对于每种方法,HTTP 规范定义了确切的语义。为 例如,GET 应该始终是一个安全的作,这意味着它没有副作用, PUT 或 DELETE 应该是幂等的,这意味着您可以重复这些作 一遍又一遍,但最终结果应该是相同的。虽然 HTTP 定义了这些 四种方法,HTML 只支持两种:GET 和 POST。幸运的是,有两种可能 解决方法:您可以使用 JavaScript 执行 PUT 或 DELETE,也可以执行 POST 使用 “real” 方法作为附加参数(建模为 HTML 表单)。Spring的HiddenHttpMethodFilter使用后一个技巧。这 filter 是一个普通的 Servlet 过滤器,因此,它可以与任何 Web 框架(不仅仅是 Spring MVC)。将此过滤器添加到您的 web.xml,然后 POST 带有隐藏的methodparameter 转换为对应的 HTTP 方法 请求。spring-doc.cadn.net.cn

为了支持 HTTP 方法转换,更新了 Spring MVC 表单标记以支持设置 HTTP 方法。例如,以下代码片段来自 Pet Clinic 示例:spring-doc.cadn.net.cn

<form:form method="delete">
    <p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

前面的示例执行 HTTP POST,其中隐藏了 “真正的” DELETE 方法 请求参数。它由HiddenHttpMethodFilter,它在 web.xml,如下例所示:spring-doc.cadn.net.cn

<filter>
    <filter-name>httpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpMethodFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

以下示例显示了相应的@Controller方法:spring-doc.cadn.net.cn

Java
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
    this.clinic.deletePet(petId);
    return "redirect:/owners/" + ownerId;
}
Kotlin
@RequestMapping(method = [RequestMethod.DELETE])
fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String {
    clinic.deletePet(petId)
    return "redirect:/owners/$ownerId"
}
HTML5 标签

Spring 表单标签库允许输入动态属性,这意味着您可以 输入任何特定于 HTML5 的属性。spring-doc.cadn.net.cn

表单input标签支持输入 type 属性,而不是text.这是 旨在允许呈现新的 HTML5 特定输入类型,例如email,date,range等。请注意,输入type='text'不是必需的,因为text是默认类型。spring-doc.cadn.net.cn

1.10.6. 瓦片

您可以在 Web 中集成 Tiles - 就像任何其他视图技术一样 使用 Spring 的应用程序。本节以广泛的方式描述了如何执行此作。spring-doc.cadn.net.cn

本节重点介绍 Spring 对 Tiles 版本 3 的支持。org.springframework.web.servlet.view.tiles3包。
依赖

为了能够使用 Tiles,您必须添加对 Tiles 版本 3.0.1 或更高版本的依赖项 及其对项目的传递依赖项spring-doc.cadn.net.cn

配置

为了能够使用 Tiles,您必须使用包含定义的文件对其进行配置 (有关定义和其他 Tiles 概念的基本信息,请参阅 https://tiles.apache.org)。在 Spring 中,这是通过使用TilesConfigurer. 以下示例ApplicationContext配置显示了如何做到这一点:spring-doc.cadn.net.cn

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/defs/general.xml</value>
            <value>/WEB-INF/defs/widgets.xml</value>
            <value>/WEB-INF/defs/administrator.xml</value>
            <value>/WEB-INF/defs/customer.xml</value>
            <value>/WEB-INF/defs/templates.xml</value>
        </list>
    </property>
</bean>

前面的示例定义了 5 个包含定义的文件。文件全部 位于WEB-INF/defs目录。在初始化WebApplicationContext, 加载文件,并初始化 Definitions Factory。之后 完成后,定义文件中包含的 Tiles 可以用作 Spring Web 应用程序。为了能够使用这些视图,您必须有一个ViewResolver与 Spring 中的任何其他视图技术一样:通常是一个方便的TilesViewResolver.spring-doc.cadn.net.cn

您可以通过添加下划线,然后 区域设置,如下例所示:spring-doc.cadn.net.cn

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/defs/tiles.xml</value>
            <value>/WEB-INF/defs/tiles_fr_FR.xml</value>
        </list>
    </property>
</bean>

在上述配置中,tiles_fr_FR.xml用于fr_FR现场 和tiles.xml默认使用。spring-doc.cadn.net.cn

由于下划线用于表示区域设置,因此我们建议不要使用 否则,它们将显示在 Tiles 定义的文件名中。
UrlBasedViewResolver

UrlBasedViewResolver实例化给定的viewClass对于每个视图,它必须 解决。以下 Bean 定义了一个UrlBasedViewResolver:spring-doc.cadn.net.cn

<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>
SimpleSpringPreparerFactorySpringBeanPreparerFactory

作为一项高级功能,Spring 还支持两个特殊的 TilePreparerFactory实现。有关如何使用的详细信息,请参阅 Tiles 文档ViewPreparerTiles 定义文件中的引用。spring-doc.cadn.net.cn

您可以指定SimpleSpringPreparerFactory自动装配ViewPreparer基于 指定的 preparer 类,应用 Spring 的容器回调以及应用 配置的 Spring BeanPostProcessors。如果 Spring 的上下文范围的注释配置具有 已激活,中的 AnnotationsViewPreparer类被自动检测并 应用的。请注意,这需要 Tiles 定义文件中的 preparer 类,因为 默认的PreparerFactory确实。spring-doc.cadn.net.cn

您可以指定SpringBeanPreparerFactory要对指定的编制者名称进行作(而不是 类),从 DispatcherServlet 的 应用程序上下文。完整的 bean 创建过程由 Spring 控制 application 上下文,允许使用显式依赖注入 configuration、作用域 bean 等。请注意,您需要定义一个 Spring bean 定义 对于每个编制者名称(在 Tiles 定义中使用)。以下示例显示了 如何定义SpringBeanPreparerFactory属性TilesConfigurer豆:spring-doc.cadn.net.cn

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/defs/general.xml</value>
            <value>/WEB-INF/defs/widgets.xml</value>
            <value>/WEB-INF/defs/administrator.xml</value>
            <value>/WEB-INF/defs/customer.xml</value>
            <value>/WEB-INF/defs/templates.xml</value>
        </list>
    </property>

    <!-- resolving preparer names as Spring bean definition names -->
    <property name="preparerFactoryClass"
            value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>

</bean>

1.10.7. RSS 和 Atom

AbstractAtomFeedViewAbstractRssFeedView继承自AbstractFeedView基类,分别用于提供 Atom 和 RSS Feed 视图。他们 基于 ROME 项目,位于 包org.springframework.web.servlet.view.feed.spring-doc.cadn.net.cn

AbstractAtomFeedView要求您实现buildFeedEntries()method 和 (可选)覆盖buildFeedMetadata()方法(默认实现为 empty) 的 Null S以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
public class SampleContentAtomView extends AbstractAtomFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model,
            Feed feed, HttpServletRequest request) {
        // implementation omitted
    }

    @Override
    protected List<Entry> buildFeedEntries(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        // implementation omitted
    }
}
Kotlin
class SampleContentAtomView : AbstractAtomFeedView() {

    override fun buildFeedMetadata(model: Map<String, Any>,
            feed: Feed, request: HttpServletRequest) {
        // implementation omitted
    }

    override fun buildFeedEntries(model: Map<String, Any>,
            request: HttpServletRequest, response: HttpServletResponse): List<Entry> {
        // implementation omitted
    }
}

类似的要求也适用于实施AbstractRssFeedView,如下例所示:spring-doc.cadn.net.cn

Java
public class SampleContentRssView extends AbstractRssFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model,
            Channel feed, HttpServletRequest request) {
        // implementation omitted
    }

    @Override
    protected List<Item> buildFeedItems(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        // implementation omitted
    }
}
Kotlin
class SampleContentRssView : AbstractRssFeedView() {

    override fun buildFeedMetadata(model: Map<String, Any>,
                                feed: Channel, request: HttpServletRequest) {
        // implementation omitted
    }

    override fun buildFeedItems(model: Map<String, Any>,
            request: HttpServletRequest, response: HttpServletResponse): List<Item> {
        // implementation omitted
    }
}

buildFeedItems()buildFeedEntries()方法传入 HTTP 请求,以 您需要访问 Locale。HTTP 响应仅针对 Cookie 或其他 HTTP 标头。源会自动写入响应 object 返回。spring-doc.cadn.net.cn

有关创建 Atom 视图的示例,请参阅 Alef Arends 的 Spring Team 博客文章。spring-doc.cadn.net.cn

1.10.8. PDF 和 Excel

Spring 提供了返回 HTML 以外的输出的方法,包括 PDF 和 Excel 电子表格。 本节介绍如何使用这些功能。spring-doc.cadn.net.cn

文档视图简介

HTML 页面并不总是用户查看模型输出的最佳方式。 Spring 使生成 PDF 文档或 Excel 电子表格变得简单 从模型数据动态获取。文档是视图,并从 服务器,以(希望)使客户端 PC 能够运行其 电子表格或 PDF 查看器应用程序作为响应。spring-doc.cadn.net.cn

为了使用 Excel 视图,您需要将 Apache POI 库添加到您的 Classpath 中。 对于 PDF 生成,您需要添加(最好)OpenPDF 库。spring-doc.cadn.net.cn

您应该使用最新版本的基础文档生成库 如果可能的话。特别是,我们强烈推荐 OpenPDF(例如,OpenPDF 1.2.12) 而不是过时的原始 iText 2.1.7,因为 OpenPDF 得到了积极维护并且 修复了不受信任的 PDF 内容的重要漏洞。
PDF 视图

单词列表的简单 PDF 视图可以扩展org.springframework.web.servlet.view.document.AbstractPdfView并实施buildPdfDocument()方法,如下例所示:spring-doc.cadn.net.cn

Java
public class PdfWordList extends AbstractPdfView {

    protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

        List<String> words = (List<String>) model.get("wordList");
        for (String word : words) {
            doc.add(new Paragraph(word));
        }
    }
}
Kotlin
class PdfWordList : AbstractPdfView() {

    override fun buildPdfDocument(model: Map<String, Any>, doc: Document, writer: PdfWriter,
            request: HttpServletRequest, response: HttpServletResponse) {

        val words = model["wordList"] as List<String>
        for (word in words) {
            doc.add(Paragraph(word))
        }
    }
}

控制器可以从外部视图定义返回此类视图 (按名称引用)或作为View实例。spring-doc.cadn.net.cn

Excel 视图

从 Spring Framework 4.2 开始,org.springframework.web.servlet.view.document.AbstractXlsView作为基础提供 类。它基于 Apache POI,具有专门的子类 (AbstractXlsxViewAbstractXlsxStreamingView),取代过时的AbstractExcelView类。spring-doc.cadn.net.cn

编程模型类似于AbstractPdfViewbuildExcelDocument()作为中心模板方法,并且控制器能够从 外部定义(按名称)或作为View实例。spring-doc.cadn.net.cn

1.10.9. Jackson

Spring 提供对 Jackson JSON 库的支持。spring-doc.cadn.net.cn

基于 Jackson 的 JSON MVC 视图

MappingJackson2JsonView使用 Jackson 库的ObjectMapper呈现响应 content 作为 JSON 格式。默认情况下,模型映射 特定于框架的类)编码为 JSON。如果 map 需要过滤,则可以指定一组特定的模型属性进行编码 通过使用modelKeys财产。您还可以使用extractValueFromSingleKeyModel属性直接提取和序列化单键模型中的值,而不是 than 作为模型属性的映射。spring-doc.cadn.net.cn

您可以根据需要使用 Jackson 提供的 附注。当您需要进一步控制时,您可以注入自定义ObjectMapper通过ObjectMapper属性,适用于需要提供自定义 JSON 的情况 特定类型的序列化器和反序列化器。spring-doc.cadn.net.cn

基于 Jackson 的 XML 视图

MappingJackson2XmlView使用 Jackson XML 扩展的 XmlMapper将响应内容呈现为 XML。如果模型包含多个条目,则应 使用modelKeybean 属性。如果 model 包含一个条目,它会自动序列化。spring-doc.cadn.net.cn

您可以根据需要使用 JAXB 或 Jackson 提供的 XML 映射来自定义 XML 映射 附注。当您需要进一步控制时,您可以注入自定义XmlMapper通过ObjectMapper属性,适用于自定义 XML 您需要为特定类型提供序列化器和反序列化器。spring-doc.cadn.net.cn

1.10.10. XML 封送

MarshallingView使用 XMLMarshaller(在org.springframework.oxmpackage) 将响应内容呈现为 XML。您可以将对象显式设置为 使用MarshallingView实例的modelKeybean 属性。或者 该视图迭代所有模型属性并封送支持的第一个类型 由Marshaller.有关org.springframework.oxm包中,请参阅使用 O/X 映射器封送 XMLspring-doc.cadn.net.cn

1.10.11. XSLT 视图

XSLT 是一种 XML 转换语言,在 Web 中作为一种流行的视图技术 应用。如果您的应用程序 自然会处理 XML,或者如果您的模型可以很容易地转换为 XML。以下内容 部分介绍如何将 XML 文档生成为模型数据,并使用 Spring Web MVC 应用程序中的 XSLT 进行验证。spring-doc.cadn.net.cn

此示例是一个简单的 Spring 应用程序,它在Controller并将其添加到模型映射中。将返回 map 以及视图 XSLT 视图的名称。有关 Spring Web MVC 的详细信息,请参见带注释的控制器Controller接口。XSLT 控制器将单词列表转换为简单的 XML 文档已准备好进行转换。spring-doc.cadn.net.cn

配置是简单 Spring Web 应用程序的标准配置:MVC 配置 必须定义一个XsltViewResolverbean 和常规 MVC 注释配置。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@EnableWebMvc
@ComponentScan
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public XsltViewResolver xsltViewResolver() {
        XsltViewResolver viewResolver = new XsltViewResolver();
        viewResolver.setPrefix("/WEB-INF/xsl/");
        viewResolver.setSuffix(".xslt");
        return viewResolver;
    }
}
Kotlin
@EnableWebMvc
@ComponentScan
@Configuration
class WebConfig : WebMvcConfigurer {

    @Bean
    fun xsltViewResolver() = XsltViewResolver().apply {
        setPrefix("/WEB-INF/xsl/")
        setSuffix(".xslt")
    }
}
控制器

我们还需要一个封装我们的单词生成逻辑的 Controller。spring-doc.cadn.net.cn

控制器逻辑封装在@Controller类中,使用 handler 方法定义如下:spring-doc.cadn.net.cn

Java
@Controller
public class XsltController {

    @RequestMapping("/")
    public String home(Model model) throws Exception {
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element root = document.createElement("wordList");

        List<String> words = Arrays.asList("Hello", "Spring", "Framework");
        for (String word : words) {
            Element wordNode = document.createElement("word");
            Text textNode = document.createTextNode(word);
            wordNode.appendChild(textNode);
            root.appendChild(wordNode);
        }

        model.addAttribute("wordList", root);
        return "home";
    }
}
Kotlin
import org.springframework.ui.set

@Controller
class XsltController {

    @RequestMapping("/")
    fun home(model: Model): String {
        val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
        val root = document.createElement("wordList")

        val words = listOf("Hello", "Spring", "Framework")
        for (word in words) {
            val wordNode = document.createElement("word")
            val textNode = document.createTextNode(word)
            wordNode.appendChild(textNode)
            root.appendChild(wordNode)
        }

        model["wordList"] = root
        return "home"
    }
}

到目前为止,我们只创建了一个 DOM 文档并将其添加到 Model 映射中。请注意,您 也可以将 XML 文件加载为Resource并使用它来代替自定义 DOM 文档。spring-doc.cadn.net.cn

有一些软件包可以自动 “domify” 一个对象图,但是,在 Spring 中,您可以完全灵活地创建 DOM 从您的模型中以您选择的任何方式。这会阻止 XML 播放的转换 在模型数据的结构中占有太大的比重,这在使用工具时很危险 来管理 DOMification 过程。spring-doc.cadn.net.cn

转型

最后,XsltViewResolver解析 “home” XSLT 模板文件并合并 DOM 文档添加到其中来生成我们的视图。如XsltViewResolver配置中,XSLT 模板位于war文件中的WEB-INF/xsl目录 并以xslt文件扩展名。spring-doc.cadn.net.cn

以下示例显示了 XSLT 转换:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" omit-xml-declaration="yes"/>

    <xsl:template match="/">
        <html>
            <head><title>Hello!</title></head>
            <body>
                <h1>My First Words</h1>
                <ul>
                    <xsl:apply-templates/>
                </ul>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="word">
        <li><xsl:value-of select="."/></li>
    </xsl:template>

</xsl:stylesheet>

前面的转换呈现为以下 HTML:spring-doc.cadn.net.cn

<html>
    <head>
        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Hello!</title>
    </head>
    <body>
        <h1>My First Words</h1>
        <ul>
            <li>Hello</li>
            <li>Spring</li>
            <li>Framework</li>
        </ul>
    </body>
</html>

1.11. MVC 配置

MVC Java 配置和 MVC XML 命名空间提供默认配置 适用于大多数应用程序,并提供配置 API 对其进行自定义。spring-doc.cadn.net.cn

对于更高级的自定义(在配置 API 中不可用), 请参见高级 Java 配置高级 XML 配置spring-doc.cadn.net.cn

您不需要了解 MVC Java 配置创建的基础 bean 和 MVC 命名空间。如果要了解更多信息,请参阅特殊 Bean 类型和 Web MVC 配置spring-doc.cadn.net.cn

1.11.1. 启用 MVC 配置

在 Java 配置中,您可以使用@EnableWebMvc用于启用 MVC 的注释 配置,如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig {
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig

在 XML 配置中,您可以使用<mvc:annotation-driven>元素以启用 MVC 配置,如下例所示:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

前面的示例注册了许多 Spring MVC 基础结构 bean 并适应依赖项 在 Classpath 上可用(例如,JSON、XML 等的有效负载转换器)。spring-doc.cadn.net.cn

1.11.2. MVC 配置 API

在 Java 配置中,您可以实现WebMvcConfigurer接口,作为 以下示例显示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    // Implement configuration methods...
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    // Implement configuration methods...
}

在 XML 中,您可以检查<mvc:annotation-driven/>.您可以 查看 Spring MVC XML 模式或使用 IDE 的代码补全功能,用于发现哪些属性和 子元素可用。spring-doc.cadn.net.cn

1.11.3. 类型转换

默认情况下,会安装各种数字和日期类型的格式化程序,以及支持 用于自定义@NumberFormat@DateTimeFormat在字段上。spring-doc.cadn.net.cn

要在 Java 配置中注册自定义格式化程序和转换器,请使用以下内容:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addFormatters(registry: FormatterRegistry) {
        // ...
    }
}

要在 XML 配置中执行相同的作,请使用以下内容:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven conversion-service="conversionService"/>

    <bean id="conversionService"
            class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="org.example.MyConverter"/>
            </set>
        </property>
        <property name="formatters">
            <set>
                <bean class="org.example.MyFormatter"/>
                <bean class="org.example.MyAnnotationFormatterFactory"/>
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.example.MyFormatterRegistrar"/>
            </set>
        </property>
    </bean>

</beans>

默认情况下,Spring MVC 在解析和格式化日期时会考虑请求 Locale 值。这适用于日期表示为带有 “input” 形式的字符串的表单 领域。但是,对于“日期”和“时间”表单字段,浏览器使用定义的固定格式 在 HTML 规范中。对于此类情况,可以按如下方式自定义日期和时间格式:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addFormatters(registry: FormatterRegistry) {
        val registrar = DateTimeFormatterRegistrar()
        registrar.setUseIsoFormat(true)
        registrar.registerFormatters(registry)
    }
}
FormatterRegistrarSPI 系列FormattingConversionServiceFactoryBean有关何时使用 FormatterRegistrar 实现。

1.11.4. 验证

默认情况下,如果存在 Bean Validation 在 Classpath(例如,Hibernate Validator)上,LocalValidatorFactoryBean是 注册为全局验证器以用于@ValidValidatedon controller 方法参数。spring-doc.cadn.net.cn

在 Java 配置中,您可以自定义全局Validator实例,作为 以下示例显示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public Validator getValidator() {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun getValidator(): Validator {
        // ...
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>

请注意,您也可以注册Validator实现,如下所示 示例显示:spring-doc.cadn.net.cn

Java
@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }
}
Kotlin
@Controller
class MyController {

    @InitBinder
    protected fun initBinder(binder: WebDataBinder) {
        binder.addValidators(FooValidator())
    }
}
如果您需要LocalValidatorFactoryBean注入某个位置,创建一个 bean 并 标记@Primary以避免与 MVC 配置中声明的冲突。

1.11.5. 拦截器

在 Java 配置中,您可以注册拦截器以应用于传入请求,例如 以下示例显示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor());
        registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(LocaleChangeInterceptor())
        registry.addInterceptor(ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**")
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
映射拦截器并不适合作为安全层,因为可能存在 对于与 annotated controller path matching不匹配,也可以匹配 trailing 斜杠和路径扩展,以及其他路径匹配选项。多 的选项已被弃用,但不匹配的可能性仍然存在。 通常,我们建议使用 Spring Security,它包括一个专用的 MvcRequestMatcher 以与 Spring MVC 路径匹配保持一致,并且还有一个安全防火墙可以阻止许多 URL 路径中不需要的字符。

1.11.6. 内容类型

您可以配置 Spring MVC 如何从请求中确定请求的媒体类型 (例如,Acceptheader、URL 路径扩展名、查询参数等)。spring-doc.cadn.net.cn

默认情况下,只有Acceptheader 中。spring-doc.cadn.net.cn

如果必须使用基于 URL 的内容类型解析,请考虑使用 query 参数 策略而不是路径扩展。请参阅 Suffix MatchSuffix Match 和 RFD 以了解 更多细节。spring-doc.cadn.net.cn

在 Java 配置中,您可以自定义请求的内容类型解析,因为 以下示例显示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureContentNegotiation(configurer: ContentNegotiationConfigurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON)
        configurer.mediaType("xml", MediaType.APPLICATION_XML)
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

1.11.7. 消息转换器

您可以自定义HttpMessageConverter在 Java 配置中,通过覆盖configureMessageConverters()(替换 Spring MVC 创建的默认转换器)或通过覆盖extendMessageConverters()(自定义默认转换器或向默认转换器添加其他转换器)。spring-doc.cadn.net.cn

以下示例使用自定义的ObjectMapper而不是默认的:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .modulesToInstall(new ParameterNamesModule());
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfiguration : WebMvcConfigurer {

    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        val builder = Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(SimpleDateFormat("yyyy-MM-dd"))
                .modulesToInstall(ParameterNamesModule())
        converters.add(MappingJackson2HttpMessageConverter(builder.build()))
        converters.add(MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()))

在前面的示例中,Jackson2ObjectMapperBuilder用于创建两者的通用配置MappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverter启用缩进后,自定义日期格式 以及jackson-module-parameter-names, 这增加了对访问参数名称的支持(Java 8 中新增的功能)。spring-doc.cadn.net.cn

此构建器自定义 Jackson 的默认属性,如下所示:spring-doc.cadn.net.cn

如果在 Classpath 中检测到以下众所周知的模块,它还会自动注册它们:spring-doc.cadn.net.cn

使用 Jackson XML 支持启用缩进需要woodstox-core-asldependency 之外jackson-dataformat-xml一。

其他有趣的 Jackson 模块可用:spring-doc.cadn.net.cn

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
        <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
            <property name="objectMapper" ref="xmlMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true"
      p:simpleDateFormat="yyyy-MM-dd"
      p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

1.11.8. 视图控制器

这是定义ParameterizableViewController那立即 在调用时转发到 View。您可以在没有 Java 控制器的静态情况下使用它 在 View 生成响应之前运行的 logic 来运行。spring-doc.cadn.net.cn

以下 Java 配置示例将请求转发到名为/home:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addViewControllers(registry: ViewControllerRegistry) {
        registry.addViewController("/").setViewName("home")
    }
}

以下示例实现了与前面示例相同的功能,但使用 XML,通过 使用<mvc:view-controller>元素:spring-doc.cadn.net.cn

<mvc:view-controller path="/" view-name="home"/>

如果@RequestMappingmethod 映射到任何 HTTP 方法的 URL,然后是视图 controller 不能用于处理相同的 URL。这是因为通过 URL 与 带注释的 controller 被认为是端点所有权的足够强的指示,因此 405 (METHOD_NOT_ALLOWED)、415 (UNSUPPORTED_MEDIA_TYPE) 或类似响应可以 发送到客户端以帮助调试。因此,建议避免 在带注解的控制器和视图控制器之间拆分 URL 处理。spring-doc.cadn.net.cn

1.11.9. 查看解析器

MVC 配置简化了视图解析程序的注册。spring-doc.cadn.net.cn

以下 Java 配置示例配置内容协商视图 使用 JSP 和 Jackson 作为默认值的解决方法View对于 JSON 渲染:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp();
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.enableContentNegotiation(MappingJackson2JsonView())
        registry.jsp()
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:jsp/>
</mvc:view-resolvers>

但请注意,FreeMarker、Tiles、Groovy Markup 和脚本模板也需要 底层 View 技术的配置。spring-doc.cadn.net.cn

MVC 命名空间提供专用元素。以下示例适用于 FreeMarker:spring-doc.cadn.net.cn

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>

在 Java 配置中,您可以添加相应的Configurer豆 如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.freeMarker().cache(false);
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/freemarker");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.enableContentNegotiation(MappingJackson2JsonView())
        registry.freeMarker().cache(false)
    }

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("/freemarker")
    }
}

1.11.10. 静态资源

此选项提供了一种从以下列表中的静态资源提供Resource基于位置。spring-doc.cadn.net.cn

在下一个示例中,给定一个以/resources,则相对路径为 用于查找和提供相对于/public在 Web 应用程序 root 或/static.这些资源的未来期为一年 expiration 以确保最大限度地使用浏览器缓存并减少 HTTP 请求 由浏览器创建。这Last-Modified信息推导自Resource#lastModified,以便支持 HTTP 条件请求"Last-Modified"头。spring-doc.cadn.net.cn

下面的清单显示了如何使用 Java 配置来实现这一点:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)))
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:resources mapping="/resources/**"
    location="/public, classpath:/static/"
    cache-period="31556926" />

资源处理程序还支持ResourceResolverimplementations 和ResourceTransformer实现 可用于创建用于处理优化资源的工具链。spring-doc.cadn.net.cn

您可以使用VersionResourceResolver对于基于 MD5 哈希的版本控制资源 URL 根据内容、固定应用程序版本或其他计算。一个ContentVersionStrategy(MD5 哈希) 是一个不错的选择 — 但有一些值得注意的例外,例如 与模块加载器一起使用的 JavaScript 资源。spring-doc.cadn.net.cn

以下示例演示如何使用VersionResourceResolver在 Java 配置中:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(VersionResourceResolver().addContentVersionStrategy("/**"))
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:resources mapping="/resources/**" location="/public/">
    <mvc:resource-chain resource-cache="true">
        <mvc:resolvers>
            <mvc:version-resolver>
                <mvc:content-version-strategy patterns="/**"/>
            </mvc:version-resolver>
        </mvc:resolvers>
    </mvc:resource-chain>
</mvc:resources>

然后,您可以使用ResourceUrlProvider重写 URL 并应用完整的解析器链,以及 transformers — 例如,插入版本。MVC 配置提供了一个ResourceUrlProviderbean 的 bean 中,以便可以将其注入到其他 bean 中。您还可以使用ResourceUrlEncodingFilter对于 Thymeleaf、JSP、FreeMarker 和其他带有 URL 标签的 恃HttpServletResponse#encodeURL.spring-doc.cadn.net.cn

请注意,当同时使用EncodedResourceResolver(例如,用于提供 gzip 压缩的或 brotli 编码的资源)和VersionResourceResolver,则必须按此顺序注册它们。 这可确保始终基于未编码的文件可靠地计算基于内容的版本。spring-doc.cadn.net.cn

对于 WebJar,/webjars/jquery/1.2.0/jquery.min.js是推荐且最有效的使用它们的方式。 相关资源位置使用 Spring Boot 开箱即用地配置(也可以配置 手动通过ResourceHandlerRegistry),并且不需要添加org.webjars:webjars-locator-coreDependency。spring-doc.cadn.net.cn

无版本的 URL,如/webjars/jquery/jquery.min.js通过WebJarsResourceResolverorg.webjars:webjars-locator-core库存在于类路径中,但代价是 类路径扫描可能会减慢应用程序启动速度。解析器可以将 URL 重写为 包括 jar 的版本,并且还可以与没有版本的传入 URL 进行匹配 — 例如,从/webjars/jquery/jquery.min.js/webjars/jquery/1.2.0/jquery.min.js.spring-doc.cadn.net.cn

基于ResourceHandlerRegistry提供更多选项 进行精细控制,例如上次修改的行为和优化的资源解析。

1.11.11. 默认 Servlet

Spring MVC 允许将DispatcherServletto (从而覆盖映射 ),同时仍然允许静态资源请求为 由容器的默认 Servlet 处理。它配置了一个/DefaultServletHttpRequestHandler其中 URL 映射为 且优先级最低 相对于其他 URL 映射。/**spring-doc.cadn.net.cn

此处理程序将所有请求转发到默认 Servlet。因此,它必须 按所有其他 URL 的顺序保持最后HandlerMappings.那就是 case 如果您使用<mvc:annotation-driven>.或者,如果您设置了 自有定制HandlerMapping实例,请务必将其orderproperty 设置为值 低于DefaultServletHttpRequestHandler,即Integer.MAX_VALUE.spring-doc.cadn.net.cn

以下示例显示如何使用默认设置启用该功能:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
        configurer.enable()
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:default-servlet-handler/>

覆盖 Servlet 映射的注意事项是,/RequestDispatcher对于 default Servlet 必须按名称而不是按路径检索。这DefaultServletHttpRequestHandler尝试自动检测 的默认 Servlet 容器,使用大多数主要 Servlet 的已知名称列表 容器(包括 Tomcat、Jetty、GlassFish、JBoss、Resin、WebLogic 和 WebSphere)。 如果默认 Servlet 已使用其他名称进行自定义配置,或者如果 在默认 Servlet 名称未知的情况下使用了不同的 Servlet 容器, 然后,您必须显式提供默认 Servlet 的名称,如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable("myCustomDefaultServlet");
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
        configurer.enable("myCustomDefaultServlet")
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>

1.11.12. 路径匹配

您可以自定义与路径匹配和 URL 处理相关的选项。 有关各个选项的详细信息,请参阅PathMatchConfigurerjavadoc 的spring-doc.cadn.net.cn

以下示例演示如何在 Java 配置中自定义路径匹配:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setPatternParser(new PathPatternParser())
            .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
    }

    private PathPatternParser patternParser() {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {

    override fun configurePathMatch(configurer: PathMatchConfigurer) {
        configurer
            .setPatternParser(patternParser)
            .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
    }

    fun patternParser(): PathPatternParser {
        //...
    }
}

以下示例显示了如何在 XML 中实现相同的配置:spring-doc.cadn.net.cn

<mvc:annotation-driven>
    <mvc:path-matching
        trailing-slash="false"
        path-helper="pathHelper"
        path-matcher="pathMatcher"/>
</mvc:annotation-driven>

<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

1.11.13. 高级 Java 配置

@EnableWebMvc进口DelegatingWebMvcConfiguration哪:spring-doc.cadn.net.cn

对于高级模式,您可以删除@EnableWebMvc并直接从DelegatingWebMvcConfiguration而不是实现WebMvcConfigurer, 如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    // ...
}
Kotlin
@Configuration
class WebConfig : DelegatingWebMvcConfiguration() {

    // ...
}

您可以将现有方法保留在WebConfig,但您现在也可以覆盖 Bean 声明 从基类中,您仍然可以拥有任意数量的其他WebMvcConfigurer上的 implementations 类路径。spring-doc.cadn.net.cn

1.11.14. 高级 XML 配置

MVC 命名空间没有高级模式。如果需要自定义 上的属性 一个 Bean,否则你可以使用BeanPostProcessor生命周期 弹簧之钩ApplicationContext,如下例所示:spring-doc.cadn.net.cn

Java
@Component
public class MyPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
        // ...
    }
}
Kotlin
@Component
class MyPostProcessor : BeanPostProcessor {

    override fun postProcessBeforeInitialization(bean: Any, name: String): Any {
        // ...
    }
}

请注意,您需要声明MyPostProcessor作为 Bean,在 XML 中显式或 通过让<component-scan/>声明。spring-doc.cadn.net.cn

1.12. HTTP/2 协议

需要 Servlet 4 容器来支持 HTTP/2,并且 Spring Framework 5 是兼容的 使用 Servlet API 4.从编程模型的角度来看,没有什么具体的 应用程序需要这样做。但是,有一些与服务器配置相关的注意事项。 有关更多详细信息,请参阅 HTTP/2 wiki 页面spring-doc.cadn.net.cn

Servlet API 确实公开了一个与 HTTP/2 相关的结构。您可以使用javax.servlet.http.PushBuilder主动将资源推送到客户端,以及 IT 支持作为@RequestMapping方法。spring-doc.cadn.net.cn

2. REST 客户端

本节介绍客户端访问 REST 终端节点的选项。spring-doc.cadn.net.cn

2.1.RestTemplate

RestTemplate是执行 HTTP 请求的同步客户端。它是原始的 Spring REST 客户端,并在底层 HTTP 客户端上公开了一个简单的模板方法 API 图书馆。spring-doc.cadn.net.cn

从 5.0 开始,RestTemplate处于维护模式,只有对 今后将接受更改和错误。请考虑使用 WebClient,它提供更现代的 API 和 支持同步、异步和流式处理方案。

2.2.WebClient

WebClient是执行 HTTP 请求的非阻塞响应式客户端。它是 在 5.0 中引入,并提供了RestTemplate,具有高效的 支持同步和异步以及流式处理方案。spring-doc.cadn.net.cn

RestTemplate,WebClient支持以下内容:spring-doc.cadn.net.cn

有关更多详细信息,请参阅 WebClientspring-doc.cadn.net.cn

3. 测试

本部分总结了spring-test用于 Spring MVC 应用程序。spring-doc.cadn.net.cn

  • Servlet API Mocks:用于单元测试控制器的 Servlet API 契约的模拟实现, 过滤器和其他 Web 组件。有关更多详细信息,请参阅 Servlet API 模拟对象。spring-doc.cadn.net.cn

  • TestContext 框架:支持在 JUnit 和 TestNG 测试中加载 Spring 配置, 包括跨测试方法对加载的配置进行高效缓存,并支持 加载一个WebApplicationContext替换为MockServletContext. 有关更多详细信息,请参阅 TestContext Frameworkspring-doc.cadn.net.cn

  • Spring MVC 测试:一个框架,也称为MockMvc,用于测试带注释的控制器 通过DispatcherServlet(即支持注释),并带有 Spring MVC 基础设施,但没有 HTTP 服务器。 有关更多详细信息,请参见 Spring MVC Testspring-doc.cadn.net.cn

  • 客户端 REST:spring-test提供MockRestServiceServer您可以将其用作 一个模拟服务器,用于测试内部使用RestTemplate. 有关更多详细信息,请参阅客户端 REST 测试spring-doc.cadn.net.cn

  • WebTestClient:专为测试 WebFlux 应用程序而构建,但也可用于 通过 HTTP 连接对任何服务器进行端到端集成测试。它是一个 非阻塞、响应式客户端,非常适合测试异步和流 场景。spring-doc.cadn.net.cn

4. 网络套接字

参考文档的这一部分涵盖了对 Servlet 堆栈、WebSocket 的支持 消息收发,包括原始 WebSocket 交互、通过 SockJS 进行的 WebSocket 仿真,以及 通过 STOMP 作为 WebSocket 上的子协议进行发布-订阅消息传递。spring-doc.cadn.net.cn

4.1. WebSocket 简介

WebSocket 协议 RFC 6455 提供了标准化的 在 Client 端和 Server 之间建立全双工双向通信通道的方法 通过单个 TCP 连接。它是与 HTTP 不同的 TCP 协议,但旨在 通过 HTTP 工作,使用端口 80 和 443,并允许重复使用现有防火墙规则。spring-doc.cadn.net.cn

WebSocket 交互以使用 HTTPUpgrade页眉 进行升级,或者在本例中切换到 WebSocket 协议。以下示例 显示了这样的交互:spring-doc.cadn.net.cn

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket (1)
Connection: Upgrade (2)
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
1 Upgrade页眉。
2 使用Upgrade连接。

支持 WebSocket 的服务器返回输出,而不是通常的 200 状态代码 类似于以下内容:spring-doc.cadn.net.cn

HTTP/1.1 101 Switching Protocols (1)
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
1 协议切换

握手成功后,HTTP 升级请求的基础 TCP 套接字将保留 open 以继续发送和接收消息。spring-doc.cadn.net.cn

有关 WebSockets 工作原理的完整介绍超出了本文档的范围。 请参阅 RFC 6455、HTML5 的 WebSocket 章节或许多介绍中的任何一个 Web 上的教程。spring-doc.cadn.net.cn

请注意,如果 WebSocket 服务器在 Web 服务器(例如 nginx)后面运行,则 可能需要对其进行配置,以便将 WebSocket 升级请求传递给 WebSocket 服务器。同样,如果应用程序在云环境中运行,请检查 云提供商与 WebSocket 支持相关的说明。spring-doc.cadn.net.cn

4.1.1. HTTP 与 WebSocket

尽管 WebSocket 设计为与 HTTP 兼容并以 HTTP 请求开头, 重要的是要了解这两种协议导致非常不同的结果 体系结构和应用程序编程模型。spring-doc.cadn.net.cn

在 HTTP 和 REST 中,应用程序被建模为多个 URL。要与应用程序交互, 客户端以请求-响应样式访问这些 URL。服务器将请求路由到 基于 HTTP URL、方法和标头的适当处理程序。spring-doc.cadn.net.cn

相比之下,在 WebSockets 中,初始连接通常只有一个 URL。 随后,所有应用程序消息都在同一 TCP 连接上流动。这指向 一种完全不同的异步、事件驱动的消息传递架构。spring-doc.cadn.net.cn

WebSocket 也是一种低级传输协议,与 HTTP 不同,它没有规定 消息内容的任何语义。这意味着无法路由或处理 一条消息,除非客户端和服务器在消息语义上达成一致。spring-doc.cadn.net.cn

WebSocket 客户端和服务器可以协商使用更高级别的消息传递协议 (例如,STOMP),通过Sec-WebSocket-Protocol标头。 如果没有这些,他们需要提出自己的惯例。spring-doc.cadn.net.cn

4.1.2. 何时使用 WebSockets

WebSockets 可以使网页具有动态和交互性。但是,在许多情况下, Ajax 和 HTTP 流式处理或长轮询的组合可以提供简单且 有效的解决方案。spring-doc.cadn.net.cn

例如,新闻、邮件和社交源需要动态更新,但可能需要 每隔几分钟这样做一次完全可以。协作、游戏和金融应用程序 另一方面,需要更接近实时。spring-doc.cadn.net.cn

延迟本身并不是决定因素。如果消息量相对较低(例如, 监控网络故障)HTTP 流或轮询可以提供有效的解决方案。 低延迟、高频率和高容量的组合造就了最好的 case 来使用 WebSocket。spring-doc.cadn.net.cn

另请记住,在 Internet 上,您无法控制的限制性代理 可能会排除 WebSocket 交互,因为它们未配置为传递Upgrade标头,或者因为它们关闭了看起来空闲的长期连接。这 意味着将 WebSocket 用于防火墙内的内部应用程序是 比面向公众的应用程序更直接的决定。spring-doc.cadn.net.cn

4.2. WebSocket API

Spring Framework 提供了一个 WebSocket API,您可以使用它来编写 client- 和 处理 WebSocket 消息的服务器端应用程序。spring-doc.cadn.net.cn

4.2.1.WebSocketHandler

创建 WebSocket 服务器就像实现WebSocketHandler或者,更多 可能会扩展TextWebSocketHandlerBinaryWebSocketHandler.以下内容 示例用途TextWebSocketHandler:spring-doc.cadn.net.cn

import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;

public class MyHandler extends TextWebSocketHandler {

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // ...
    }

}

有专用的 WebSocket Java 配置和 XML 命名空间支持,用于将前面的 WebSocket 处理程序添加到特定 URL,如下例所示:spring-doc.cadn.net.cn

import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

前面的示例用于 Spring MVC 应用程序,应该包含在内 在DispatcherServlet.然而,Spring 的 WebSocket 支持不依赖于 Spring MVC。它相对简单 集成一个WebSocketHandler到其他 HTTP 服务环境中WebSocketHttpRequestHandler.spring-doc.cadn.net.cn

使用WebSocketHandlerAPI 直接与间接,例如通过 STOMP 消息传递,应用程序必须同步消息的发送 因为底层标准 WebSocket 会话 (JSR-356) 不允许并发 发送。一种选择是将WebSocketSessionConcurrentWebSocketSessionDecorator.spring-doc.cadn.net.cn

4.2.2. WebSocket 握手

自定义初始 HTTP WebSocket 握手请求的最简单方法是通过 一个HandshakeInterceptor,它公开了握手的“之前”和“之后”的方法。 你可以使用这样的拦截器来排除握手或创建任何属性 available to 的WebSocketSession.以下示例使用内置侦听器 要将 HTTP 会话属性传递给 WebSocket 会话:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:handshake-interceptors>
            <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

更高级的选项是扩展DefaultHandshakeHandler执行 WebSocket 握手的步骤,包括验证客户端源, 协商子协议和其他细节。应用程序可能还需要使用此 选项(如果需要配置自定义RequestUpgradeStrategy为了 适配尚不支持的 WebSocket 服务器引擎和版本 (有关此主题的更多信息,请参阅部署)。 Java 配置和 XML 命名空间都允许配置自定义HandshakeHandler.spring-doc.cadn.net.cn

Spring 提供了一个WebSocketHandlerDecorator基类,可用于装饰 一个WebSocketHandler具有其他行为。日志记录和异常处理 使用 WebSocket Java 配置时,默认提供和添加 implementations 或 XML 命名空间。这ExceptionWebSocketHandlerDecorator捕获所有未捕获的 由任何WebSocketHandler方法并关闭 WebSocket 具有状态的会话1011,这表示服务器错误。

4.2.3. 部署

Spring WebSocket API 很容易集成到 Spring MVC 应用程序中,其中 这DispatcherServlet同时提供 HTTP WebSocket 握手和其他 HTTP 请求。它也很容易集成到其他 HTTP 处理场景中 通过调用WebSocketHttpRequestHandler.这既方便又容易 理解。但是,对于 JSR-356 运行时,需要特别注意。spring-doc.cadn.net.cn

Java WebSocket API (JSR-356) 提供了两种部署机制。第一个 涉及启动时的 Servlet 容器类路径扫描(Servlet 3 的一项功能)。 另一个是在 Servlet 容器初始化时使用的注册 API。 这两种机制都无法实现使用单个 “前端控制器” 用于所有 HTTP 处理 — 包括 WebSocket 握手和所有其他 HTTP requests — 例如 Spring MVC 的DispatcherServlet.spring-doc.cadn.net.cn

这是 JSR-356 的一个重大限制,Spring 的 WebSocket 支持解决了这个限制 特定于服务器RequestUpgradeStrategy实现,即使在 JSR-356 运行时中运行时也是如此。 目前,Tomcat、Jetty、GlassFish、WebLogic、WebSphere 和 Undertow(和 WildFly)。spring-doc.cadn.net.cn

克服 Java WebSocket API 中上述限制的请求是 创建并可以在 eclipse-ee4j/websocket-api#211 中遵循。 Tomcat、Undertow 和 WebSphere 提供了自己的 API 替代方案,这些 API 替代方案 使这成为可能,Jetty 也是可能的。我们满怀希望 更多的服务器也会做同样的事情。

第二个考虑因素是需要支持 JSR-356 的 Servlet 容器 要执行ServletContainerInitializer(SCI) 扫描可能会减慢应用程序的速度 启动 — 在某些情况下,效果会非常显著。如果在 升级到支持 JSR-356 的 Servlet 容器版本,它应该 可以有选择地启用或禁用 Web 片段(和 SCI 扫描) 通过使用<absolute-ordering />元素web.xml,如下例所示:spring-doc.cadn.net.cn

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering/>

</web-app>

然后,您可以按名称有选择地启用 Web 片段,例如 Spring 自己的SpringServletContainerInitializer为 Servlet 3 提供支持 Java 初始化 API。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering>
        <name>spring_web</name>
    </absolute-ordering>

</web-app>

4.2.4. 服务器配置

每个底层 WebSocket 引擎都公开了控制 运行时特征,例如消息缓冲区大小、空闲超时、 和其他。spring-doc.cadn.net.cn

对于 Tomcat、WildFly 和 GlassFish,您可以添加ServletServerContainerFactoryBean发送到您的 WebSocket Java 配置,如下例所示:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <bean class="org.springframework...ServletServerContainerFactoryBean">
        <property name="maxTextMessageBufferSize" value="8192"/>
        <property name="maxBinaryMessageBufferSize" value="8192"/>
    </bean>

</beans>
对于客户端 WebSocket 配置,您应该使用WebSocketContainerFactoryBean(XML) 或ContainerProvider.getWebSocketContainer()(Java 配置)。

对于 Jetty,您需要提供预配置的 JettyWebSocketServerFactory和插头 这转化为 Spring 的DefaultHandshakeHandler通过 WebSocket Java 配置。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoWebSocketHandler(),
            "/echo").setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/echo" handler="echoHandler"/>
        <websocket:handshake-handler ref="handshakeHandler"/>
    </websocket:handlers>

    <bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
        <constructor-arg ref="upgradeStrategy"/>
    </bean>

    <bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
        <constructor-arg ref="serverFactory"/>
    </bean>

    <bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
        <constructor-arg>
            <bean class="org.eclipse.jetty...WebSocketPolicy">
                <constructor-arg value="SERVER"/>
                <property name="inputBufferSize" value="8092"/>
                <property name="idleTimeout" value="600000"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>

4.2.5. 允许的来源

从 Spring Framework 4.1.5 开始,WebSocket 和 Sockjs 的默认行为是接受 仅限同源请求。也可以允许所有或指定的源列表。 此检查主要针对浏览器客户端设计。没有什么能阻止其他类型 的客户端修改Originheader 值(有关详细信息,请参阅 RFC 6454:Web Origin 概念)。spring-doc.cadn.net.cn

三种可能的行为是:spring-doc.cadn.net.cn

  • 仅允许同源请求(默认):在此模式下,启用 Sockjs 时, Iframe HTTP 响应标头X-Frame-Options设置为SAMEORIGIN和 JSONP transport 被禁用,因为它不允许检查请求的来源。 因此,启用此模式时,不支持 IE6 和 IE7。spring-doc.cadn.net.cn

  • 允许指定的源列表:每个允许的源必须以http://https://.在此模式下,启用 Sockjs 时,将禁用 IFrame 传输。 因此,IE6 到 IE9 在执行此作时不受支持 mode 已启用。spring-doc.cadn.net.cn

  • 允许所有源:要启用此模式,您应该提供作为允许的源 价值。在此模式下,所有传输都可用。*spring-doc.cadn.net.cn

您可以配置 WebSocket 和 Sockjs 允许的源,如下例所示:spring-doc.cadn.net.cn

import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers allowed-origins="https://mydomain.com">
        <websocket:mapping path="/myHandler" handler="myHandler" />
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

4.3. SockJS 回退

在公共 Internet 上,不受您控制的限制性代理可能会阻止 WebSocket 交互,要么是因为它们未配置为将Upgradeheader 或 ,因为它们会关闭似乎处于空闲状态的长期连接。spring-doc.cadn.net.cn

此问题的解决方案是 WebSocket 仿真 — 即尝试使用 WebSocket 首先,然后回退到模拟 WebSocket 的基于 HTTP 的技术 交互并公开相同的应用程序级 API。spring-doc.cadn.net.cn

在 Servlet 堆栈上,Spring Framework 提供服务器(和客户端)支持 对于 Sockjs 协议。spring-doc.cadn.net.cn

4.3.1. 概述

Sockjs 的目标是让应用程序使用 WebSocket API,但回退到 非 WebSocket 替代方案,而无需 更改应用程序代码。spring-doc.cadn.net.cn

Sockjs 包括:spring-doc.cadn.net.cn

Sockjs 专为在浏览器中使用而设计。它使用了多种技术 以支持各种浏览器版本。 有关 Sockjs 传输类型和浏览器的完整列表,请参阅 Sockjs 客户端页面。运输 分为三大类:WebSocket、HTTP 流和 HTTP 长轮询。 有关这些类别的概述,请参阅此博客文章spring-doc.cadn.net.cn

Sockjs 客户端首先发送GET /info自 从服务器获取基本信息。之后,它必须决定哪种传输方式 使用。如果可能,使用 WebSocket。如果没有,在大多数浏览器中, 至少有一个 HTTP 流式处理选项。如果不是,则 HTTP (long) 轮询。spring-doc.cadn.net.cn

所有传输请求都具有以下 URL 结构:spring-doc.cadn.net.cn

https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

WebSocket 传输只需要一个 HTTP 请求即可执行 WebSocket 握手。 此后的所有消息都在该套接字上交换。spring-doc.cadn.net.cn

HTTP 传输需要更多请求。例如,Ajax/XHR 流依赖于 一个长时间运行的服务器到客户端消息请求和其他 HTTP POST 客户端到服务器消息的请求。长轮询与此类似,只不过它 在每次服务器到客户端发送后结束当前请求。spring-doc.cadn.net.cn

Sockjs 添加了最少的消息框架。例如,服务器将信件o(“打开”框架)最初,消息以a["message1","message2"](JSON 编码数组),则字母h(“heartbeat” 帧)如果没有消息流 25 秒(默认),并且字母c(“close” frame) 以关闭会话。spring-doc.cadn.net.cn

要了解更多信息,请在浏览器中运行示例并观察 HTTP 请求。 Sockjs 客户端允许修复传输列表,因此可以 一次查看每种运输。Sockjs 客户端还提供了一个 debug 标志 ,这将在浏览器控制台中启用有用的消息。在服务器端,您可以启用TRACE的日志记录org.springframework.web.socket. 有关更多详细信息,请参阅 Sockjs 协议旁白测试spring-doc.cadn.net.cn

4.3.2. 启用 SockJS

您可以通过 Java 配置启用 Sockjs,如下例所示:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler").withSockJS();
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:sockjs/>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

前面的示例用于 Spring MVC 应用程序,应包含在 配置DispatcherServlet.但是,Spring 的 WebSocket 并且 Sockjs 支持不依赖于 Spring MVC。它相对简单 在SockJsHttpRequestHandler.spring-doc.cadn.net.cn

在浏览器端,应用程序可以使用sockjs-client(版本 1.0.x)。它 模拟 W3C WebSocket API 并与服务器通信以选择最佳 transport 选项,具体取决于运行它的浏览器。参见 sockjs-client 页面和 浏览器支持的传输类型。客户端还提供多个 Configuration Options (配置选项) — 例如,指定要包含的传输。spring-doc.cadn.net.cn

4.3.3. IE 8 和 9

Internet Explorer 8 和 9 仍在使用中。他们是 拥有 Sockjs 的关键原因。本节涵盖重要的 有关在这些浏览器中运行的注意事项。spring-doc.cadn.net.cn

Sockjs 客户端使用 Microsoft 的XDomainRequest. 这可以跨域工作,但不支持发送 Cookie。 Cookie 通常对于 Java 应用程序至关重要。 但是,由于 Sockjs 客户端可以与许多服务器一起使用 类型(不仅仅是 Java 类型)中,它需要知道 cookie 是否重要。 如果是这样,则 Sockjs 客户端首选 Ajax/XHR 进行流式处理。否则,它会 依赖于基于 iframe 的技术。spring-doc.cadn.net.cn

第一个/info请求是 可以影响客户选择交通工具的信息。 其中一个细节是服务器应用程序是否依赖 Cookie (例如,用于身份验证目的或使用粘性会话进行集群)。 Spring 的 Sockjs 支持包括一个名为sessionCookieNeeded. 默认情况下,它是启用的,因为大多数 Java 应用程序都依赖于JSESSIONID饼干。如果您的应用程序不需要它,您可以关闭此选项, 然后 Sockjs 客户端应该选择xdr-streaming在 IE 8 和 9 中。spring-doc.cadn.net.cn

如果您确实使用基于 iframe 的传输,请记住 可以通过以下方式指示浏览器在给定页面上阻止使用 IFrames 设置 HTTP 响应标头X-Frame-OptionsDENY,SAMEORIGINALLOW-FROM <origin>.这用于防止点击劫持spring-doc.cadn.net.cn

Spring Security 3.2+ 支持设置X-Frame-Options在每个 响应。默认情况下,Spring Security Java 配置将其设置为DENY. 在 3.2 中, Spring Security XML 命名空间默认情况下不设置该 Headers 但可以配置为执行此作。将来,它可能会默认设置它。spring-doc.cadn.net.cn

有关如何配置 设置X-Frame-Options页眉。您还可以查看 gh-2718 以获取更多背景信息。spring-doc.cadn.net.cn

如果您的应用程序添加了X-Frame-Options响应标头(理应如此! ,并且依赖于基于 iframe 的传输,您需要将 header 值设置为SAMEORIGINALLOW-FROM <origin>.春季 SockJS 支持还需要知道 Sockjs 客户端的位置,因为它已加载 从 iframe 中。默认情况下,iframe 设置为下载 Sockjs 客户端 从 CDN 位置。最好将此选项配置为使用 与应用程序来自同一源的 URL。spring-doc.cadn.net.cn

以下示例显示了如何在 Java 配置中执行此作:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS()
                .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
    }

    // ...

}

XML 命名空间通过<websocket:sockjs>元素。spring-doc.cadn.net.cn

在初始开发过程中,请启用 Sockjs 客户端devel模式,以防止 浏览器缓存 Sockjs 请求(如 iframe),否则 被缓存。有关如何启用它的详细信息,请参阅 Sockjs 客户端页面。

4.3.4. 心跳

Sockjs 协议要求服务器发送心跳消息以排除代理 得出连接已挂起的结论。Spring Sockjs 配置有一个属性 叫heartbeatTime可用于自定义频率。默认情况下, 检测信号在 25 秒后发送,假设没有发送其他消息 连接。此 25 秒值符合以下针对公共 Internet 应用程序的 IETF 建议spring-doc.cadn.net.cn

当通过 WebSocket 和 Sockjs 使用 STOMP 时,如果 STOMP 客户端和服务器协商 heartbeats 进行交换,则禁用 Sockjs 心跳。

Spring Sockjs 支持还允许您配置TaskScheduler自 计划检测信号任务。任务调度器由线程池 使用基于可用处理器数量的默认设置。你 应考虑根据您的特定需求自定义设置。spring-doc.cadn.net.cn

4.3.5. 客户端断开连接

HTTP 流和 HTTP 长轮询 Sockjs 传输需要保持连接 开放时间比平时长。有关这些技术的概述,请参阅此博客文章spring-doc.cadn.net.cn

在 Servlet 容器中,这是通过 Servlet 3 异步支持完成的,该 允许退出 Servlet 容器线程,处理请求并继续 写入来自另一个线程的响应。spring-doc.cadn.net.cn

一个具体问题是 Servlet API 不为客户端提供通知 这已经消失了。参见 eclipse-ee4j/servlet-api#44。 但是,Servlet 容器会在后续写入尝试时引发异常 响应。由于 Spring 的 Sockjs 服务支持服务器发送的心跳(每个 默认为 25 秒),这意味着通常会在该时间内检测到客户端断开连接 时间段(如果消息发送更频繁,则更早)。spring-doc.cadn.net.cn

因此,网络 I/O 故障可能是因为客户端已断开连接,这 可能会用不必要的堆栈跟踪填充日志。Spring 尽最大努力识别 此类网络故障表示客户端断开连接(特定于每个服务器)并记录 使用专用日志类别的最小消息,DISCONNECTED_CLIENT_LOG_CATEGORY(在AbstractSockJsSession).如果您需要查看堆栈跟踪,可以将其 log 类别设置为 TRACE。

4.3.6. SockJS 和 CORS

如果您允许跨域请求(请参阅允许的源),则 Sockjs 协议 在 XHR 流和轮询传输中使用 CORS 实现跨域支持。因此 CORS 标头会自动添加,除非响应中存在 CORS 标头 检测到。因此,如果应用程序已配置为提供 CORS 支持(例如, 通过 Servlet 过滤器)、Spring 的SockJsService跳过此部分。spring-doc.cadn.net.cn

也可以通过设置suppressCorsSpring 的 SockJsService 中的属性。spring-doc.cadn.net.cn

Sockjs 需要以下 Headers 和值:spring-doc.cadn.net.cn

有关确切的实现,请参阅addCorsHeadersAbstractSockJsService和 这TransportTypeenum 的 intent 文件。spring-doc.cadn.net.cn

或者,如果 CORS 配置允许,请考虑排除 Sockjs 端点前缀,从而让 Spring 的SockJsService处理它。spring-doc.cadn.net.cn

4.3.7.SockJsClient

Spring 提供了一个 Sockjs Java 客户端来连接到远程 Sockjs 端点,而无需 使用浏览器。当需要双向 两个服务器之间通过公共网络(即网络代理可以 排除使用 WebSocket 协议)。Sockjs Java 客户端也非常有用 用于测试目的(例如,模拟大量并发用户)。spring-doc.cadn.net.cn

Sockjs Java 客户端支持websocket,xhr-streamingxhr-polling运输。其余的只有在浏览器中使用才有意义。spring-doc.cadn.net.cn

您可以配置WebSocketTransport跟:spring-doc.cadn.net.cn

XhrTransport,根据定义,同时支持xhr-streamingxhr-polling因为 从客户端的角度来看,除了用于连接的 URL 之外,没有其他区别 添加到服务器。目前有两种实现方式:spring-doc.cadn.net.cn

以下示例演示如何创建 Sockjs 客户端并连接到 Sockjs 端点:spring-doc.cadn.net.cn

List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());

SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
Sockjs 对消息使用 JSON 格式的数组。默认情况下,使用 Jackson 2 并需要 以位于 Classpath 上。或者,您可以配置SockJsMessageCodec并在SockJsClient.

要使用SockJsClient要模拟大量并发用户,您需要 需要配置底层 HTTP 客户端(用于 XHR 传输)以允许足够的 连接数和线程数。以下示例显示了如何使用 Jetty 执行此作:spring-doc.cadn.net.cn

HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));

以下示例显示了服务器端与 Sockjs 相关的属性(有关详细信息,请参见 javadoc) 您还应考虑自定义:spring-doc.cadn.net.cn

@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/sockjs").withSockJS()
            .setStreamBytesLimit(512 * 1024) (1)
            .setHttpMessageCacheSize(1000) (2)
            .setDisconnectDelay(30 * 1000); (3)
    }

    // ...
}
1 streamBytesLimit属性设置为 512KB(默认值为 128KB —128 * 1024).
2 httpMessageCacheSize属性设置为 1,000(默认值为100).
3 disconnectDelayproperty 设置为 30 个属性秒(默认值为 5 秒 -5 * 1000).

4.4. STOMP踏

WebSocket 协议定义了两种类型的消息(文本和二进制),但它们的 内容未定义。该协议定义了一种机制,供客户端和服务器协商 子协议(即更高级别的消息传递协议)在 WebSocket 上使用,以 定义每个可以发送的消息类型、格式、每个消息的内容 消息,依此类推。子协议的使用是可选的,但无论哪种方式,客户端和 服务器需要就定义消息内容的某个协议达成一致。spring-doc.cadn.net.cn

4.4.1. 概述

STOMP(简单 Text Oriented Messaging Protocol)最初是为脚本语言创建的 (例如 Ruby、Python 和 Perl)连接到企业消息代理。是的 旨在解决常用消息传递模式的最小子集。STOMP 可以是 通过任何可靠的双向流式处理网络协议(如 TCP 和 WebSocket)使用。 尽管 STOMP 是面向文本的协议,但消息有效负载可以是 text 或 binary。spring-doc.cadn.net.cn

STOMP 是一种基于帧的协议,其帧基于 HTTP。下面的清单显示了结构 的 STOMP 帧:spring-doc.cadn.net.cn

COMMAND
header1:value1
header2:value2

Body^@

客户端可以使用SENDSUBSCRIBE要发送或订阅的命令 消息以及destination标头,该标头描述了 消息是关于以及谁应该接收它。这样,一个简单的 publish-subscribe 机制,可用于通过 broker 发送消息 发送到其他连接的客户端,或者向服务器发送消息以请求 执行一些工作。spring-doc.cadn.net.cn

当您使用 Spring 的 STOMP 支持时,Spring WebSocket 应用程序会执行 作为 STOMP 代理。消息被路由到@Controller消息处理 方法或跟踪订阅的简单内存代理,以及 向订阅用户广播消息。您还可以配置 Spring 以工作 使用专用的 STOMP 代理(例如 RabbitMQ、ActiveMQ 等)进行实际 消息广播。在这种情况下, Spring 会维护 与代理的 TCP 连接,将消息中继到代理,并传递消息 从它向下到连接的 WebSocket 客户端。因此,Spring Web 应用程序可以 依赖于基于 HTTP 的统一安全性、通用验证和熟悉的编程 model 进行消息处理。spring-doc.cadn.net.cn

以下示例显示了订阅以接收股票报价的客户端,该 服务器可能会定期发出(例如,通过发送消息的计划任务 通过SimpMessagingTemplate到经纪人):spring-doc.cadn.net.cn

SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*

^@

以下示例显示了一个发送交易请求的客户端,该请求服务器 可以通过@MessageMapping方法:spring-doc.cadn.net.cn

SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@

执行后,服务器可以 向客户广播交易确认消息和详细信息。spring-doc.cadn.net.cn

目标的含义在 STOMP 规范中故意保持不透明。它可以 可以是任何字符串,则完全由 STOMP 服务器来定义语义和 它们支持的目标的语法。然而,对于 destinations 设置为类似路径的字符串,其中/topic/..暗示发布-订阅 (一对多) 和/queue/表示点对点(一对一)消息 交流。spring-doc.cadn.net.cn

STOMP 服务器可以使用MESSAGE命令向所有订阅者广播消息。 以下示例显示了向订阅的客户端发送股票报价的服务器:spring-doc.cadn.net.cn

MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM

{"ticker":"MMM","price":129.45}^@

服务器无法发送未经请求的消息。所有消息 必须响应特定的客户端订阅,并且subscription标头必须与id标头的 客户端订阅。spring-doc.cadn.net.cn

前面的概述旨在提供对 STOMP 协议。我们建议完整地查看协议规范spring-doc.cadn.net.cn

4.4.2. 好处

使用 STOMP 作为子协议可以让 Spring Framework 和 Spring Security 提供更丰富的编程模型,而不是使用原始 WebSockets。同一点可以是 介绍了 HTTP 与原始 TCP 以及它如何让 Spring MVC 和其他 Web 框架 提供丰富的功能。以下是好处列表:spring-doc.cadn.net.cn

  • 无需发明自定义消息协议和消息格式。spring-doc.cadn.net.cn

  • STOMP 客户端(包括 Spring Framework 中的 Java 客户端)可用。spring-doc.cadn.net.cn

  • 您可以(可选地)使用消息代理(例如 RabbitMQ、ActiveMQ 等)来 管理订阅和广播消息。spring-doc.cadn.net.cn

  • 应用程序逻辑可以组织成任意数量的@Controller实例和消息可以是 根据 STOMP 目标标头路由到它们,而不是处理原始 WebSocket 消息 使用单个WebSocketHandler对于给定连接。spring-doc.cadn.net.cn

  • 您可以使用 Spring Security 根据 STOMP 目标和消息类型保护消息。spring-doc.cadn.net.cn

4.4.3. 启用 STOMP

STOMP over WebSocket 支持在spring-messagingspring-websocket模块。拥有这些依赖项后,就可以公开 STOMP endpoints,通过 WebSocket 和 Sockjs Fallback,如下例所示:spring-doc.cadn.net.cn

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();  (1)
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes("/app"); (2)
        config.enableSimpleBroker("/topic", "/queue"); (3)
    }
}
1 /portfolio是 WebSocket(或 SockJS)到的端点的 HTTP URL。 客户端需要连接以进行 WebSocket 握手。
2 目标报头以/app路由到@MessageMappingmethods 中的@Controller类。
3 使用内置的消息代理进行订阅和广播,以及 路由目标标头以/topic `or `/queue到经纪人。

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker application-destination-prefix="/app">
        <websocket:stomp-endpoint path="/portfolio">
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:simple-broker prefix="/topic, /queue"/>
    </websocket:message-broker>

</beans>
对于内置的 simple broker,/topic/queue前缀没有任何特殊的 意义。它们只是区分 pub-sub 和 point-to-point 的约定 消息收发(即,多个订阅者与 1 个使用者)。当您使用外部代理时, 检查代理的 STOMP 页面以了解哪种类型的 STOMP 目标和 它支持的前缀。

要从浏览器连接,对于 Sockjs,您可以使用sockjs-client.对于 STOMP,许多应用程序都有 使用了 jmesnil/stomp-websocket 库 (也称为 stomp.js),功能齐全,已在生产环境中用于 年,但不再维护。目前 JSteunou/webstomp-client 是最多的 该库的积极维护和不断发展的继任者。以下示例代码 基于它:spring-doc.cadn.net.cn

var socket = new SockJS("/spring-websocket-portfolio/portfolio");
var stompClient = webstomp.over(socket);

stompClient.connect({}, function(frame) {
}

或者,如果你通过 WebSocket 连接(没有 Sockjs),你可以使用以下代码:spring-doc.cadn.net.cn

var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
}

请注意,stompClient在前面的示例中不需要指定loginpasscode头。即使这样做了,他们也会被忽略(或者更确切地说, overridden) 的有关身份验证的更多信息,请参见Connecting to a Broker and Authenticationspring-doc.cadn.net.cn

有关更多示例代码,请参阅:spring-doc.cadn.net.cn

4.4.4. WebSocket 服务器

要配置底层 WebSocket 服务器,服务器配置中的信息适用。但是,对于 Jetty,您需要设置 这HandshakeHandlerWebSocketPolicy通过StompEndpointRegistry:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }
}

4.4.5. 消息流

一旦 STOMP 端点被公开,Spring 应用程序就会成为 STOMP 代理 连接的客户端。本节介绍服务器端的消息流。spring-doc.cadn.net.cn

spring-messaging模块包含对消息传递应用程序的基本支持 它源自 Spring Integration,并且是 后来提取并合并到 Spring 框架中,以便在许多 Spring 项目和应用程序场景中更广泛地使用。 以下列表简要介绍了一些可用的消息传递抽象:spring-doc.cadn.net.cn

Java 配置(即@EnableWebSocketMessageBroker) 和 XML 命名空间配置 (即<websocket:message-broker>) 使用上述组件来组合消息 工作流。下图显示了 simple 内置消息 broker 已启用:spring-doc.cadn.net.cn

消息流简单代理

上图显示了三个消息通道:spring-doc.cadn.net.cn

下图显示了外部代理(例如 RabbitMQ) 配置为管理订阅和广播消息:spring-doc.cadn.net.cn

消息流代理中继

前面两个图之间的主要区别在于使用 “broker relay” 来传递 消息通过 TCP 传输到外部 STOMP 代理,并用于从 broker 添加到订阅的客户端。spring-doc.cadn.net.cn

当从 WebSocket 连接接收到消息时,它们被解码为 STOMP 帧。 变成了弹簧Message表示形式,并发送到clientInboundChannel以便进一步处理。例如,其 STOMP 消息 目标标头开头为/app可以路由到@MessageMappingmethods 中的 注解的控制器,而/topic/queue消息可以直接路由 发送到消息代理。spring-doc.cadn.net.cn

带注释的@Controller处理来自客户端的 STOMP 消息的 STOMP 消息可能会将消息发送到 消息代理通过brokerChannel,并且代理会广播 消息通过clientOutboundChannel.一样 controller 也可以执行相同的作来响应 HTTP 请求,因此客户端可以执行 HTTP POST,然后是@PostMapping方法可以向消息代理发送消息 向订阅的客户端广播。spring-doc.cadn.net.cn

我们可以通过一个简单的例子来追踪流程。请考虑以下示例,该示例设置了一个服务器:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio");
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic");
    }
}

@Controller
public class GreetingController {

    @MessageMapping("/greeting")
    public String handle(String greeting) {
        return "[" + getTimestamp() + ": " + greeting;
    }
}

前面的示例支持以程:spring-doc.cadn.net.cn

  1. 客户端连接到http://localhost:8080/portfolio并且,一旦 WebSocket 连接 建立,STOMP 帧开始在其上流动。spring-doc.cadn.net.cn

  2. 客户端发送目标标头为/topic/greeting.一旦收到 并解码后,消息将发送到clientInboundChannel,然后路由到 Message Broker,用于存储客户端订阅。spring-doc.cadn.net.cn

  3. 客户端将 SEND 帧发送到/app/greeting.这/appprefix 有助于将其路由到 带注释的控制器。在/app前缀被剥离,则剩余的/greeting部分目标映射到@MessageMappingmethod 中GreetingController.spring-doc.cadn.net.cn

  4. GreetingController变成弹簧Message跟 基于返回值和默认目标标头/topic/greeting(从 input destination 派生自/app替换为/topic).生成的消息将发送到brokerChannel并处理 由 Message Broker 提供。spring-doc.cadn.net.cn

  5. 消息代理查找所有匹配的订阅者,并向每个订阅者发送一个 MESSAGE 帧 通过clientOutboundChannel,从中消息编码为 STOMP 帧 并通过 WebSocket 连接发送。spring-doc.cadn.net.cn

下一节将提供有关带注释方法的更多详细信息,包括 支持的参数和返回值的种类。spring-doc.cadn.net.cn

4.4.6. 带注解的控制器

应用程序可以使用带注释的@Controller类来处理来自客户端的消息。 此类可以声明@MessageMapping,@SubscribeMapping@ExceptionHandler方法,如以下主题所述:spring-doc.cadn.net.cn

@MessageMapping

您可以使用@MessageMapping来注释根据其 目的地。它在方法级别和类型级别都受支持。在类型 水平@MessageMapping用于表示 控制器。spring-doc.cadn.net.cn

默认情况下,映射值为 Ant 样式的路径模式(例如/thing*,/thing/**), 包括对模板变量的支持(例如/thing/{id}).值可以是 引用方式@DestinationVariablemethod 参数。应用程序还可以切换到 映射的点分隔目标约定,如 点作为分隔符中所述。spring-doc.cadn.net.cn

支持的方法参数

下表描述了方法参数:spring-doc.cadn.net.cn

Method 参数 描述

Messagespring-doc.cadn.net.cn

用于访问完整的消息。spring-doc.cadn.net.cn

MessageHeadersspring-doc.cadn.net.cn

要访问Message.spring-doc.cadn.net.cn

MessageHeaderAccessor,SimpMessageHeaderAccessorStompHeaderAccessorspring-doc.cadn.net.cn

用于通过类型化访问器方法访问标头。spring-doc.cadn.net.cn

@Payloadspring-doc.cadn.net.cn

要访问消息的有效负载,请由配置的MessageConverter.spring-doc.cadn.net.cn

此注释的存在不是必需的,因为默认情况下,如果不存在 other 参数。spring-doc.cadn.net.cn

您可以使用@javax.validation.Valid或 Spring 的@Validated, 以自动验证 payload 参数。spring-doc.cadn.net.cn

@Headerspring-doc.cadn.net.cn

要访问特定的标头值,以及使用org.springframework.core.convert.converter.Converter,如有必要。spring-doc.cadn.net.cn

@Headersspring-doc.cadn.net.cn

用于访问消息中的所有标头。此参数必须可分配给java.util.Map.spring-doc.cadn.net.cn

@DestinationVariablespring-doc.cadn.net.cn

用于访问从消息目标提取的模板变量。 值将根据需要转换为声明的方法参数类型。spring-doc.cadn.net.cn

java.security.Principalspring-doc.cadn.net.cn

反映在 WebSocket HTTP 握手时登录的用户。spring-doc.cadn.net.cn

返回值

默认情况下,来自@MessageMappingmethod 序列化为有效负载 通过匹配的MessageConverter并作为MessagebrokerChannel, 从那里向订阅者广播。出站消息的目的地是 与入站消息的 ID 相同,但前缀为/topic.spring-doc.cadn.net.cn

您可以使用@SendTo@SendToUserannotations 自定义 输出消息。@SendTo用于自定义目标目标或 指定多个目标。@SendToUser用于定向输出消息 仅与输入消息关联的用户。请参阅 用户目标spring-doc.cadn.net.cn

您可以同时使用@SendTo@SendToUser同时使用相同的方法,并且两者 在类级别受支持,在这种情况下,它们充当 类。但是,请记住,任何方法级别的@SendTo@SendToUser附注 在类级别覆盖任何此类注释。spring-doc.cadn.net.cn

消息可以异步处理,并且@MessageMappingmethod 可以返回ListenableFuture,CompletableFutureCompletionStage.spring-doc.cadn.net.cn

请注意,@SendTo@SendToUser只是一种方便,相当于使用SimpMessagingTemplate以发送消息。如有必要,对于更高级的场景,@MessageMapping方法可以回退到使用SimpMessagingTemplate径直。 这可以代替返回值,也可能同时返回值。 请参阅发送消息spring-doc.cadn.net.cn

@SubscribeMapping

@SubscribeMapping类似于@MessageMapping但将映射缩小到 仅限订阅消息。它支持与@MessageMapping.然而 对于返回值,默认情况下,消息将直接发送到客户端(通过clientOutboundChannel响应订阅)而不是代理(通过brokerChannel作为对匹配订阅的广播)。添加@SendTo@SendToUser覆盖此行为并改为发送到 broker。spring-doc.cadn.net.cn

这在什么情况下有用?假设代理映射到/topic/queue而 应用程序控制器映射到/app.在此设置中,代理存储所有 订阅/topic/queue用于重复广播的 URL,以及 应用程序无需参与。客户端还可以订阅 一些/appdestination 的 Destination 中,控制器可以返回一个值来响应该 订阅,而不涉及代理,而无需再次存储或使用订阅 (实际上是一次性的请求-回复交换)。其中一个用例是填充 UI 使用启动时的初始数据。spring-doc.cadn.net.cn

这在什么情况下没有用?不要尝试将 broker 和 controller 映射到同一个目的地 前缀,除非您希望两者都独立处理消息(包括订阅)、 出于某种原因。入站消息是并行处理的。无法保证 broker 或 controller 首先处理给定的消息。如果目标是收到通知 当订阅存储并准备好进行广播时,客户端应请求 receipt (如果服务器支持) (Simple Broker 不支持)。例如,使用 Java STOMP 客户端,您可以执行以下作来添加收据:spring-doc.cadn.net.cn

@Autowired
private TaskScheduler messageBrokerTaskScheduler;

// During initialization..
stompClient.setTaskScheduler(this.messageBrokerTaskScheduler);

// When subscribing..
StompHeaders headers = new StompHeaders();
headers.setDestination("/topic/...");
headers.setReceipt("r1");
FrameHandler handler = ...;
stompSession.subscribe(headers, handler).addReceiptTask(receiptHeaders -> {
    // Subscription ready...
});

服务器端选项是注册一个ExecutorChannelInterceptorbrokerChannel并实施afterMessageHandled在处理完消息(包括订阅)后调用的方法。spring-doc.cadn.net.cn

@MessageExceptionHandler

应用程序可以使用@MessageExceptionHandler处理异常的方法@MessageMapping方法。您可以在 annotation 中声明异常 本身或通过 method 参数(如果你想访问异常实例)。 下面的示例通过 method 参数声明一个 exception:spring-doc.cadn.net.cn

@Controller
public class MyController {

    // ...

    @MessageExceptionHandler
    public ApplicationError handleException(MyException exception) {
        // ...
        return appError;
    }
}

@MessageExceptionHandler方法支持灵活的方法签名和支持 方法参数类型和返回值与@MessageMapping方法。spring-doc.cadn.net.cn

通常@MessageExceptionHandler方法在@Controller类 (或类层次结构)中声明它们。如果您希望此类方法适用 更全局地(跨控制器),您可以在标有@ControllerAdvice.这与 Spring MVC 中提供的类似支持相当。spring-doc.cadn.net.cn

4.4.7. 发送消息

如果您想从 应用?任何应用程序组件都可以向brokerChannel. 最简单的方法是注入SimpMessagingTemplate和 使用它来发送消息。通常,您可以通过以下方式注入它 type,如下例所示:spring-doc.cadn.net.cn

@Controller
public class GreetingController {

    private SimpMessagingTemplate template;

    @Autowired
    public GreetingController(SimpMessagingTemplate template) {
        this.template = template;
    }

    @RequestMapping(path="/greetings", method=POST)
    public void greet(String greeting) {
        String text = "[" + getTimestamp() + "]:" + greeting;
        this.template.convertAndSend("/topic/greetings", text);
    }

}

但是,您也可以通过其名称 (brokerMessagingTemplate),如果另一个 存在相同类型的 bean。spring-doc.cadn.net.cn

4.4.8. 简单代理

内置的 Simple Message Broker 处理来自客户端的订阅请求, 将它们存储在内存中,并将消息广播到具有匹配项的已连接客户端 目的地。代理支持类似路径的目标,包括订阅 更改为 Ant 样式的目标模式。spring-doc.cadn.net.cn

应用程序还可以使用点分隔(而不是斜杠分隔)的目标。 请参阅点作为分隔符

如果配置了任务计划程序,则简单代理支持 STOMP 检测信号。 要配置调度程序,您可以声明自己的TaskSchedulerbean 并将其设置为 这MessageBrokerRegistry.或者,您可以使用自动 但是,您需要@Lazy避免 内置 WebSocket 配置和WebSocketMessageBrokerConfigurer.例如:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private TaskScheduler messageBrokerTaskScheduler;

    @Autowired
    public void setMessageBrokerTaskScheduler(@Lazy TaskScheduler taskScheduler) {
        this.messageBrokerTaskScheduler = taskScheduler;
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/queue/", "/topic/")
                .setHeartbeatValue(new long[] {10000, 20000})
                .setTaskScheduler(this.messageBrokerTaskScheduler);

        // ...
    }
}

4.4.9. 外部代理

simple 代理非常适合入门,但仅支持 STOMP 命令(它不支持 acks、收据和其他一些功能), 依赖于简单的消息发送循环,不适合集群。 或者,您可以升级应用程序以使用功能齐全的 消息代理。spring-doc.cadn.net.cn

请参阅所选消息代理(例如 RabbitMQ、ActiveMQ 等)的 STOMP 文档,安装代理、 并在启用 STOMP 支持的情况下运行它。然后,您可以启用 STOMP 代理中继 (而不是简单的代理)在 Spring 配置中。spring-doc.cadn.net.cn

以下示例配置启用功能齐全的代理:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableStompBrokerRelay("/topic", "/queue");
        registry.setApplicationDestinationPrefixes("/app");
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker application-destination-prefix="/app">
        <websocket:stomp-endpoint path="/portfolio" />
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:stomp-broker-relay prefix="/topic,/queue" />
    </websocket:message-broker>

</beans>

上述配置中的 STOMP 代理中继是一个 SpringMessageHandler,通过将消息转发到外部消息代理来处理消息。 为此,它与代理建立 TCP 连接,将所有消息转发给代理, 然后,通过 WebSocket 会话。从本质上讲,它充当转发消息的 “中继” 双向。spring-doc.cadn.net.cn

io.projectreactor.netty:reactor-nettyio.netty:netty-all依赖项添加到项目中进行 TCP 连接管理。

此外,应用程序组件(例如 HTTP 请求处理方法、 业务服务等)也可以将消息发送到 Broker Relay,如 ,将消息广播到订阅的 WebSocket 客户端。spring-doc.cadn.net.cn

实际上,代理中继支持健壮且可扩展的消息广播。spring-doc.cadn.net.cn

4.4.10. 连接到 Broker

STOMP 代理中继维护与代理的单个“系统”TCP 连接。 此连接用于来自服务器端应用程序的消息 仅用于接收消息。您可以配置 STOMP 凭证(即 STOMP 框架loginpasscodeheaders) 进行这是暴露的 在 XML 命名空间和 Java 配置中作为systemLoginsystemPasscode默认值为guestguest.spring-doc.cadn.net.cn

STOMP 代理中继还为每个连接的 TCP 连接创建一个单独的 TCP 连接 WebSocket 客户端。您可以配置用于所有 TCP 的 STOMP 凭证 代表客户创建的连接。这在 XML 命名空间中公开 和 Java 配置作为clientLoginclientPasscode属性替换为 default 的值guestguest.spring-doc.cadn.net.cn

STOMP 代理中继始终将loginpasscode标头CONNECT框架,它代表客户端转发给 broker。因此,WebSocket 客户端 不需要设置这些标头。它们将被忽略。正如 Authentication 部分所解释的那样,WebSocket 客户端应该依赖 HTTP 身份验证来保护 WebSocket 终端节点并建立客户端身份。

STOMP 代理中继还向消息发送和接收检测信号 broker 通过 “system” TCP 连接。您可以配置发送的间隔 和接收检测信号(默认每个 10 秒)。如果连接到代理 丢失,则代理中继会继续尝试重新连接,每 5 秒一次, 直到它成功。spring-doc.cadn.net.cn

任何 Spring bean 都可以实现ApplicationListener<BrokerAvailabilityEvent>在与 broker 的 “system” 连接丢失时接收通知,以及 重新建立。例如,广播股票报价的 Stock Quote 服务可以 当没有活动的 “system” 连接时停止尝试发送消息。spring-doc.cadn.net.cn

默认情况下,STOMP 代理中继始终连接,并在 if 与同一主机和端口的连接丢失。如果您希望提供多个地址, 在每次尝试连接时,您可以配置地址的提供商,而不是 固定主机和端口。下面的示例展示了如何做到这一点:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    // ...

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableStompBrokerRelay("/queue/", "/topic/").setTcpClient(createTcpClient());
        registry.setApplicationDestinationPrefixes("/app");
    }

    private ReactorNettyTcpClient<byte[]> createTcpClient() {
        return new ReactorNettyTcpClient<>(
                client -> client.addressSupplier(() -> ... ),
                new StompReactorNettyCodec());
    }
}

您还可以使用virtualHost财产。 此属性的值设置为host标头CONNECT框架 )并且可能很有用(例如,在云环境中,实际主机连接到哪个 建立的 TCP 连接与提供 基于云的 STOMP 服务)。spring-doc.cadn.net.cn

4.4.11. 点作为分隔符

当邮件路由到@MessageMapping方法,它们与AntPathMatcher.默认情况下,模式应使用 slash () 作为分隔符。 这是 Web 应用程序中的良好约定,类似于 HTTP URL。但是,如果 您更习惯于消息传递约定,可以切换到使用 DOT (/.) 作为分隔符。spring-doc.cadn.net.cn

以下示例显示了如何在 Java 配置中执行此作:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    // ...

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setPathMatcher(new AntPathMatcher("."));
        registry.enableStompBrokerRelay("/queue", "/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }
}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:websocket="http://www.springframework.org/schema/websocket"
        xsi:schemaLocation="
                http://www.springframework.org/schema/beans
                https://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/websocket
                https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
        <websocket:stomp-endpoint path="/stomp"/>
        <websocket:stomp-broker-relay prefix="/topic,/queue" />
    </websocket:message-broker>

    <bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
        <constructor-arg index="0" value="."/>
    </bean>

</beans>

之后,控制器可以使用点 (.) 作为@MessageMapping方法 如下例所示:spring-doc.cadn.net.cn

@Controller
@MessageMapping("red")
public class RedController {

    @MessageMapping("blue.{green}")
    public void handleGreen(@DestinationVariable String green) {
        // ...
    }
}

客户端现在可以将消息发送到/app/red.blue.green123.spring-doc.cadn.net.cn

在前面的示例中,我们没有更改 “broker relay” 上的前缀,因为这些 完全依赖于外部 Message Broker。请参阅 STOMP 文档页面 用于查看 Destination Headers 支持的约定的代理。spring-doc.cadn.net.cn

另一方面,“简单代理”确实依赖于配置的PathMatcher,因此,如果 切换分隔符,该更改也适用于 broker 和 broker 的匹配方式 destinations 从 message 到 subscriptions 中的模式。spring-doc.cadn.net.cn

4.4.12. 认证

每个 STOMP over WebSocket 消息收发会话都以 HTTP 请求开头。 这可以是升级到 WebSockets 的请求(即 WebSocket 握手) 或者,在 Sockjs 回退的情况下,一系列 Sockjs HTTP 传输请求。spring-doc.cadn.net.cn

许多 Web 应用程序已经进行了身份验证和授权,以便 安全 HTTP 请求。通常,用户通过 Spring Security 进行身份验证 通过使用某些机制,例如登录页面、HTTP 基本身份验证或其他方式。 经过身份验证的用户的安全上下文保存在 HTTP 会话中 ,并与同一基于 Cookie 的会话中的后续请求相关联。spring-doc.cadn.net.cn

因此,对于 WebSocket 握手或 Sockjs HTTP 传输请求, 通常,已经有一个经过身份验证的用户可通过HttpServletRequest#getUserPrincipal().Spring 会自动关联该用户 替换为它们创建的 WebSocket 或 Sockjs 会话,然后,使用所有 通过用户标头通过该会话传输的 STOMP 消息。spring-doc.cadn.net.cn

简而言之,典型的 Web 应用程序不需要执行任何作 超越了它已经为安全所做的工作。用户通过 具有安全上下文的 HTTP 请求级别,该上下文通过基于 Cookie 的 HTTP 会话(然后与创建的 WebSocket 或 Sockjs 会话相关联 ),并导致在每个Message流动 通过应用程序。spring-doc.cadn.net.cn

STOMP 协议确实具有loginpasscodeheaders 的CONNECT框架。 这些最初是为 TCP 上的 STOMP 设计的,并且是 STOMP 所必需的。但是,对于 STOMP 默认情况下,Spring 会忽略 STOMP 协议的身份验证标头 级别,并假定用户已在 HTTP 传输级别进行身份验证。 预期是 WebSocket 或 Sockjs 会话包含经过身份验证的用户。spring-doc.cadn.net.cn

4.4.13. Tokens认证

Spring Security OAuth 支持基于Tokens的安全性,包括 JSON Web Tokens (JWT)。 您可以将其用作 Web 应用程序中的身份验证机制。 包括 WebSocket 交互上的 STOMP,如前面的 部分(即,通过基于 Cookie 的会话维护身份)。spring-doc.cadn.net.cn

同时,基于 Cookie 的会话并不总是最合适的(例如, 在不维护服务器端会话的应用程序中或在 移动应用程序,其中通常使用 Headers 进行身份验证)。spring-doc.cadn.net.cn

WebSocket 协议 RFC 6455“没有规定服务器在 WebSocket 握手。但是,在实践中,浏览器客户端只能使用标准的 authentication headers (即基本 HTTP authentication) 或 cookie 和 cannot (例如) 提供自定义标头。同样,Sockjs JavaScript 客户端也不提供 一种使用 Sockjs 传输请求发送 HTTP 标头的方法。参见 sockjs-client 问题 196。 相反,它确实允许发送可用于发送Tokens的查询参数。 但这也有其自身的缺点(例如,Tokens可能会无意中 使用服务器日志中的 URL 记录)。spring-doc.cadn.net.cn

上述限制适用于基于浏览器的客户端,不适用于 Spring 基于 Java 的 STOMP 客户端,它确实支持使用 WebSocket 和 Sockjs 请求。

因此,希望避免使用 cookie 的应用程序可能没有任何好处 HTTP 协议级别的身份验证替代方案。不使用 cookie, 他们可能更喜欢在 STOMP 消息传递协议级别使用 Headers 进行身份验证。 执行此作需要两个简单的步骤:spring-doc.cadn.net.cn

  1. 使用 STOMP 客户端在连接时传递身份验证标头。spring-doc.cadn.net.cn

  2. 使用ChannelInterceptor.spring-doc.cadn.net.cn

下一个示例使用服务器端配置来注册自定义身份验证 拦截 器。请注意,拦截器只需要进行身份验证并将 CONNECT 上的 user 标头Message.Spring 记录并保存经过身份验证的 用户,并将其与同一会话上的后续 STOMP 消息相关联。以下内容 示例显示了如何注册自定义身份验证侦听器:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor =
                        MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    Authentication user = ... ; // access authentication header(s)
                    accessor.setUser(user);
                }
                return message;
            }
        });
    }
}

另请注意,当您使用 Spring Security 的消息授权时,目前, 您需要确保身份验证ChannelInterceptorconfig 已排序 领先于 Spring Security 的。最好通过在 它自己的WebSocketMessageBrokerConfigurer标有@Order(Ordered.HIGHEST_PRECEDENCE + 99).spring-doc.cadn.net.cn

4.4.14. 授权

Spring Security 提供 WebSocket 子协议授权,该授权使用ChannelInterceptor根据消息中的 User 标头对消息进行授权。 此外,Spring Session 还提供 WebSocket 集成,以确保用户的 HTTP 会话在 WebSocket 会话仍处于活动状态时不会过期。spring-doc.cadn.net.cn

4.4.15. 用户目标

应用程序可以发送针对特定用户的消息,以及 Spring 的 STOMP 支持 识别前缀为/user/为此目的。 例如,客户端可能会订阅/user/queue/position-updates目的地。UserDestinationMessageHandler处理此目标并将其转换为 destination (destination (destination ) 中唯一的唯一目标(例如/queue/position-updates-user123). 这提供了订阅通用命名目标的便利性,而 同时,确保不会与订阅相同的其他用户发生冲突 目标,以便每个用户都可以接收唯一的 Stock Position 更新。spring-doc.cadn.net.cn

使用用户目标时,配置 broker 和 应用程序目标前缀,如启用 STOMP 中所示,否则 broker 将处理带有 “/user” 前缀的消息,这些消息只能由UserDestinationMessageHandler.

在发送端,消息可以发送到目标,例如/user/{username}/queue/position-updates,而 由UserDestinationMessageHandler发送到一个或多个目的地,每个目的地一个 会话。这允许应用程序中的任何组件 发送针对特定用户的消息,而不必了解更多信息 而不是其名称和通用目标。这也通过 annotation 和消息传递模板。spring-doc.cadn.net.cn

消息处理方法可以将消息发送给与 通过@SendToUser注释(也支持 类级别共享一个公共目标),如下例所示:spring-doc.cadn.net.cn

@Controller
public class PortfolioController {

    @MessageMapping("/trade")
    @SendToUser("/queue/position-updates")
    public TradeResult executeTrade(Trade trade, Principal principal) {
        // ...
        return tradeResult;
    }
}

如果用户有多个会话,则默认情况下,订阅了所有会话 的目标。但是,有时,可能需要 仅以发送正在处理的消息的会话为目标。您可以通过以下方式执行此作 设置broadcast属性设置为 false,如下例所示:spring-doc.cadn.net.cn

@Controller
public class MyController {

    @MessageMapping("/action")
    public void handleAction() throws Exception{
        // raise MyBusinessException here
    }

    @MessageExceptionHandler
    @SendToUser(destinations="/queue/errors", broadcast=false)
    public ApplicationError handleException(MyBusinessException exception) {
        // ...
        return appError;
    }
}
虽然用户目标通常意味着经过身份验证的用户,但这并不是严格要求的。 未与已验证的用户关联的 WebSocket 会话 可以订阅用户目标。在这种情况下,@SendToUser注解 的行为与broadcast=false(即,仅将 会话发送正在处理的消息)。

您可以从任何应用程序向用户目标发送消息 组件,例如,将SimpMessagingTemplate由 Java 配置创建,或者 XML 命名空间。(bean 名称为brokerMessagingTemplate如果需要 用于@Qualifier.)以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Service
public class TradeServiceImpl implements TradeService {

    private final SimpMessagingTemplate messagingTemplate;

    @Autowired
    public TradeServiceImpl(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    // ...

    public void afterTradeExecuted(Trade trade) {
        this.messagingTemplate.convertAndSendToUser(
                trade.getUserName(), "/queue/position-updates", trade.getResult());
    }
}
当您将用户目标与外部消息代理一起使用时,应检查代理 有关如何管理非活动队列的文档,以便在用户会话为 over,则删除所有唯一用户队列。例如,RabbitMQ 会创建自动删除 queues (当您使用目标,例如/exchange/amq.direct/position-updates. 因此,在这种情况下,客户端可以订阅/user/exchange/amq.direct/position-updates. 同样,ActiveMQ 具有用于清除非活动目标的配置选项

在多应用程序服务器方案中,用户目标可能保持未解析状态,因为 用户已连接到其他服务器。在这种情况下,您可以配置 destination 广播未解析的消息,以便其他服务器有机会尝试。 这可以通过userDestinationBroadcast属性的MessageBrokerRegistry在 Java 配置中,并且user-destination-broadcast属性 的message-broker元素。spring-doc.cadn.net.cn

4.4.16. 消息顺序

来自代理的消息将发布到clientOutboundChannel,从他们所在的位置 写入 WebSocket 会话。由于通道由ThreadPoolExecutor消息 在不同的线程中处理,并且客户端接收到的结果序列可能会 与发布的确切顺序不匹配。spring-doc.cadn.net.cn

如果这是一个问题,请启用setPreservePublishOrder标志,如下例所示:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    protected void configureMessageBroker(MessageBrokerRegistry registry) {
        // ...
        registry.setPreservePublishOrder(true);
    }

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker preserve-publish-order="true">
        <!-- ... -->
    </websocket:message-broker>

</beans>

设置该标志后,同一客户端会话中的消息将发布到clientOutboundChannel一次一个,以便保证发布顺序。 请注意,这会产生较小的性能开销,因此应仅在需要时启用它。spring-doc.cadn.net.cn

4.4.17. 事件

几个ApplicationContext事件已发布,并且可以 通过实现 Spring 的ApplicationListener接口:spring-doc.cadn.net.cn

  • BrokerAvailabilityEvent:指示代理何时可用或不可用。 虽然 “simple” broker 在启动时立即可用,并且在 应用程序正在运行,则 STOMP“代理中继”可能会丢失其连接 添加到功能齐全的 Broker 中(例如,如果 Broker 已重新启动)。代理中继 具有 reconnect 逻辑并重新建立与 broker 的 “system” 连接 当它回来时。因此,每当状态从 connected 到 disconnected,反之亦然。使用SimpMessagingTemplate应该 订阅此事件,并避免在 Broker 未发送消息时发送消息 可用。无论如何,他们都应该准备好处理MessageDeliveryException发送消息时。spring-doc.cadn.net.cn

  • SessionConnectEvent:在收到新的 STOMP CONNECT 时发布 指示新客户端会话的开始。该事件包含表示 connect,包括会话 ID、用户信息(如果有)和客户端的任何自定义标头 送。这对于跟踪客户端会话非常有用。订购的组件 到 this 事件中,可以将包含的消息包装为SimpMessageHeaderAccessorStompMessageHeaderAccessor.spring-doc.cadn.net.cn

  • SessionConnectedEvent:在SessionConnectEvent当 broker 已发送 STOMP CONNECTED 帧以响应 CONNECT.此时, STOMP 会话可以认为是完全建立的。spring-doc.cadn.net.cn

  • SessionSubscribeEvent:在收到新的 STOMP SUBSCRIBE 时发布。spring-doc.cadn.net.cn

  • SessionUnsubscribeEvent:在收到新的 STOMP UNSUBSCRIBE 时发布。spring-doc.cadn.net.cn

  • SessionDisconnectEvent:在 STOMP 会话结束时发布。DISCONNECT 可能会 已从客户端发送,或者它可能在 WebSocket 会话已关闭。在某些情况下,此事件会多次发布 每个会话。组件对于多个断开连接事件应该是幂等的。spring-doc.cadn.net.cn

当您使用功能齐全的代理时,STOMP“代理中继”会自动重新连接 “system” 连接。客户端连接、 但是,不会自动重新连接。假设启用了检测信号,则客户端 通常会注意到 Broker 在 10 秒内没有响应。客户需要 实现自己的 reconnecting logic。

4.4.18. 拦截

事件为生命周期提供通知 的 STOMP 连接,但并非针对每条客户端消息。应用程序还可以注册ChannelInterceptor拦截任何消息和处理链的任何部分。 以下示例显示如何拦截来自客户端的入站消息:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new MyChannelInterceptor());
    }
}

自定义ChannelInterceptor可以使用StompHeaderAccessorSimpMessageHeaderAccessor访问有关消息的信息,如下例所示:spring-doc.cadn.net.cn

public class MyChannelInterceptor implements ChannelInterceptor {

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        StompCommand command = accessor.getStompCommand();
        // ...
        return message;
    }
}

应用程序还可以实现ExecutorChannelInterceptor,这是一个子接口 之ChannelInterceptor在处理消息的线程中使用回调。 虽然ChannelInterceptor对于发送到通道的每条消息调用一次,ExecutorChannelInterceptor在每个MessageHandler订阅来自频道的消息。spring-doc.cadn.net.cn

请注意,与SessionDisconnectEvent前面描述的 DISCONNECT 消息 可以来自客户端,也可以在 WebSocket 会话已关闭。在某些情况下,拦截器可能会拦截此 message 的请求。组件在 多个断开连接事件。spring-doc.cadn.net.cn

4.4.19. STOMP 客户端

Spring 提供了一个基于 WebSocket 的 STOMP 客户端和一个基于 TCP 的 STOMP 客户端。spring-doc.cadn.net.cn

首先,您可以创建和配置WebSocketStompClient,如下例所示:spring-doc.cadn.net.cn

WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setTaskScheduler(taskScheduler); // for heartbeats

在前面的示例中,您可以将StandardWebSocketClientSockJsClient, 因为这也是WebSocketClient.这SockJsClient能 使用 WebSocket 或基于 HTTP 的传输作为后备。有关更多详细信息,请参阅SockJsClient.spring-doc.cadn.net.cn

接下来,您可以建立连接并为 STOMP 会话提供处理程序。 如下例所示:spring-doc.cadn.net.cn

String url = "ws://127.0.0.1:8080/endpoint";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(url, sessionHandler);

当会话可供使用时,将通知处理程序,如下例所示:spring-doc.cadn.net.cn

public class MyStompSessionHandler extends StompSessionHandlerAdapter {

    @Override
    public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
        // ...
    }
}

建立会话后,可以发送任何有效负载,并且 serialized 替换为配置的MessageConverter,如下例所示:spring-doc.cadn.net.cn

session.send("/topic/something", "payload");

您还可以订阅目标。这subscribe方法需要处理程序 ,并返回一个Subscription处理 用于取消订阅。对于收到的每条消息,处理程序可以指定目标Objecttype 中,payload 应反序列化为该类型,如下例所示:spring-doc.cadn.net.cn

session.subscribe("/topic/something", new StompFrameHandler() {

    @Override
    public Type getPayloadType(StompHeaders headers) {
        return String.class;
    }

    @Override
    public void handleFrame(StompHeaders headers, Object payload) {
        // ...
    }

});

要启用 STOMP 心跳,您可以配置WebSocketStompClient替换为TaskScheduler以及可选的自定义检测信号间隔(写入不活动为 10 秒, 这会导致发送检测信号,10 秒为读取不活动状态,这会导致 关闭连接)。spring-doc.cadn.net.cn

WebSocketStompClient仅在不活动的情况下发送心跳,即当 no 发送其他消息。使用外部代理时,这可能会带来挑战 因为具有非代理目标的消息表示活动,但实际上并非 转发给代理。在这种情况下,您可以配置TaskScheduler初始化 External Broker 时,确保 当只有带有非 broker 的消息时,heartbeat 也会转发到代理 destination 的 Destination 发送。spring-doc.cadn.net.cn

当您使用WebSocketStompClient用于性能测试以模拟数千个 的客户端,请考虑关闭检测信号,因为每个 Connection 会计划自己的检测信号任务,并且未针对 在同一台计算机上运行的大量客户端。

STOMP 协议还支持回执,其中客户端必须添加receipt标头,服务器在 send 或 subscribe 的 intent 请求。为了支持这一点,StompSession提供setAutoReceipt(boolean)这会导致receiptheader 为 在每个后续 send 或 subscribe 事件上添加。 或者,您也可以手动将收据标头添加到StompHeaders. send 和 subscribe 都返回一个Receiptable可用于注册接收成功和失败回调。 对于此功能,您必须为客户端配置TaskScheduler以及收据过期前的时间(默认为 15 秒)。spring-doc.cadn.net.cn

请注意,StompSessionHandler本身是一个StompFrameHandler,这样就可以 除了handleExceptioncallback 的 消息处理异常和handleTransportError为 传输级错误包括ConnectionLostException.spring-doc.cadn.net.cn

4.4.20. WebSocket 作用域

每个 WebSocket 会话都有一个属性映射。该映射作为标题附加到 入站客户端消息,可以从控制器方法访问,如下例所示:spring-doc.cadn.net.cn

@Controller
public class MyController {

    @MessageMapping("/action")
    public void handle(SimpMessageHeaderAccessor headerAccessor) {
        Map<String, Object> attrs = headerAccessor.getSessionAttributes();
        // ...
    }
}

您可以在websocket范围。 你可以将 WebSocket 范围的 bean 注入控制器和任何通道拦截器 在clientInboundChannel.这些通常是单例和实时 比任何单个 WebSocket 会话都长。因此,您需要使用 范围 proxy 模式,如下例所示:spring-doc.cadn.net.cn

@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {

    @PostConstruct
    public void init() {
        // Invoked after dependencies injected
    }

    // ...

    @PreDestroy
    public void destroy() {
        // Invoked when the WebSocket session ends
    }
}

@Controller
public class MyController {

    private final MyBean myBean;

    @Autowired
    public MyController(MyBean myBean) {
        this.myBean = myBean;
    }

    @MessageMapping("/action")
    public void handle() {
        // this.myBean from the current WebSocket session
    }
}

与任何自定义作用域一样, Spring 会初始化一个新的MyBean实例的第一个 从控制器访问实例并将实例存储在 WebSocket 中 会话属性。随后返回相同的实例,直到会话 结束。WebSocket 范围的 bean 调用了所有 Spring 生命周期方法,如 如前面的示例所示。spring-doc.cadn.net.cn

4.4.21. 性能

在性能方面没有灵丹妙药。许多因素 影响它,包括消息的大小和音量,是否应用 方法执行需要阻塞和外部因素的工作 (例如网络速度和其他问题)。本节的目标是提供 可用配置选项的概述以及一些想法 关于如何推断扩展。spring-doc.cadn.net.cn

在消息传递应用程序中,消息通过通道进行异步传递 由线程池支持的执行。配置此类应用程序需要 对频道和消息流有很好的了解。因此,它是 建议查看消息流spring-doc.cadn.net.cn

显而易见的起点是配置支持clientInboundChannelclientOutboundChannel.默认情况下,两者 配置为可用处理器数量的两倍。spring-doc.cadn.net.cn

如果带注解的方法中消息的处理主要是 CPU 绑定的,则 的线程数clientInboundChannel应保持靠近 处理器数量。如果他们所做的工作更受 IO 限制并且需要阻塞 或者等待数据库或其他外部系统,则线程池大小 可能需要增加。spring-doc.cadn.net.cn

ThreadPoolExecutor具有三个重要属性:核心线程池大小、 最大线程池大小和队列要存储的容量 没有可用线程的任务。spring-doc.cadn.net.cn

一个常见的混淆点是配置核心池大小(例如,10) 最大池大小(例如,20)将导致线程池具有 10 到 20 个线程。 实际上,如果容量保留为默认值 Integer.MAX_VALUE, 线程池永远不会超过核心池大小,因为 所有其他任务都已排队。spring-doc.cadn.net.cn

请参阅 javadocThreadPoolExecutor了解这些属性的工作原理,以及 了解各种排队策略。spring-doc.cadn.net.cn

clientOutboundChannel端,它就是向 WebSocket 发送消息 客户。如果客户端位于快速网络上,则线程数应 保持接近可用处理器的数量。如果它们速度较慢或开启 低带宽,它们需要更长的时间来消耗消息,并给 thread 池。因此,增加线程池大小变得必要。spring-doc.cadn.net.cn

虽然clientInboundChannel可以预测 — 毕竟,它基于应用程序的作用 — 如何配置 “clientOutboundChannel” 更难,因为它基于其他因素 应用程序的控制。因此,另外两个 属性与消息的发送有关:sendTimeLimitsendBufferSizeLimit.您可以使用这些方法来配置 send 允许接收以及发送时可以缓冲多少数据 消息。spring-doc.cadn.net.cn

一般的思路是,在任何给定时间,只能使用单个线程 以发送到客户端。同时,所有其他消息都会被缓冲,而您 可以使用这些属性来决定允许发送消息的时间 take 以及在此期间可以缓冲多少数据。请参阅 javadoc 和 XML 架构的文档,以获取重要的其他详细信息。spring-doc.cadn.net.cn

以下示例显示了可能的配置:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setSendTimeLimit(15 * 1000).setSendBufferSizeLimit(512 * 1024);
    }

    // ...

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker>
        <websocket:transport send-timeout="15000" send-buffer-size="524288" />
        <!-- ... -->
    </websocket:message-broker>

</beans>

您还可以使用前面显示的 WebSocket 传输配置来配置 传入 STOMP 消息的最大允许大小。理论上,WebSocket 消息的大小几乎可以是无限的。在实践中,WebSocket 服务器强加 限制 — 例如,Tomcat 上为 8K,Jetty 上为 64K。因此,STOMP 客户端 (例如 JavaScript webstomp-client 等)在 16K 边界处拆分较大的 STOMP 消息,并将其作为多个发送 WebSocket 消息,这需要服务器缓冲并重新组装。spring-doc.cadn.net.cn

Spring 的 STOMP-over-WebSocket 支持可以做到这一点,因此应用程序可以配置 STOMP 消息的最大大小,与 WebSocket 服务器特定的消息无关 大小。请记住,WebSocket 消息大小会自动 如有必要,以确保它们可以在 最低。spring-doc.cadn.net.cn

以下示例显示了一种可能的配置:spring-doc.cadn.net.cn

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setMessageSizeLimit(128 * 1024);
    }

    // ...

}

以下示例显示了与上述示例等效的 XML 配置:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:message-broker>
        <websocket:transport message-size="131072" />
        <!-- ... -->
    </websocket:message-broker>

</beans>

关于扩展的一个重要点涉及使用多个应用程序实例。 目前,您无法使用 simple broker 执行此作。 但是,当您使用功能齐全的代理(如 RabbitMQ)时,每个应用程序 实例连接到代理,并且从一个应用程序广播消息 实例可以通过 broker 广播到连接的 WebSocket 客户端 通过任何其他应用程序实例。spring-doc.cadn.net.cn

4.4.22. 监控

当您使用@EnableWebSocketMessageBroker<websocket:message-broker>钥匙 基础设施组件自动收集统计数据和计数器,这些统计数据和计数器提供 对应用程序内部状态的重要见解。配置 还声明了WebSocketMessageBrokerStats那会聚集所有 available information 放在一个位置,默认情况下将其记录在INFO级别一次 每 30 分钟一班。这个 bean 可以通过 Spring 的MBeanExporter以便在运行时查看(例如,通过 JDK 的jconsole). 以下列表总结了可用信息:spring-doc.cadn.net.cn

客户端 WebSocket 会话
当前

指示有多少个客户端会话 目前,计数进一步按 WebSocket 与 HTTP 细分 流式传输和轮询 Sockjs 会话。spring-doc.cadn.net.cn

指示已建立的会话总数。spring-doc.cadn.net.cn

异常闭合
连接失败

已建立但 在 60 秒内未收到任何消息后关闭。这是 通常表示代理或网络问题。spring-doc.cadn.net.cn

超出发送限制

会话在超过配置的发送后关闭 timeout 或 send buffer 限制(可能发生在慢速客户端) (请参阅上一节)。spring-doc.cadn.net.cn

传输错误

会话在传输错误后关闭,例如 无法读取或写入 WebSocket 连接,或者 HTTP 请求或响应。spring-doc.cadn.net.cn

STOMP 框架

CONNECT、CONNECTED 和 DISCONNECT 帧的总数 processed,指示在 STOMP 级别连接的客户端数。请注意, 当会话异常关闭或 客户端关闭而不发送 DISCONNECT 帧。spring-doc.cadn.net.cn

STOMP 代理中继
TCP 连接

指示代表客户端的 TCP 连接数 为代理建立 WebSocket 会话。这应该等于 客户端 WebSocket 会话数 + 1 个额外的共享“系统”连接 用于从应用程序内发送消息。spring-doc.cadn.net.cn

STOMP 框架

CONNECT、CONNECTED 和 DISCONNECT 帧的总数 转发给经纪人或从经纪人代表客户接收。请注意, DISCONNECT 帧将发送到代理,而不管客户端 WebSocket 如何 会话已关闭。因此,DISCONNECT 帧数较低 代理正在主动关闭连接(可能是由于 检测信号未及时到达、输入帧无效或其他问题)。spring-doc.cadn.net.cn

客户端入站通道

来自支持clientInboundChannel,从而深入了解传入消息处理的运行状况。任务排队 此处指示应用程序可能太慢,无法处理消息。 如果存在 I/O 绑定任务(例如,数据库查询速度慢、对第三方的 HTTP 请求 REST API 等),请考虑增加线程池大小。spring-doc.cadn.net.cn

客户端出站通道

来自支持clientOutboundChannel这提供了对向客户端广播消息的运行状况的见解。任务 此处排队表示客户端使用消息的速度太慢。 解决此问题的一种方法是增加线程池大小以容纳 预期的并发慢速客户端数。另一种选择是将 发送超时和发送缓冲区大小限制(请参阅上一节)。spring-doc.cadn.net.cn

Sockjs 任务调度程序

来自 Sockjs 任务调度程序线程池的统计信息 用于发送检测信号。请注意,当在 STOMP 级别,则禁用 SockJS 心跳。spring-doc.cadn.net.cn

4.4.23. 测试

使用 Spring 的 STOMP-over-WebSocket 时,有两种主要方法可以测试应用程序 支持。第一种是编写服务器端测试来验证功能 控制器及其带注释的消息处理方法。第二种是写 涉及运行客户端和服务器的完整端到端测试。spring-doc.cadn.net.cn

这两种方法并不相互排斥。相反,每个都有其位置 在整体测试策略中。服务器端测试更集中,更易于编写 并维护。另一方面,端到端集成测试更完整,并且 测试的次数更多,但它们的编写和维护也更多。spring-doc.cadn.net.cn

服务器端测试的最简单形式是编写控制器单元测试。然而 这还不够有用,因为控制器的大部分工作都取决于其 附注。纯粹的单元测试根本无法测试这一点。spring-doc.cadn.net.cn

理想情况下,被测控制器应该像在运行时一样被调用,这与 使用 Spring MVC Test 测试处理 HTTP 请求的控制器的方法 框架 — 也就是说,不运行 Servlet 容器,而是依赖 Spring Framework 调用带注释的控制器。与 Spring MVC Test 一样,您有两个 此处可能的替代方案,要么使用 “context-based” 要么使用 “standalone” 设置:spring-doc.cadn.net.cn

  • 在 Spring TestContext 框架中,injectclientInboundChannel作为测试字段,以及 使用它来发送要由 Controller 方法处理的消息。spring-doc.cadn.net.cn

  • 手动设置调用 控制器(即SimpAnnotationMethodMessageHandler) 并传递消息 控制器直接连接到它。spring-doc.cadn.net.cn

这两个设置场景都在 tests for the stock portfolio 示例应用程序中进行了演示。spring-doc.cadn.net.cn

第二种方法是创建端到端集成测试。为此,您需要 以嵌入式模式运行 WebSocket 服务器并作为 WebSocket 客户端连接到它 发送包含 STOMP 帧的 WebSocket 消息。 Stock portfolio 示例应用程序的测试也通过使用 Tomcat 作为嵌入式 WebSocket 服务器和一个简单的 STOMP 客户端进行测试。spring-doc.cadn.net.cn

5. 其他 Web 框架

本章详细介绍了 Spring 与第三方 Web 框架的集成。spring-doc.cadn.net.cn

Spring 框架的核心价值主张之一是支持选择。一般来说,Spring 不会强迫您使用或购买任何 特定的架构、技术或方法(尽管它肯定建议 一些优于其他)。这种自由选择架构、技术或 与开发人员及其开发团队最相关的方法是 可以说在 Web 领域最为明显,Spring 提供了自己的 Web 框架 (Spring MVCSpring WebFlux),同时, 支持与许多流行的第三方 Web 框架集成。spring-doc.cadn.net.cn

5.1. 通用配置

在深入研究每个受支持的 Web 框架的集成细节之前,让我们 首先看一下不特定于任何一个 Web 的常见 Spring 配置 框架。(本节同样适用于 Spring 自己的 Web 框架变体.)spring-doc.cadn.net.cn

Spring 的轻量级 应用程序模型是分层体系结构的模型。请记住,在“经典” 分层架构,Web 层只是众多层之一。它充当 入口点,并委托给服务对象 (立面)来满足特定于业务的(以及 presentation-technology agnostic) 用例。在 Spring 中,这些服务对象、任何其他 特定于业务的对象、数据访问对象和其他对象存在于不同的“业务 context“,该对象不包含 Web 或表示层对象(表示对象、 ,通常在不同的“表示 context“)的本节详细介绍了如何配置 Spring 容器(一个WebApplicationContext),其中包含应用程序中的所有 'business bean'。spring-doc.cadn.net.cn

继续具体作,您需要做的就是声明一个ContextLoaderListener在标准 Java EE servlet 中web.xml文件,并添加contextConfigLocation<context-param/> 部分(在同一个文件中),该部分定义 要加载的 Spring XML 配置文件集。spring-doc.cadn.net.cn

请考虑以下<listener/>配置:spring-doc.cadn.net.cn

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

进一步考虑以下内容<context-param/>配置:spring-doc.cadn.net.cn

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>

如果未指定contextConfigLocationcontext 参数、ContextLoaderListener查找名为/WEB-INF/applicationContext.xml自 负荷。加载上下文文件后, Spring 会创建一个WebApplicationContext对象,并将其存储在ServletContext网络 应用。spring-doc.cadn.net.cn

所有 Java Web 框架都是在 Servlet API 之上构建的,因此您可以使用 以下代码片段访问此 “业务上下文”ApplicationContextContextLoaderListener.spring-doc.cadn.net.cn

以下示例演示如何获取WebApplicationContext:spring-doc.cadn.net.cn

WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);

WebApplicationContextUtilsclass 是为了方便起见,所以你不需要记住ServletContext属性。其getWebApplicationContext()method 返回null如果对象 在WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE钥匙。与其冒险获得NullPointerExceptions在您的应用程序中,它更好 要使用getRequiredWebApplicationContext()方法。此方法会引发异常 当ApplicationContext缺失。spring-doc.cadn.net.cn

一旦您引用了WebApplicationContext中,您可以通过 bean 的 name 或 type。大多数开发人员按名称检索 bean,然后将它们强制转换为他们的 实现的接口。spring-doc.cadn.net.cn

幸运的是,本节中的大多数框架都有更简单的方法来查找 bean。 它们不仅可以轻松地从 Spring 容器中获取 bean,而且还允许您 在他们的控制器上使用依赖注入。每个 Web 框架部分都有更多详细信息 关于其具体的整合策略。spring-doc.cadn.net.cn

5.2. JSF

JavaServer Faces (JSF) 是 JCP 的标准、基于组件、事件驱动的 Web 用户界面框架。它是 Java EE 保护伞的正式组成部分,但也是 可单独使用,例如通过在 Tomcat 中嵌入 Mojarra 或 MyFaces。spring-doc.cadn.net.cn

请注意,最新版本的 JSF 与 CDI 基础设施密切相关 在应用服务器中,一些新的 JSF 功能只能在这样的 环境。Spring 的 JSF 支持不再积极发展,主要是 存在是为了在对基于 JSF 的旧应用程序进行现代化改造时进行迁移。spring-doc.cadn.net.cn

Spring 的 JSF 集成中的关键元素是 JSFELResolver机制。spring-doc.cadn.net.cn

5.2.1. Spring Bean 解析器

SpringBeanFacesELResolver符合 JSFELResolver实现 与 JSF 和 JSP 使用的标准统一 EL 集成。它委托给 Spring 的 “商业环境”WebApplicationContext首先,然后到 基础 JSF 实现的 default 解析程序。spring-doc.cadn.net.cn

在配置方面,您可以定义SpringBeanFacesELResolver在 JSF 中faces-context.xml文件,如下例所示:spring-doc.cadn.net.cn

<faces-config>
    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
        ...
    </application>
</faces-config>

5.2.2. 使用FacesContextUtils

自定义ELResolver将 property 映射到 中的 bean 时效果很好faces-config.xml,但有时,您可能需要显式地获取 Bean。 这FacesContextUtilsClass 让这一切变得简单。它类似于WebApplicationContextUtils,除了 它需要一个FacesContext参数而不是ServletContext参数。spring-doc.cadn.net.cn

以下示例演示如何使用FacesContextUtils:spring-doc.cadn.net.cn

ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());

5.3. Apache Struts 2.x

Struts 由 Craig McClanahan 发明,是一个开源项目 由 Apache Software Foundation 托管。当时,它大大简化了 JSP/Servlet 编程范式,并赢得了许多使用专有 框架。它简化了编程模型,它是开源的(因此是免费的,如 啤酒),并且它有一个庞大的社区,这让该项目得以发展并在其中流行起来 Java Web 开发人员。spring-doc.cadn.net.cn

作为原始 Struts 1.x 的后续版本,请查看 Struts 2.x 和 Struts 提供的 Spring 插件。 内置的 Spring 集成。spring-doc.cadn.net.cn

5.4. Apache Tapestry 5.x

Tapestry 是一个面向组件的框架,用于创建 动态、健壮、高度可扩展的 Java Web 应用程序。spring-doc.cadn.net.cn

虽然 Spring 有自己强大的 Web 层,但还有许多独特的 使用 Tapestry 组合构建企业 Java 应用程序的优势 用于 Web 用户界面,Spring 容器用于较低层。spring-doc.cadn.net.cn

有关更多信息,请参阅 Tapestry 的 Spring 专用集成模块spring-doc.cadn.net.cn

5.5. 更多资源

以下链接指向有关 本章。spring-doc.cadn.net.cn