Web Servlet

1. Spring Web MVC

Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,并且从一开始就包含在Spring 框架中。正式名称“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

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

DispatcherServlet,与任何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并覆盖特定方法(请参阅上下文层次结构下的示例)。
对于编程用例,一个GenericWebApplicationContext可以用作替代AnnotationConfigWebApplicationContext. 请参阅GenericWebApplicationContextjavadoc 了解详情。

以下示例web.xml配置寄存器并初始化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 遵循不同的初始化顺序。Spring Boot 不是挂接到Servlet 容器的生命周期,而是使用 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实例。 这些 bean是有效继承的,并且可以在特定于 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

Beans 解释

HandlerMappingspring-doc.cadn.net.cn

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

两个主要的HandlerMapping实现是RequestMappingHandlerMapping(支持@RequestMapping注释方法)和SimpleUrlHandlerMapping(它维护对处理程序的 URI 路径模式的显式注册)。spring-doc.cadn.net.cn

HandlerAdapterspring-doc.cadn.net.cn

帮助DispatcherServlet调用映射到请求的处理程序,无论 如何实际调用处理程序。例如,调用带注释的控制器 需要解析注释。的主要目的HandlerAdapter是 以屏蔽DispatcherServlet从这样的细节来看。spring-doc.cadn.net.cn

HandlerExceptionResolverspring-doc.cadn.net.cn

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

ViewResolverspring-doc.cadn.net.cn

解析逻辑String-based 视图名称从处理程序返回到实际的View用它来呈现响应。请参阅视图分辨率视图技术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

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

FlashMapManagerspring-doc.cadn.net.cn

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

1.1.3. Web MVC 配置

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

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

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

1.1.4. Servlet 配置

在 Servlet 3.0+ 环境中,您可以选择配置 Servlet 容器 以编程方式作为替代方案或与web.xml文件。以下内容 示例注册一个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

isAsyncSupported受保护的方法AbstractDispatcherServletInitializer提供了一个位置来启用异步支持DispatcherServlet以及所有 映射到它的过滤器。默认情况下,此标志设置为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

  • 语言环境解析器绑定到请求,以让流程中的元素 解析处理请求时要使用的区域设置(呈现视图、准备 数据,依此类推)。如果不需要区域设置解析,则不需要区域设置解析器。spring-doc.cadn.net.cn

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

  • 如果指定多部分文件解析器,则会检查请求中的多部分。如果 多部分,则请求被包装在MultipartHttpServletRequest为 过程中由其他元素进一步加工。有关更多信息,请参阅 Multipart Resolver 有关多部分处理的信息。spring-doc.cadn.net.cn

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

  • 如果返回模型,则呈现视图。如果没有返回任何模型(可能是由于 拦截请求的预处理器或后处理器,可能是为了安全起见 原因),则不会呈现任何视图,因为请求可能已经得到满足。spring-doc.cadn.net.cn

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

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

您可以自定义单个DispatcherServlet实例,通过添加 Servlet 初始化参数 (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) 更改为 指示可以在哪里找到上下文。字符串可能由多个 strings(使用逗号作为分隔符)来支持多个上下文。在以下情况下 具有定义两次的 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 可能包含编码的保留字符,例如 或 can 反过来 在解码后改变路径的结构,这也可以带来安全性 问题。此外,Servlet 容器可能会规范化"/"";"servletPath到变化 度数,这使得它进一步无法执行startsWith比较 这requestURI.spring-doc.cadn.net.cn

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

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

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

1.1.7. 拦截

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

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

有关如何作的示例,请参阅有关 MVC 配置部分中的拦截器 配置拦截器。您也可以使用单独的 setter 直接注册它们HandlerMapping实现。spring-doc.cadn.net.cn

postHandle方法不太有用@ResponseBodyResponseEntity方法 响应是在HandlerAdapter和之前postHandle.这意味着对响应进行任何更改为时已晚,例如将 一个额外的标题。对于此类方案,可以实现ResponseBodyAdvice以及 将其声明为控制器通知 bean 或直接在RequestMappingHandlerAdapter.spring-doc.cadn.net.cn

1.1.8. 例外

如果在请求映射期间发生异常或从请求处理程序(例如 一个@Controller)、DispatcherServlet委托到链HandlerExceptionResolverbean 来解决异常并提供替代处理,这通常是 错误响应。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 状态代码。 也可以看看替代ResponseEntityExceptionHandlerREST 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 声明内置解析器 例外,对于@ResponseStatus带注释的异常,并支持@ExceptionHandler方法。您可以自定义该列表或替换它。spring-doc.cadn.net.cn

容器错误页面

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

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

在前面的示例中,当异常冒泡或响应处于错误状态时, Servlet 容器在容器内对配置的 URL 进行 ERROR 分派 (例如,/error).然后由DispatcherServlet,可能会映射它 设置为@Controller,可以实现以返回带有模型的错误视图名称 或呈现 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

的子类AbstractCachingViewResolver缓存它们解析的视图实例。 缓存提高了某些视图技术的性能。您可以关闭 缓存,方法是将cache属性设置为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接口,该接口基于 请求文件名或Accept页眉。请参阅内容协商spring-doc.cadn.net.cn

BeanNameViewResolverspring-doc.cadn.net.cn

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

处理

您可以通过声明多个解析器 bean 来链接查看解析器,并在必要时通过 将order属性来指定排序。请记住,order 属性越高, 视图解析器在链中的位置越晚。spring-doc.cadn.net.cn

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

配置视图分辨率就像添加ViewResolver豆子到你的Spring 配置。MVC Config视图解析器提供了专用的配置 API,用于添加对 HTML 模板有用的无逻辑视图控制器 没有控制器逻辑的渲染。spring-doc.cadn.net.cn

重 定向

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

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

请注意,如果控制器方法用@ResponseStatus,注释 值优先于由RedirectView.spring-doc.cadn.net.cn

转发

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

内容协商

ContentNegotiatingViewResolver不是解析视图本身,而是解析委托 到其他视图解析器,然后选择与所请求的表示类似的视图 由客户。表示形式可以从Accept标头或从 query 参数(例如"/path?format=pdf").spring-doc.cadn.net.cn

ContentNegotiatingViewResolver选择适当的View处理请求 通过将请求媒体类型与媒体类型(也称为Content-Type) 由View与其每个ViewResolvers.这 第一View在具有兼容Content-Type返回表示给客户端。如果无法由ViewResolver链 通过DefaultViews咨询财产。这 后一种选项适用于单例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查找语言环境解析器,如果 找到一个,它尝试使用它来设置区域设置。通过使用RequestContext.getLocale()方法,您始终可以检索由区域设置解析器解析的区域设置。spring-doc.cadn.net.cn

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

语言环境解析器和拦截器在org.springframework.web.servlet.i18n包,并在应用程序中配置 上下文以正常方式。以下区域设置解析器选择包含在 Spring。spring-doc.cadn.net.cn

时区

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

如果可用,用户的TimeZone可以通过使用RequestContext.getTimeZone()方法。自动使用时区信息 在任何日期/时间ConverterFormatter向 Spring 的ConversionService.spring-doc.cadn.net.cn

标头解析器

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

此区域设置解析器检查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在调度程序的 应用程序上下文。下一个示例显示,对所有*.view资源 包含名为siteLanguage现在更改区域设置。因此,例如, 对 URL 的请求,https://www.sf.net/home.view?siteLanguage=nl,更改站点 语言到荷兰语。以下示例演示如何拦截区域设置: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 框架主题来设置 应用程序,从而增强用户体验。主题是静态 资源,通常是样式表和图像,这些资源会影响 应用。spring-doc.cadn.net.cn

定义主题

要在 Web 应用程序中使用主题,您必须设置org.springframework.ui.context.ThemeSource接口。这WebApplicationContext接口扩展ThemeSource而是将其职责委托给专门的 实现。默认情况下,委托是org.springframework.ui.context.support.ResourceBundleThemeSource实现 从类路径的根目录加载属性文件。使用自定义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使用空的基本名称前缀。结果, 属性文件是从类路径的根目录加载的。因此,您将cool.properties类路径根目录中的主题定义(对于 示例,在/WEB-INF/classes).这ResourceBundleThemeSource使用标准 Java 资源包加载机制,允许主题完全国际化。为 例如,我们可以有一个/WEB-INF/classes/cool_nl.properties引用特殊 带有荷兰语文本的背景图像。spring-doc.cadn.net.cn

解决主题

定义主题后,如上一节所述, 您决定使用哪个主题。这DispatcherServlet查找名为themeResolver找出哪个ThemeResolver实现以使用。主题解析器的工作原理大致相同 方式作为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. 多部分解析器

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

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

阿帕奇共享资源FileUpload

使用 Apache CommonsFileUpload,您可以配置类型为CommonsMultipartResolver名称为multipartResolver.您还需要拥有 这commons-fileuploadjar 作为对类路径的依赖项。spring-doc.cadn.net.cn

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

共享资源文件上传传统上仅适用于 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名称为multipartResolver.spring-doc.cadn.net.cn

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

1.1.13. 日志记录

Spring MVC 中的 DEBUG 级日志记录被设计为紧凑、最小和 人性化。它侧重于对 与其他仅在调试特定问题时有用的其他问题相比。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-web模块提供了一些有用的过滤器: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-web模块提供FormContentFilter拦截 HTTP PUT、PATCH 和 DELETE 内容类型为application/x-www-form-urlencoded,从 请求的正文,并将ServletRequest制作表单数据 可通过ServletRequest.getParameter*()方法系列。spring-doc.cadn.net.cn

1.2.2. 请求头转发

当请求通过代理(例如负载均衡器)时,主机、端口和 方案可能会发生变化,这使得创建指向正确链接成为一项挑战 从客户端的角度来看,主机、端口和方案。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

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

为了支持异步请求和错误分派 filter 应映射为DispatcherType.ASYNC还有DispatcherType.ERROR. 如果使用 Spring Framework 的AbstractAnnotationConfigDispatcherServletInitializer(参见 Servlet 配置)所有过滤器都会自动注册所有分派 类型。但是,如果通过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 配置) 所有筛选器都会自动为所有调度类型注册。但是,如果注册 过滤器通过web.xml或在 Spring Boot 中通过FilterRegistrationBean请务必包括DispatcherType.ASYNC.spring-doc.cadn.net.cn

1.2.4. CORS

Spring MVC 通过对 CORS 配置的注释提供细粒度的支持 控制器。但是,当与 Spring Security 一起使用时,我们建议依赖内置的CorsFilter必须在 Spring Security 的过滤器链之前排序。spring-doc.cadn.net.cn

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

1.3. 带注释的控制器

Spring MVC 提供了一个基于注释的编程模型,其中@Controller@RestController组件使用注释来表达请求映射、请求输入、 异常处理等。带注释的控制器具有灵活的方法签名和 不必扩展基类,也不必实现特定的接口。 以下示例显示了由注释定义的控制器: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. 声明

您可以使用标准 Spring Bean 定义来定义控制器 Bean Servlet的WebApplicationContext.这@Controller刻板印象允许自动检测, 与 Spring 通用支持对检测保持一致@Component类路径中的类 并为它们自动注册 bean 定义。它也充当了对 带注释的类,指示其作为 Web 组件的角色。spring-doc.cadn.net.cn

启用此类自动检测@Controllerbean,您可以将组件扫描添加到 您的 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 的接口回调(例如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— 将字符串模式与字符串路径匹配。这是原版 解决方案也用于在 Spring 配置中选择类路径上的资源,在 文件系统和其他位置。它的效率较低,并且字符串路径输入是 有效处理 URL 的编码和其他问题的挑战。spring-doc.cadn.net.cn

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

PathPattern支持与AntPathMatcher.此外,它还 支持捕获模式,例如{*spring},用于匹配 0 个或多个路径段 在路径的尽头。PathPattern还限制了用于匹配多个 路径段,以便仅在模式的末尾允许它。这消除了许多 为给定请求选择最佳匹配模式时出现歧义的情况。 有关完整的模式语法,请参阅 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")),但你可以 如果名称相同并且您的代码是通过调试编译的,请省略该详细信息 信息或使用-parametersJava 8 上的编译器标志。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/**)被认为较少 比没有双通配符的其他模式更具体。spring-doc.cadn.net.cn

有关完整详细信息,请点击上面链接到模式比较器。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 时。路径扩展的一个安全替代方案是 以使用查询参数策略。如果必须使用文件扩展名,请考虑限制 它们通过mediaTypesContentNegotiationConfigurer 的属性。spring-doc.cadn.net.cn

后缀匹配和 RFD

反射文件下载 (RFD) 攻击与 XSS 类似,因为它依赖于请求输入 (例如,查询参数和 URI 变量)反映在响应中。但是,而不是 将 JavaScript 插入 HTML 中,RFD 攻击依赖于浏览器切换来执行 下载响应,并在稍后双击时将响应视为可执行脚本。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 路径包含的文件扩展名既不 允许为安全,也未明确注册用于内容协商。但是,它可以 当 URL 直接输入浏览器时,可能会产生副作用。spring-doc.cadn.net.cn

默认情况下,许多通用路径扩展被允许为安全。定制应用HttpMessageConverter实现可以显式注册内容的文件扩展名 协商以避免出现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属性,按内容类型缩小映射范围。

consumes属性还支持否定表达式——例如,!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指除“文本/纯色”以外的任何内容类型。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.

您还可以将相同的方法用于请求标头条件,如以下示例所示: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 条件,但最好使用 consumesproduce 代替。
HTTP 头, 选项

@GetMapping(和@RequestMapping(method=HttpMethod.GET)) 支持 HTTP HEAD 透明地用于请求映射。控制器方法不需要更改。 响应包装器,应用于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 是通过设置Allowresponse 标头添加到 HTTP 列表中 所有@RequestMapping具有匹配 URL 模式的方法。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是 组合注释的示例。提供它们是因为可以说,大多数 控制器方法应映射到特定的 HTTP 方法,而不是使用@RequestMapping, 默认情况下,它与所有 HTTP 方法匹配。如果您需要一个组合的示例 注释,看看这些是如何声明的。spring-doc.cadn.net.cn

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

显式注册

您可以以编程方式注册处理程序方法,这些方法可用于动态 注册或高级情况,例如同一处理程序的不同实例 在不同的 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 注入控制器的目标处理程序和处理程序映射。
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 注入控制器的目标处理程序和处理程序映射。
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

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

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

当前经过身份验证的用户 — 可能是特定的Principal实现类(如果已知)。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

当前请求区域设置,由最具体的LocaleResolver可用(在 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 请求参数,包括多部分文件。参数值 转换为声明的方法参数类型。看@RequestParam也 作为多部分spring-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

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

@RequestPartspring-doc.cadn.net.cn

要访问multipart/form-data请求,将零件的主体替换为HttpMessageConverter. 请参阅分段spring-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

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

SessionStatus+ 班级@SessionAttributesspring-doc.cadn.net.cn

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

UriComponentsBuilderspring-doc.cadn.net.cn

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

@SessionAttributespring-doc.cadn.net.cn

用于访问任何会话属性,与存储在会话中的模型属性相反 由于类级@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 标头和正文)的返回值将被转换 通过HttpMessageConverter实现并写入响应。 请参阅 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用于与隐式模型一起渲染的实例 — 确定 通过命令对象和@ModelAttribute方法。处理程序方法还可以 通过声明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

要使用的视图和模型属性,以及响应状态(可选)。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,根据请求的 媒体类型,例如“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 字段格式。spring-doc.cadn.net.cn

类型转换中的一个实际问题是处理空的 String 源值。 如果这样的值变成null类型转换的结果。 这可能是Long,UUID和其他目标类型。如果要允许null要注入,请使用required标志,或声明 argument 作为@Nullable.spring-doc.cadn.net.cn

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

或者,您可以专门处理例如生成的MissingPathVariableException在必需的情况下@PathVariable.转换后的空值将被视为 一个空的原始值,因此相应的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 变量来屏蔽该变量内容,并确保请求可以 成功匹配,独立于矩阵变量顺序和存在。 以下示例使用矩阵变量: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
}

鉴于所有路径段都可能包含矩阵变量,有时可能需要 消除矩阵变量预期位于哪个路径变量中的歧义。 以下示例显示了如何执行此作: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通过路径匹配。在 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.

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

如果目标方法参数类型不是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

考虑以下带有标头的请求: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参数,则填充地图 替换为所有标头值。spring-doc.cadn.net.cn

内置支持可用于将逗号分隔的字符串转换为 类型转换系统已知的字符串或其他类型的数组或集合。为 例如,用@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方法参数上的注释,以从中访问属性 模型,或者如果不存在,则将其实例化。model 属性也与 名称与字段名称匹配的 HTTP Servlet 请求参数中的值。这是被引用的 作为数据绑定,它使您不必处理解析和转换单个 查询参数和表单字段。以下示例显示了如何执行此作: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 方法的一种替代方法 提供它或依靠框架创建模型属性,就是有一个Converter<String, T>以提供实例。当模型属性 name 与请求值(例如 path 变量或请求)的名称匹配 参数,并且有一个ConverterString设置为模型属性类型。 在以下示例中,模型属性名称为account与 URI 匹配的 路径变量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 {
    // ...
}

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

数据绑定可能会导致错误。默认情况下,一个BindException被提高。但是,要检查 对于控制器方法中的此类错误,可以添加一个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进入控制器并直接访问它,或者,或者,将@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 确定)并且未被任何其他参数解析器的参数解析器都被视为已注释 跟@ModelAttribute.spring-doc.cadn.net.cn

@SessionAttributes

@SessionAttributes用于在 HTTP Servlet 会话中存储模型属性 请求。它是一个类型级注释,用于声明 特定控制器。这通常列出模型属性的名称或类型的名称 模型属性,这些属性应透明地存储在会话中以供后续使用 访问请求。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 会话并保存在 HTTP Servlet 会话中。它仍然在那里 直到另一个控制器方法使用SessionStatusmethod 参数来清除 存储,如以下示例所示: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进入控制器方法。spring-doc.cadn.net.cn

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

@RequestAttribute

@SessionAttribute,您可以使用@RequestAttribute注释到 访问之前创建的预先存在的请求属性(例如,由 ServletFilterHandlerInterceptor):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注解。
重定向属性(Redirect Attributes)

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

将原始类型属性附加为查询参数可能是所需的结果,如果 模型实例是专门为重定向准备的。但是,在注释的 控制器时,模型可以包含为渲染目的添加的其他属性(例如, 下拉字段值)。为了避免此类属性出现在 URL、一个@RequestMapping方法可以声明类型为RedirectAttributes和 使用它来指定要提供给的确切属性RedirectView.如果方法 做重定向,内容RedirectAttributes被使用。否则,内容 模型。spring-doc.cadn.net.cn

RequestMappingHandlerAdapter提供了一个名为ignoreDefaultModelOnRedirect,您可以使用它来指示默认Model如果控制器方法重定向,则永远不应使用。相反,控制器 方法应声明类型为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 会话中(因此,do 不出现在 URL 中)。有关详细信息,请参阅 Flash 属性spring-doc.cadn.net.cn

闪光属性

Flash 属性为一个请求提供了一种存储用于 另一个。重定向时最常需要这样做,例如 Post-Redirect-Get 模式。Flash 属性在 重定向(通常在会话中)在 重定向并立即删除。spring-doc.cadn.net.cn

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

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

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

将请求与闪存属性匹配

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

为了减少此类问题的可能性,RedirectView自动“盖章”FlashMap实例,其中包含目标重定向 URL 的路径和查询参数。在 turn,默认值FlashMapManager在以下情况下将该信息与传入请求进行匹配 它查找“输入”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>,如果没有在注释中指定参数名称, 然后,映射将填充每个给定参数名称的多部分文件。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注释以在使用 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 {
    // ...
}

您可以使用@RequestPart结合使用javax.validation.Valid或使用 Spring 的@Validated注释,这两者都会导致应用标准 Bean 验证。 默认情况下,验证错误会导致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 配置的“消息转换器”选项来 配置或自定义消息转换。spring-doc.cadn.net.cn

您可以使用@RequestBody结合使用javax.validation.Valid或 Spring 的@Validated注释,这两者都会导致应用标准 Bean 验证。 默认情况下,验证错误会导致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) {
    // ...
}
Http实体

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将返回序列化的方法上的注释 通过 HttpMessageConverter 发送到响应正文。 以下列表显示了一个示例: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 配置的“消息转换器”选项来 配置或自定义消息转换。spring-doc.cadn.net.cn

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

响应实体

ResponseEntity就像@ResponseBody但带有状态和标题。 例如: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异步,和/或单值和多值响应式类型。这允许以下类型的异步响应: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 的@JsonViewComments 激活序列化视图类,如以下示例所示: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.有关更多详细信息,请参阅控制器建议部分。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方法 在这种情况下,返回值@RequestMapping方法被解释为模型 属性。这通常不是必需的,因为它是 HTML 控制器中的默认行为, 除非返回值是String否则将被解释为视图名称。@ModelAttribute还可以自定义模型属性名称,如以下示例所示: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,而这些人反过来又可以:spring-doc.cadn.net.cn

  • 将请求参数(即表单或查询数据)绑定到模型对象。spring-doc.cadn.net.cn

  • 转换基于字符串的请求值(例如请求参数、路径变量、 标头、cookie 等)添加到控制器方法参数的目标类型。spring-doc.cadn.net.cn

  • 将模型对象值格式化为String呈现 HTML 表单时的值。spring-doc.cadn.net.cn

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

@InitBinder方法支持许多相同的参数@RequestMapping方法 do,但@ModelAttribute(命令对象)参数。通常,它们被声明为 使用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,您可以重复使用相同的方法并注册 控制器专用Formatter实现,如以下示例所示: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 请求的绑定 参数(即表单数据或查询参数)添加到模型对象中的属性,以及 其嵌套对象。spring-doc.cadn.net.cn

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

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

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

例如,给定 HTTP 表单数据终结点,恶意客户端可以提供 存在于模型对象图中但不属于 HTML 表单的属性 在浏览器中显示。这可能导致在模型对象和任何 其嵌套对象的,预计不会更新。spring-doc.cadn.net.cn

建议的方法是使用仅公开的专用模型对象 与表单提交相关的属性。例如,在用于更改的表单上 用户的电子邮件地址,模型对象应声明一组最小的属性,例如 如下所示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;
    }

}

如果您不能或不想为每个数据使用专用模型对象 绑定用例,您必须限制数据绑定允许的属性。 理想情况下,您可以通过setAllowedFields()方法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()方法DataBinder及其子类。 但请注意,“允许列表”比“拒绝列表”更安全。因此setAllowedFields()应该被青睐setDisallowedFields().spring-doc.cadn.net.cn

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

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

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

1.3.6. 例外

@Controller并且@ControllerAdvice类可以有@ExceptionHandler处理来自控制器方法的异常的方法,如以下示例所示: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

对于匹配的异常类型,最好将目标异常声明为方法参数, 如前面的示例所示。当多个异常方法匹配时,根异常匹配为 通常优先于原因异常匹配。更具体地说,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变体,该方法通常使用 实际的FileSystemExceptionRemoteException实例作为参数, 由于它们都从IOException.但是,如果有任何此类匹配 异常在包装器异常中传播,该异常本身是一个IOException, 传入的异常实例就是该包装异常。spring-doc.cadn.net.cn

handle(Exception)变体。这是 在包装场景中始终与包装器异常一起调用,使用 实际匹配异常要通过ex.getCause()在这种情况下。 传入的异常是实际的FileSystemExceptionRemoteException仅当这些作为顶级异常抛出时,实例。spring-doc.cadn.net.cn

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

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

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

@ExceptionHandlerSpring MVC 中的方法构建在DispatcherServlet级别,HandlerExceptionResolver 机制。spring-doc.cadn.net.cn

方法参数

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

方法参数 描述

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

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

HandlerMethodspring-doc.cadn.net.cn

用于访问引发异常的控制器方法。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

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

HttpMethodspring-doc.cadn.net.cn

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

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

当前请求区域设置,由最具体的LocaleResolver可用 — 在 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

为了访问任何会话属性,与存储在 会话作为类级别的结果@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用于与隐式模型一起渲染的实例 — 确定 通过命令对象和@ModelAttribute方法。处理程序方法还可以 通过声明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

要使用的视图和模型属性,以及响应状态(可选)。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 确定), 默认情况下,它被视为要添加到模型中的模型属性。如果是简单类型, 它仍然没有解决。spring-doc.cadn.net.cn

REST API 异常

REST 服务的一个常见要求是在 响应。Spring Framework 不会自动执行此作,因为表示 响应正文中的错误详细信息特定于应用程序。但是,一个@RestController可以使用@ExceptionHandler方法ResponseEntity返回 值来设置响应的状态和正文。也可以声明此类方法 在@ControllerAdvice类以在全球范围内应用它们。spring-doc.cadn.net.cn

实现全局异常处理的应用程序,响应中包含错误详细信息 身体应考虑延长ResponseEntityExceptionHandler, 它为 Spring MVC 引发的异常提供处理,并为其提供钩子 自定义响应正文。要利用这一点,请创建一个ResponseEntityExceptionHandler,用@ControllerAdvice,覆盖 必要的方法,并将其声明为 Spring bean。spring-doc.cadn.net.cn

1.3.7. 控制器建议

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

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

启动时,RequestMappingHandlerMappingExceptionHandlerExceptionResolver检测 控制器通知 bean 并在运行时应用它们。全球@ExceptionHandler方法 从@ControllerAdvice在本地之后应用,从@Controller. 相比之下,全球@ModelAttribute@InitBinder方法在本地方法之前应用。spring-doc.cadn.net.cn

@ControllerAdvice注释具有允许您缩小控制器集的属性 以及他们所申请的处理程序。例如: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,这是一个轻量级函数式编程模型,其中函数 用于路由和处理请求,合约设计为不可变性。 它是基于注释的编程模型的替代方案,但在其他方面运行在 相同的 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>). 当路由器函数匹配时,返回一个处理程序函数;否则为空的可选。RouterFunction相当于@RequestMapping注释,但使用 major 路由器功能不仅提供数据,还提供行为。spring-doc.cadn.net.cn

RouterFunctions.route()提供了一个路由器构建器,有助于创建路由器, 如以下示例所示: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,例如通过将其公开在@Configuration类,它将由 servlet 自动检测,如运行服务器中所述。spring-doc.cadn.net.cn

1.4.2. 处理程序函数

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方法来创建它。您可以使用构建器设置响应状态、添加响应标头或提供正文。以下示例使用 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,Publisher,或ReactiveAdapterRegistry.例如: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)

如果不仅是正文,而且状态或标头都基于异步类型, 您可以使用静态async方法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 可以通过 静态的sse方法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的。
2 createPerson是一个处理程序函数,用于存储新的Person包含在请求正文中。
3 getPerson是一个处理程序函数,返回单个人,由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的。
2 createPerson是一个处理程序函数,用于存储新的Person包含在请求正文中。
3 getPerson是一个处理程序函数,返回单个人,由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

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

一般建议使用route()builder,因为它提供了 典型映射场景的便捷捷径,无需难以发现 静态导入。 例如,路由器函数构建器提供了GET(String, HandlerFunction)为 GET 请求创建映射;和POST(String, HandlerFunction)对于 POST。spring-doc.cadn.net.cn

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

谓词

你可以自己写RequestPredicate,但RequestPredicates实用程序类 提供常用的实现,基于请求路径、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 时,这一点也很重要,同样 稍后会描述。 请注意,此行为与基于注释的编程模型不同,其中 自动选择“最具体”的控制器方法。spring-doc.cadn.net.cn

使用路由器函数构建器时,所有定义的路由都组合成一个RouterFunctionbuild(). 还有其他方法可以将多个路由器功能组合在一起: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标头将被路由到PersonHandler.getPerson
2 GET /person使用Accept标头将被路由到PersonHandler.listPeople
3 POST /person没有其他谓词映射到PersonHandler.createPerson
4 otherRoute是在其他地方创建并添加到构建的路由中的路由器函数。
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标头将被路由到PersonHandler.getPerson
2 GET /person使用Accept标头将被路由到PersonHandler.listPeople
3 POST /person没有其他谓词映射到PersonHandler.createPerson
4 otherRoute是在其他地方创建并添加到构建的路由中的路由器函数。
嵌套路由

一组路由器函数通常具有共享谓词,例如共享谓词 路径。 在上面的示例中,共享谓词将是匹配的路径谓词/person, 由其中三个路由使用。使用注释时,您可以使用类型级删除此重复@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是采用路由器构建器的消费者。
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 设置,通过 MVC Config 使用Spring配置声明 处理请求所需的组件。MVC Java 配置声明以下内容 支持功能端点的基础结构组件:spring-doc.cadn.net.cn

  • RouterFunctionMapping:检测一个或多个RouterFunction<?>Spring的豆子 配置,对它们进行排序,通过RouterFunction.andOther,并将请求路由到生成的组合RouterFunction.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路由上的方法 函数生成器。 使用注释,您可以使用@ControllerAdvice一个ServletFilter,或两者兼而有之。 该过滤器将应用于构建者构建的所有路径。 这意味着嵌套路由中定义的过滤器不适用于“顶级”路由。 例如,考虑以下示例: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添加自定义请求标头的过滤器仅应用于两个 GET 路由。
2 after记录响应的过滤器将应用于所有路由,包括嵌套路由。
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添加自定义请求标头的过滤器仅应用于两个 GET 路由。
2 after记录响应的过滤器将应用于所有路由,包括嵌套路由。

filter方法采用HandlerFilterFunction:一个 采用ServerRequestHandlerFunction并返回一个ServerResponse. 处理程序函数参数表示链中的下一个元素。 这通常是路由到的处理程序,但也可以是另一个 如果应用了多个,则进行筛选。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方法,可以在路由器函数构建器上应用 通过以下方式过滤到现有路由器功能RouterFunction.filter(HandlerFilterFunction).spring-doc.cadn.net.cn

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

1.5. URI 链接

本节介绍 Spring Framework 中可用于处理 URI 的各种选项。spring-doc.cadn.net.cn

1.5.1. Uri组件

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. UriBuilder

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使用UriComponentsBuilder内部和 公开共享配置选项。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但是,它不是静态工厂方法,而是一个实际实例 ,其中包含配置和首选项,如以下示例所示: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

考虑“;”,它在路径中是合法的,但具有保留的含义。第一个选项将 “;”在 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通过内部扩展和编码 URI 模板 这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()

DefaultUriBuilderFactory实现用途UriComponentsBuilder内部设置为 展开和编码 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-*标头,用于指定客户端发起的地址。考虑使用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.例如,您可以使用一种技术 类似于通过代理进行模拟测试,以避免按名称引用控制器方法,如以下示例所示 (该示例假定静态导入MvcUriComponentsBuilder.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(带有String返回值)代替。

前面的示例在MvcUriComponentsBuilder.在内部,他们依赖 上ServletUriComponentsBuilder从方案、主机、端口、 上下文路径和当前请求的 servlet 路径。这在大多数情况下效果很好。 然而,有时,它可能还不够。例如,您可能不在 请求(例如准备链接的批处理)或可能需要插入路径 prefix(例如从请求路径中删除的区域设置前缀,需要 重新插入链接)。spring-doc.cadn.net.cn

对于这种情况,您可以使用静态fromXxx重载方法,接受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-*标头,用于指定客户端发起的地址。请考虑使用 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,其默认实现使用 类的大写字母和方法名称(例如,getThing方法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 容器中启用了异步请求处理功能,控制器方法就可以包装任何受支持的控制器方法 返回值与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 的转发,不同之处在于它允许 应用程序恢复 Servlet 容器线程上的请求处理。spring-doc.cadn.net.cn

  • ServletRequest提供对当前DispatcherType,您可以 用于区分处理初始请求、异步请求 dispatch、forward 和其他调度程序类型。spring-doc.cadn.net.cn

DeferredResult处理工作如下:spring-doc.cadn.net.cn

  • 控制器返回一个DeferredResult并将其保存在内存中 可以访问它的队列或列表。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异步启动的初始请求的回调 processing(而不是postHandleafterCompletion).spring-doc.cadn.net.cn

HandlerInterceptor实现还可以注册一个CallableProcessingInterceptorDeferredResultProcessingInterceptor,以更深入地集成 异步请求的生命周期(例如,处理超时事件)。看AsyncHandlerInterceptor了解更多详情。spring-doc.cadn.net.cn

DeferredResult提供onTimeout(Runnable)onCompletion(Runnable)回调。 请参阅javadoc 的DeferredResult了解更多详情。Callable可以替代WebAsyncTask这会暴露额外的 超时和完成回调的方法。spring-doc.cadn.net.cn

与 WebFlux 相比

Servlet API 最初是为通过 Filter-Servlet 进行单次传递而构建的 链。Servlet 3.0 中添加的异步请求处理允许应用程序退出 Filter-Servlet 链,但将响应保持打开状态以供进一步处理。弹簧 MVC 异步支持是围绕该机制构建的。当控制器返回DeferredResult, Filter-Servlet 链被退出,Servlet 容器线程被释放。后来,当 这DeferredResult设置时,一个ASYNCdispatch(到同一 URL),在此期间 controller 再次映射,但不是调用它,DeferredResult值被使用 (就像控制器返回它一样)以恢复处理。spring-doc.cadn.net.cn

相比之下,Spring WebFlux 既不是基于 Servlet API 构建的,也不需要这样的 异步请求处理功能,因为它在设计上是异步的。异步 处理内置于所有框架合约中,并由所有 请求处理的阶段。spring-doc.cadn.net.cn

从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持 异步和响应式类型作为控制器方法中的返回值。 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

对象

您可以使用ResponseBodyEmitter返回值以生成对象流,其中 每个对象都使用HttpMessageConverter并写入 response,如以下示例所示: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) 支持服务器发送的事件,其中从服务器发送的事件 根据 W3C SSE 规范进行格式化。生成 SSE 流式传输,返回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 不支持服务器发送的事件。考虑将 Spring 的 WebSocket 消息传递SockJS 回退传输(包括 SSE)一起使用,以 广泛的浏览器。spring-doc.cadn.net.cn

另请参阅上一节,了解有关异常处理的注释。spring-doc.cadn.net.cn

原始数据

有时,绕过消息转换并直接流式传输到响应很有用OutputStream(例如,用于文件下载)。您可以使用StreamingResponseBody返回值类型,如以下示例所示: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 支持在控制器中使用响应式客户端库(另请阅读 WebFlux 部分中的响应式库)。 这包括WebClientspring-webflux和其他,例如 Spring Data 响应式数据存储库。在这样的场景下,能够返回很方便 控制器方法中的响应式类型。spring-doc.cadn.net.cn

响应式返回值的处理方式如下:spring-doc.cadn.net.cn

  • 单值 Promise 适配于,类似于使用DeferredResult.例子 包括Mono(反应堆)或Single(RxJava)。spring-doc.cadn.net.cn

  • 具有流媒体类型(例如application/x-ndjsontext/event-stream)适应,类似于使用ResponseBodyEmitterSseEmitter.示例包括Flux(反应堆)或Observable(RxJava)。 申请也可以返回Flux<ServerSentEvent>Observable<ServerSentEvent>.spring-doc.cadn.net.cn

  • 具有任何其他媒体类型(例如application/json)被改编 to,类似于使用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 消息传递解决方案(例如 STOMP over WebSocketWebSocket with SockJS) 具有内置心跳机制。spring-doc.cadn.net.cn

1.6.7. 配置

必须在 Servlet 容器级别启用异步请求处理功能。 MVC 配置还公开了异步请求的多个选项。spring-doc.cadn.net.cn

Servlet 容器

Filter 和 Servlet 声明具有asyncSupported标志,需要设置为true以启用异步请求处理。此外,过滤器映射应为 声明以处理ASYNC javax.servlet.DispatchType.spring-doc.cadn.net.cn

在 Java 配置中,当您使用AbstractAnnotationConfigDispatcherServletInitializer初始化 Servlet 容器,这是自动完成的。spring-doc.cadn.net.cn

web.xml配置,您可以添加<async-supported>true</async-supported>DispatcherServlet以及Filter声明并添加<dispatcher>ASYNC</dispatcher>以过滤映射。spring-doc.cadn.net.cn

弹簧 MVC

MVC 配置公开了以下与异步请求处理相关的选项:spring-doc.cadn.net.cn

您可以配置以下内容:spring-doc.cadn.net.cn

  • 异步请求的默认超时值,如果未设置,则取决于 在底层 Servlet 容器上。spring-doc.cadn.net.cn

  • AsyncTaskExecutor用于在使用响应式类型流式传输时阻止写入,并用于执行Callable从 controller 方法。如果您 具有响应式类型的流或具有返回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

弹簧 MVCHandlerMapping实现为 CORS 提供内置支持。成功后将请求映射到处理程序,HandlerMapping实现检查 CORS 配置中的给定的请求和处理程序并采取进一步的作。预检请求被直接处理直接,而简单和实际的 CORS 请求被拦截、验证并具有设置所需的 CORS 响应标头。spring-doc.cadn.net.cn

为了启用跨域请求(即Originheader 存在,并且 与请求的主机不同),您需要有一些显式声明的 CORS 配置。如果未找到匹配的 CORS 配置,则预检请求为 拒绝。不会将 CORS 标头添加到简单和实际 CORS 请求的响应中 因此,浏览器拒绝它们。spring-doc.cadn.net.cn

HandlerMapping可以使用基于 URL 模式的单独配置CorsConfiguration映射。在大多数情况下,应用程序 使用 MVC Java 配置或 XML 命名空间声明此类映射,结果 在传递给所有HandlerMapping实例。spring-doc.cadn.net.cn

您可以在HandlerMapping与更多水平 细粒度的处理程序级 CORS 配置。例如,带注释的控制器可以使用 类级或方法级@CrossOrigin注释(其他处理程序可以实现CorsConfigurationSource).spring-doc.cadn.net.cn

组合全局和本地配置的规则通常是累加的,例如, 所有全局和所有本地原点。对于那些只能使用单个值的属性 已接受,例如allowCredentialsmaxAge,则局部将覆盖全局值。看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响应标头是通过复制 CORS 预检请求中指定的相关标头和方法来处理的。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

@CrossOrigin注释启用对带注释的控制器方法的跨域请求, 如以下示例所示: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必须是设置为一个或多个特定域(但不是特殊值)或 这"*"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. 全局配置

除了细粒度的控制器方法级配置之外,您可能还希望 还定义一些全局 CORS 配置。您可以设置基于 URLCorsConfiguration在任何HandlerMapping.但是,大多数应用程序都使用 MVC Java 配置或 MVC XML 命名空间来执行此作。spring-doc.cadn.net.cn

默认情况下,全局配置启用以下功能:spring-doc.cadn.net.cn

allowCredentials默认情况下不会启用,因为这会建立信任级别,这会暴露敏感的用户特定信息(例如 cookie 和 CSRF Tokens),并且只能在适当的情况下使用。当它启用时allowOrigins必须是设置为一个或多个特定域(但不是特殊值)或 这"*"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 安全性内置了对 科斯。

要配置过滤器,请将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. 网络安全

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-Controlresponse 标头,然后是条件请求 标头(例如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-Control标头,并被接受为参数 在许多地方:spring-doc.cadn.net.cn

虽然 RFC 7234 描述了所有可能的 指令Cache-Controlresponse 标头,CacheControl类型采用 面向用例的方法,重点关注常见场景: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也接受更简单的cachePeriod属性(以秒为单位定义),该属性 工作原理如下: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值,或两者。对于有条件的GETHEAD请求,您可以将响应设置为 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 集成的视图技术。 我们假设您已经熟悉视图分辨率spring-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

查看配置

以下示例展示了如何将 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 的“设置”和“共享变量”直接传递给 FreeMarkerConfiguration对象(由 Spring 管理)通过设置适当的 bean 属性FreeMarkerConfigurer豆。这freemarkerSettings属性要求 一个java.util.Properties对象,而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/>元素。此元素主要允许表单显示来自 form-backing 对象,并显示来自Validator在 Web 或业务层。Spring 也支持 FreeMarker 中的相同功能, 具有用于生成表单输入元素本身的额外便利宏。spring-doc.cadn.net.cn

绑定宏

一组标准宏在spring-webmvc.jar文件 FreeMarker,因此它们始终可用于适当配置的应用程序。spring-doc.cadn.net.cn

Spring 模板库中定义的一些宏被认为是内部的 (private),但宏定义中不存在此类作用域,使所有宏都可见 调用代码和用户模板。以下部分仅重点介绍宏 您需要直接从模板中调用。如果您想查看宏代码 直接调用该文件spring.ftl并且位于org.springframework.web.servlet.view.freemarker包。spring-doc.cadn.net.cn

简单绑定

在基于 FreeMarker 模板的 HTML 表单中,这些模板充当 Spring MVC 的表单视图 控制器,可以使用类似于下一个示例的代码绑定到字段值,并且 以与 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.address.street.这bind宏假定 默认 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.消息代码/>spring-doc.cadn.net.cn

messageText(根据 code 参数从资源包输出字符串,回退到默认参数的值)spring-doc.cadn.net.cn

<@spring.message文本代码,text/>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输入路径、属性、字段类型/>spring-doc.cadn.net.cn

formHiddenInput(用于提交非用户输入的隐藏输入字段)spring-doc.cadn.net.cn

<@spring.formHiddenInput 路径、属性/>spring-doc.cadn.net.cn

formPasswordInput(用于收集密码的标准输入字段。请注意,没有 value 会填充到这种类型的字段中。spring-doc.cadn.net.cn

<@spring.formPasswordInput 路径、属性/>spring-doc.cadn.net.cn

formTextarea(用于收集长、自由格式文本输入的大文本字段)spring-doc.cadn.net.cn

<@spring.formTextarea 路径、属性/>spring-doc.cadn.net.cn

formSingleSelect(允许单个必需值的选项下拉框 已选择)spring-doc.cadn.net.cn

<@spring.formSingleSelect 路径、选项、属性/>spring-doc.cadn.net.cn

formMultiSelect(允许用户选择 0 个或多个值的选项列表框)spring-doc.cadn.net.cn

<@spring.formMultiSelect 路径、选项、属性/>spring-doc.cadn.net.cn

formRadioButtons(一组单选按钮,用于进行单个选择 从可用选项中)spring-doc.cadn.net.cn

<@spring.formRadioButtons 路径、选项分隔符、属性/>spring-doc.cadn.net.cn

formCheckboxes(一组允许选择 0 个或多个值的复选框)spring-doc.cadn.net.cn

<@spring.formCheckboxes 路径、选项、分隔符、属性/>spring-doc.cadn.net.cn

formCheckbox(单个复选框)spring-doc.cadn.net.cn

<@spring.formCheckbox 路径、属性/>spring-doc.cadn.net.cn

showErrors(简化绑定字段的验证错误的显示)spring-doc.cadn.net.cn

<@spring.showErrors 分隔符,classOrStyle/>spring-doc.cadn.net.cn

在 FreeMarker 模板中,formHiddenInputformPasswordInput实际上不是必需的,因为您可以使用普通的formInput宏, 指定hiddenpassword作为fieldType参数。

上述任何宏的参数都具有一致的含义:spring-doc.cadn.net.cn

  • path:要绑定到的字段的名称(即“command.name”)spring-doc.cadn.net.cn

  • options:一个Map输入中可以选择的所有可用值 田。 地图的键表示从表单 POST 回来的值并绑定到命令对象。针对键存储的地图对象是标签显示在表单上给用户,可能与相应的值不同由表单发回。通常,此类地图由 控制器。 您可以使用任何Map实现,具体取决于所需的行为。 对于严格排序的地图,您可以使用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 纯银”'。spring-doc.cadn.net.cn

  • classOrStyle:对于showErrors宏,CSS 类的名称,该span包装每个错误使用的元素。如果未提供任何信息(或值为空),则错误将包装在<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

showErrors宏采用分隔符参数(用于分隔给定字段上的多个错误)并且还接受第二个参数——这个时间、类名或样式属性。请注意,FreeMarker 可以指定默认值属性参数的值。以下示例显示如何使用formInputshowErrors宏:spring-doc.cadn.net.cn

<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>

下一个示例显示了表单片段的输出,生成 name 字段并显示validation 提交表单后出现错误,字段中没有值。 验证 通过 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宏并接受相同的 参数列表。通常,第二个参数 (attributes) 用于传递样式 information 或rowscols属性textarea.spring-doc.cadn.net.cn

选择字段

您可以使用四个选择字段宏在 您的 HTML 表单:spring-doc.cadn.net.cn

四个宏中的每一个都接受一个Map包含表单值的选项 字段和与该值对应的标签。值和标签可以是 相同。spring-doc.cadn.net.cn

下一个示例是 FTL 中的单选按钮。表单支持对象指定默认值 值为“London”,因此无需验证。当表单是 渲染时,要选择的整个城市列表将作为参考数据提供 model,名称为“cityMap”。以下列表显示了示例:spring-doc.cadn.net.cn

...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>

前面的列表呈现了一行单选按钮,每个值对应cityMap,并使用 的分隔符。未提供其他属性(宏的最后一个参数为 失踪)。这""cityMap使用相同的String映射中的每个键值对。地图的 键是表单实际提交的内容POST请求参数。映射值是 用户看到的标签。在前面的示例中,给定三个知名城市的列表 和表单支持对象中的默认值,则 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)
}

该代码现在生成输出,其中无线电值是相关代码,但 用户仍然会看到更用户友好的城市名称,如下所示: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,并且使用web.xml文件,作为 由 Spring 的 bind 支持使用。使元素符合 XHTML 或覆盖 默认的 HTML 转义值,您可以在模板中指定两个变量(或在 您的模型,它们对您的模板可见)。指定 模板中的 them 是可以稍后在 模板处理,为表单中的不同字段提供不同的行为。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 框架有一个内置的用于将 Spring MVC 与 Groovy 标记一起使用的集成。spring-doc.cadn.net.cn

Groovy 标记模板引擎需要 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

EJS公司spring-doc.cadn.net.cn

纳斯霍恩spring-doc.cadn.net.cn

ERB再培训局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接口。
要求

您需要在类路径上安装脚本引擎,其详细信息因脚本引擎而异:spring-doc.cadn.net.cn

您需要有脚本模板库。对于 JavaScript 做到这一点的一种方法是 通过 WebJarsspring-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

如果您的模板技术需要一些自定义,您可以提供一个脚本 实现自定义渲染函数。例如,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
    }
}
设置sharedEngine属性设置为false使用非线程安全时是必需的 脚本引擎,其模板库不是为并发而设计的,例如 Handlebars 或 React 在 Nashorn 上运行。在这种情况下,由于此错误,需要 Java SE 8 更新 60,但通常是 在任何情况下都建议使用最新的 Java SE 补丁版本。

polyfill.js仅定义windowHandlebars 需要的对象才能正常运行,如下所示:spring-doc.cadn.net.cn

var window = {};

这个基本的render.js实现在使用模板之前编译模板。生产就绪 实现还应存储任何重复使用的缓存模板或预编译模板。 您可以在脚本端执行此作(并处理您需要的任何自定义 — 管理 模板引擎配置)。以下示例显示了如何执行此作: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

查看解析器

使用 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 页面的开发与这些 数据绑定功能,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

我们浏览表单标签,并查看每个标签的使用方式示例。我们有 包括生成的 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以便命令对象可以 可通过内部标签访问。此库中的所有其他标签都是form标记。spring-doc.cadn.net.cn

假设我们有一个名为User.它是一个具有属性的 JavaBean 如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由页面控制器。继续阅读以查看更复杂的示例 内部标签如何与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.如果已将表单支持对象以另一个名称放入模型中 (绝对是最佳实践),您可以将表单绑定到命名变量,作为 以下示例显示: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'默认情况下。 有关此标记的示例,请参阅表单标记。您还可以使用 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如果绑定值为true.这value属性对应于setValue(Object)value 属性。spring-doc.cadn.net.cn

  • 方法二:当绑定值类型为arrayjava.util.Collectioninput(checkbox)被标记为checked如果配置的setValue(Object)值为 存在于边界中Collection.spring-doc.cadn.net.cn

  • 方法三:对于任何其他绑定值类型,使用input(checkbox)被标记为checked如果配置的setValue(Object)等于绑定值。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

本节基于上一个示例构建checkbox标签部分。有时,您更喜欢 不必在您的 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可作为模型属性使用 包含要从中选择的值的字符串。如果您使用Map, 映射条目键用作值,映射条目的值用作 要显示的标签。您还可以使用自定义对象,您可以在其中提供 属性名称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标记,您可能想要 将可用选项作为运行时变量传入。对于这种用法,您可以使用radiobuttons标记。你传入一个Array一个ListMap其中包含 中的可用选项items财产。如果您使用Map,则地图入口键为 用作值,地图条目的值用作要显示的标签。 您还可以使用自定义对象,您可以在其中提供值的属性名称 通过使用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 值,您可以设置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’s技能在草药学中,“技能”行的 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标记

此标签呈现 HTML 列表option元素。它设置了selected属性 基于绑定值。以下 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居住在英国,则“国家/地区”行的 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属性通常填充了项目对象的集合或数组。itemValueitemLabel引用这些项目对象的 bean 属性,如果 指定。否则,项对象本身将转换为字符串。或者 您可以指定Map项目,在这种情况下,映射键被解释为选项 值和映射值对应于选项标签。如果itemValueitemLabel(或两者兼而有之) 恰好也指定了,item value 属性适用于 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"/>

如果我们选择提交housevalue 作为隐藏的,则 HTML 如下:spring-doc.cadn.net.cn

<input name="house" type="hidden" value="Gryffindor"/>
errors标记

此标记在 HTML 中呈现字段错误span元素。它提供对错误的访问 在您的控制器中创建的,或由与 你的控制器。spring-doc.cadn.net.cn

假设我们想要显示firstNamelastName字段。我们有一个用于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。对于每种方法,HTTP 规范定义了确切的语义。为 例如,GET 应该始终是一个安全的作,这意味着它没有副作用, PUT 或 DELETE 应该是幂等的,这意味着您可以重复这些作 一遍又一遍,但最终结果应该是一样的。虽然 HTTP 定义了这些 四种方法,HTML 只支持两种:GET 和 POST。幸运的是,有两种可能 解决方法:您可以使用 JavaScript 进行 PUT 或 DELETE,也可以执行 POST 使用“实数”方法作为附加参数(建模为 HTML 表单)。Spring的HiddenHttpMethodFilter使用后一种技巧。这 filter 是一个普通的 Servlet 过滤器,因此,它可以与任何 Web 框架(不仅仅是 Spring MVC)。将此过滤器添加到您的web.xml,然后 POST 带有隐藏的method参数转换为相应的 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. 图块

您可以像任何其他视图技术一样将 Tiles 集成到 Web 中 使用 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

配置

为了能够使用磁贴,您必须使用包含定义的文件来配置它 (有关定义和其他磁贴概念的基本信息,请参阅 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>

前面的示例定义了包含定义的五个文件。这些文件都是 位于WEB-INF/defs目录。在初始化WebApplicationContext, 加载文件,并初始化定义工厂。之后有 已完成,定义文件中包含的图块可以用作 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 还支持两个特殊的 TilesPreparerFactory实现。有关如何使用的详细信息,请参阅 Tiles 文档ViewPreparerTiles 定义文件中的引用。spring-doc.cadn.net.cn

您可以指定SimpleSpringPreparerFactory自动布线ViewPreparer实例基于 指定的准备者类,应用 Spring 的容器回调以及 配置的 Spring BeanPostProcessors。如果 Spring 的上下文范围注释配置具有 已激活,注释ViewPreparer类被自动检测,并且 应用的。请注意,这需要 Tiles 定义文件中的准备者类,如 默认的PreparerFactory确实。spring-doc.cadn.net.cn

您可以指定SpringBeanPreparerFactory对指定的准备者名称进行作(而不是 的类),从 DispatcherServlet 的 应用程序上下文。完整的 bean 创建过程由 Spring 控制 应用程序上下文,允许使用显式依赖注入 配置、作用域 bean 等。请注意,您需要定义一个 Spring bean 定义 每个准备者名称(如图块定义中使用的那样)。以下示例显示 如何定义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()方法(默认实现为 空)。以下示例显示了如何执行此作: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 请求,以防 您需要访问区域设置。HTTP 响应仅针对 cookie 或其他 HTTP 标头。提要会自动写入响应 对象。spring-doc.cadn.net.cn

有关创建 Atom 视图的示例,请参阅 Alef Arendsen 的 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 库添加到您的类路径中。 对于 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作为基础提供 class 的 Excel 视图。它基于 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呈现响应 内容为 JSON。默认情况下,模型映射的整个内容(除了 特定于框架的类)被编码为 JSON。对于 map 需要过滤,可以指定一组特定的模型属性进行编码 通过使用modelKeys财产。您还可以使用extractValueFromSingleKeyModel属性直接提取和序列化单键模型中的值,而不是直接序列化 而不是模型属性的映射。spring-doc.cadn.net.cn

您可以使用 Jackson 提供的 JSON 映射根据需要自定义 JSON 映射 附注。当您需要进一步控制时,您可以注入自定义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 映射 附注。当您需要进一步控制时,您可以注入自定义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 Mapper 封送 XMLspring-doc.cadn.net.cn

1.10.11. XSLT 视图

XSLT 是一种 XML 的转换语言,作为 Web 中的一种视图技术很受欢迎 应用。如果您的应用程序,XSLT 可以作为视图技术的不错选择 自然地处理 XML 或您的模型是否可以轻松转换为 XML。以下内容 部分展示了如何将 XML 文档生成为模型数据并使用 Spring Web MVC 应用程序中的 XSLT。spring-doc.cadn.net.cn

这个例子是一个简单的 Spring 应用程序,它在Controller并将它们添加到模型映射中。地图随视图一起返回 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

有一些软件包可以自动“支配” 对象图,但是在 Spring 中,您可以完全灵活地创建 DOM 以您选择的任何方式从您的模型中。这可以防止 XML 播放的转换 在模型数据结构中占有太大比例,这在使用工具时是一种危险 来管理 DOMification 过程。spring-doc.cadn.net.cn

转型

最后,XsltViewResolver解析“主页”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 并适应依赖项 在类路径上可用(例如,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 值。这适用于日期表示为带有“输入”形式的字符串的表单 领域。但是,对于“日期”和“时间”表单字段,浏览器使用定义的固定格式 在 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)
    }
}
FormatterRegistrarSPIFormattingConversionServiceFactoryBean有关何时使用的更多信息 FormatterRegistrar 实现。

1.11.4. 验证

默认情况下,如果存在 Bean Validation 在类路径上(例如,Hibernate Validator),则LocalValidatorFactoryBean是 注册为全局验证器,用于@ValidValidatedon 控制器方法参数。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>
映射拦截器不适合作为安全层,因为 对于与带注释的控制器路径匹配的不匹配,也可以匹配尾随 斜杠和路径扩展,以及其他路径匹配选项。多 这些选项已被弃用,但不匹配的可能性仍然存在。 通常,我们建议使用 Spring Security,它包括一个专用的 MvcRequestMatcher 以与 Spring MVC 路径匹配保持一致,并且还具有阻止许多安全防火墙 URL 路径中不需要的字符。

1.11.6. 内容类型

您可以配置 Spring MVC 如何从请求中确定请求的媒体类型 (例如,Acceptheader、URL 路径扩展名、查询参数等)。spring-doc.cadn.net.cn

默认情况下,只有Accept标头。spring-doc.cadn.net.cn

如果必须使用基于 URL 的内容类型解析,请考虑使用 query 参数 策略 over 路径扩展。请参阅后缀匹配和后缀匹配和 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

以下示例添加 XML 和 Jackson JSON 转换器,其中包含自定义的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

如果在类路径上检测到以下已知模块,它还会自动注册它们:spring-doc.cadn.net.cn

使用 Jackson XML 支持启用缩进需要woodstox-core-asl依赖性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那立即 在调用时转发到视图。您可以在没有 Java 控制器的静态情况下使用它 在视图生成响应之前运行的逻辑。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"/>

如果@RequestMapping方法映射到任何 HTTP 方法的 URL,然后映射到视图 controller 不能用于处理相同的 URL。这是因为通过 URL 匹配到 带注释的控制器被认为是端点所有权的足够强的指示,因此 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 和脚本模板也需要 底层视图技术的配置。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 并应用完整的解析器链和 转换器 — 例如,插入版本。MVC 配置提供了一个ResourceUrlProvider豆子,这样就可以注射到别人身上。您还可以使用ResourceUrlEncodingFilter对于 Thymeleaf、JSP、FreeMarker 和其他具有 URL 标签的 恃HttpServletResponse#encodeURL.spring-doc.cadn.net.cn

请注意,当同时使用EncodedResourceResolver(例如,用于提供 gzip 或 Brotli 编码的资源)和VersionResourceResolver,您必须按此顺序注册它们。 这可确保始终根据未编码的文件可靠地计算基于内容的版本。spring-doc.cadn.net.cn

对于 WebJars,版本化 URL (例如/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 允许映射DispatcherServlet到 (从而覆盖映射 容器的默认 Servlet),同时仍然允许静态资源请求 由容器的默认 Servlet 处理。它配置了一个/DefaultServletHttpRequestHandlerURL 映射为 和 最低优先级 相对于其他 URL 映射。/**spring-doc.cadn.net.cn

此处理程序将所有请求转发到默认 Servlet。因此,它必须 按所有其他 URL 的顺序保留在最后HandlerMappings.那就是 如果使用<mvc:annotation-driven>.或者,如果您将 自己定制HandlerMapping实例,请务必将其order属性设置为值 低于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对于 默认 Servlet 必须按名称而不是路径检索。这DefaultServletHttpRequestHandler尝试自动检测 启动时的容器,使用大多数主要 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实现 类路径。spring-doc.cadn.net.cn

1.11.14. 高级 XML 配置

MVC 命名空间没有高级模式。如果您需要自定义属性 无法更改的 bean,则可以使用BeanPostProcessor生命周期 Spring的钩子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主动向客户推送资源,而 支持作为方法参数@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 和 支持同步、异步和流式处理方案。

有关详细信息,请参阅 REST 端点spring-doc.cadn.net.cn

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 框架spring-doc.cadn.net.cn

  • Spring MVC Test:一个框架,也称为MockMvc,用于测试带注释的控制器 通过DispatcherServlet(即支持注释),并配有 Spring MVC 基础设施,但没有 HTTP 服务器。 有关更多详细信息,请参阅 Spring MVC 测试spring-doc.cadn.net.cn

  • 客户端 REST:spring-test提供一个MockRestServiceServer您可以用作 用于测试客户端代码的模拟服务器,该代码在内部使用RestTemplate. 有关更多详细信息,请参阅客户端 REST 测试spring-doc.cadn.net.cn

  • WebTestClient:为测试 WebFlux 应用程序而构建,但也可用于 通过 HTTP 连接对任何服务器进行端到端集成测试。这是一个 非阻塞、响应式客户端,非常适合测试异步和流式处理 场景。spring-doc.cadn.net.cn

4. Web套接字

参考文档的这一部分涵盖了对 Servlet 堆栈、WebSocket 的支持 消息传递,包括原始 WebSocket 交互、通过 SockJS 进行的 WebSocket 仿真,以及 通过 STOMP 作为 WebSocket 上的子协议进行发布-订阅消息传递。spring-doc.cadn.net.cn

4.1. WebSocket 简介

WebSocket 协议 RFC 6455 提供了一个标准化的 在客户端和服务器之间建立全双工双向通信通道的方法 通过单个 TCP 连接。它是与 HTTP 不同的 TCP 协议,但旨在 通过 HTTP 工作,使用端口 80 和 443,并允许重复使用现有防火墙规则。spring-doc.cadn.net.cn

WebSocket 交互从使用 HTTP 的 HTTP 请求开始Upgrade页眉 升级,或者在这种情况下,切换到 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 套接字将保留 打开,供客户端和服务器继续发送和接收消息。spring-doc.cadn.net.cn

WebSocket 工作原理的完整介绍超出了本文档的范围。 请参阅 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-ProtocolHTTP 握手请求上的标头。 如果没有这一点,他们需要制定自己的公约。spring-doc.cadn.net.cn

4.1.2. 何时使用 WebSocket

WebSockets 可以使网页具有动态性和交互性。然而,在许多情况下, Ajax 和 HTTP 流的组合或长轮询可以提供一个简单的 有效的解决方案。spring-doc.cadn.net.cn

例如,新闻、邮件和社交源需要动态更新,但可能是 每隔几分钟就这样做一次完全没问题。协作、游戏和金融应用程序,在 另一方面,需要更接近实时。spring-doc.cadn.net.cn

延迟本身并不是决定因素。如果消息量相对较低(例如 监控网络故障)HTTP 流或轮询可以提供有效的解决方案。 正是低延迟、高频和高音量的结合造就了最好的 使用 WebSocket 的案例。spring-doc.cadn.net.cn

还要记住,在互联网上,您无法控制的限制性代理 可能会阻止 WebSocket 交互,因为它们未配置为传递Upgrade标头,或者因为它们关闭了显示为空闲的长期连接。这 意味着将 WebSocket 用于防火墙内的内部应用程序是 与面向公众的应用程序相比,直接做出决定。spring-doc.cadn.net.cn

4.2. WebSocket API

Spring Framework 提供了一个 WebSocket API,您可以使用它来编写处理 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,它公开了握手“之前”和“之后”的方法。 您可以使用这样的拦截器来排除握手或创建任何属性 可用于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 配置时,默认情况下提供和添加实现 或 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 请求 — 例如 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 的默认行为是接受 仅限同源请求。也可以允许所有或指定的源列表。 此检查主要针对浏览器客户端设计。没有什么能阻止其他类型 的客户端修改Origin标头值(有关更多详细信息,请参阅 RFC 6454:Web 源概念)。spring-doc.cadn.net.cn

三种可能的行为是:spring-doc.cadn.net.cn

  • 仅允许同源请求(默认):在此模式下,当启用 SockJS 时, Iframe HTTP 响应标头X-Frame-Options设置为SAMEORIGIN和 JSONP 传输被禁用,因为它不允许检查请求的来源。 因此,启用此模式时不支持 IE6 和 IE7。spring-doc.cadn.net.cn

  • 允许指定的源列表:每个允许的源必须以http://https://.在此模式下,启用 SockJS 时,将禁用 IFrame 传输。 因此,当此 模式已启用。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 后备

在公共互联网上,超出您控制范围的限制性代理可能会阻止 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(长) 轮询。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(“open”帧)最初,消息以a["message1","message2"](JSON 编码数组)、字母h(“检测信号”帧),如果没有消息流 25 秒(默认情况下),字母c(“关闭”框架)以关闭会话。spring-doc.cadn.net.cn

若要了解详细信息,请在浏览器中运行示例并观察 HTTP 请求。 SockJS 客户端允许修复传输列表,因此可以 一次查看每个运输。SockJS 客户端还提供了一个调试标志 这在浏览器控制台中启用有用的消息。在服务器端,您可以启用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 页面和 浏览器支持的传输类型。客户端还提供了几个 配置选项 - 例如,指定要包含的传输。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来自 SockJS 客户端的请求是 可能影响客户选择交通工具的信息。 其中一个细节是服务器应用程序是否依赖于 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 命名空间不会设置该标头 但可以配置为这样做。将来,它可能会默认设置它。spring-doc.cadn.net.cn

有关如何配置 设置X-Frame-Options页眉。您还可以查看 gh-2718 以获取更多背景信息。spring-doc.cadn.net.cn

如果您的应用程序将X-Frame-Options响应标头(理所当然! 并且依赖于基于 iframe 的传输,您需要将标头值设置为SAMEORIGINALLOW-FROM <origin>. Spring 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您可以使用它来自定义频率。默认情况下,一个heartbeat 在 25 秒后发送,假设没有发送其他消息。 连接。 此 25 秒值符合以下 IETF 针对公共 Internet 应用程序的建议。spring-doc.cadn.net.cn

在 WebSocket 和 SockJS 上使用 STOMP 时,如果 STOMP 客户端和服务器协商要交换的心跳,则禁用 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 需要以下标头和值:spring-doc.cadn.net.cn

有关确切的实现,请参阅addCorsHeadersAbstractSockJsService和 这TransportTypeenum 的 enum 中。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 并且需要 位于类路径上。或者,您可以配置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 disconnectDelay属性设置为 30 属性秒(默认值为 5 秒 —5 * 1000).

4.4. 跺脚

WebSocket 协议定义了两种类型的消息(文本和二进制),但它们的内容未定义。该协议定义了一种机制,供客户端和服务器协商子协议(即更高级别的消息传递协议),以便在 WebSocket 之上使用定义每个可以发送的消息类型、格式、每个消息的内容等等。子协议的使用是可选的,但无论哪种方式,客户端和服务器都需要就定义消息内容的某种协议达成一致。spring-doc.cadn.net.cn

4.4.1. 概述

STOMP(简单面向文本的消息传递协议)最初是为脚本语言(例如 Ruby、Python 和 Perl)创建的,用于连接到企业消息代理。 是的 旨在解决常用消息传递模式的最小子集。STOMP 可以用于任何可靠的双向流网络协议,例如 TCP 和 WebSocket。尽管 STOMP 是一种面向文本的协议,但消息有效负载可以是文本或二进制。spring-doc.cadn.net.cn

STOMP 是一种基于帧的协议,其帧基于 HTTP 建模。以下列表显示了结构STOMP 帧的结构:spring-doc.cadn.net.cn

COMMAND
header1:value1
header2:value2

Body^@

客户端可以使用SENDSUBSCRIBE要发送或订阅的命令 messages,以及destination描述 消息是关于谁应该接收它的。这使简单的 发布-订阅机制,可用于通过代理发送消息 发送到其他连接的客户端或向服务器发送消息以请求 执行一些工作。spring-doc.cadn.net.cn

当您使用 Spring 的 STOMP 支持时,Spring WebSocket 应用程序会起作用 作为客户的 STOMP 经纪人。消息被路由到@Controller消息处理 方法或跟踪订阅和 向订阅用户广播消息。您还可以将 Spring 配置为工作 使用专用的 STOMP 代理(例如 RabbitMQ、ActiveMQ 等)进行实际作 消息广播。在这种情况下,Spring 会维护 与代理的 TCP 连接,向代理中继消息,并传递消息 从它到连接的 WebSocket 客户端。因此,Spring Web 应用程序可以 依赖基于 HTTP 的统一安全性、通用验证和熟悉的编程 用于消息处理的模型。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 提供比使用原始 WebSocket 更丰富的编程模型。同样的一点可以是 关于 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 端点,通过 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被路由到@MessageMapping方法@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>
对于内置的简单代理,将/topic/queue前缀没有任何特殊的 意义。它们只是区分发布订阅和点对点的约定 消息传递(即,许多订阅者与一个消费者)。当您使用外部代理时, 查看代理的 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头。即使确实如此,它们也会被忽略(或者更确切地说, overrided)在服务器端。有关身份验证的更多信息,请参阅连接到代理身份验证spring-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 应用程序就会成为 连接的客户端。本节介绍服务器端的消息流。spring-doc.cadn.net.cn

spring-messaging模块包含对消息传递应用程序的基础支持 起源于 Spring Integration,是 后来被提取并合并到 Spring 框架中,以便在许多 Spring 项目和应用场景中更广泛地使用。 以下列表简要介绍了一些可用的消息传递抽象:spring-doc.cadn.net.cn

Java 配置(即@EnableWebSocketMessageBroker) 和 XML 命名空间配置 (即,<websocket:message-broker>) 使用上述组件来组合消息 工作流程。下图显示了当简单内置消息时使用的组件 broker 已启用:spring-doc.cadn.net.cn

消息流简单代理

上图显示了三个消息通道:spring-doc.cadn.net.cn

下图显示了外部代理(例如 RabbitMQ)时使用的组件 配置为管理订阅和广播消息:spring-doc.cadn.net.cn

消息流代理中继

前面两个图之间的主要区别在于使用“代理中继”进行传递 消息通过 TCP 发送到外部 STOMP 代理,并从 经纪人到订阅的客户。spring-doc.cadn.net.cn

当从 WebSocket 连接接收消息时,它们会被解码为 STOMP 帧, 变成了一根SpringMessage表示,并发送到clientInboundChannel用于进一步处理。例如,其 STOMP 消息 目标标头以/app可以路由到@MessageMapping方法 带注释的控制器,而/topic/queue消息可以直接路由 到消息代理。spring-doc.cadn.net.cn

一个带注释的@Controller处理来自客户端的 STOMP 消息的 可以将消息发送到 消息代理通过brokerChannel,并且代理广播 通过clientOutboundChannel.一样 控制器也可以执行相同的作来响应 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然后路由到 消息代理,用于存储客户端订阅。spring-doc.cadn.net.cn

  3. 客户端将 SEND 帧发送到/app/greeting.这/app前缀有助于将其路由到 带注释的控制器。之后/app前缀被剥离,其余的/greeting目标的一部分映射到@MessageMapping方法GreetingController.spring-doc.cadn.net.cn

  4. GreetingController变成了弹簧Message跟 基于返回值的有效负载和默认目标标头/topic/greeting(派生自输入目标,其中/app替换为/topic).生成的消息被发送到brokerChannel并处理 由消息代理。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

方法参数 描述

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

不需要此注释的存在,因为默认情况下,如果没有 其他参数匹配。spring-doc.cadn.net.cn

您可以使用以下命令注释有效负载参数@javax.validation.Valid或 Spring 的@Validated, 自动验证有效负载参数。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

返回值

默认情况下,来自@MessageMapping方法序列化为有效负载 通过匹配MessageConverter并作为MessagebrokerChannel, 从那里向订阅者广播。出站消息的目的地是 与入站消息相同,但前缀为/topic.spring-doc.cadn.net.cn

您可以使用@SendTo@SendToUser注释来自定义目标 输出消息。@SendTo用于自定义目标目标或 指定多个目的地。@SendToUser用于引导输出消息 仅与输入消息关联的用户。请参阅用户目标spring-doc.cadn.net.cn

您可以同时使用@SendTo@SendToUser同时在同一个方法上,并且同时 在类级别受支持,在这种情况下,它们充当 类。但是,请记住,任何方法级别@SendTo@SendToUser附注 在类级别覆盖任何此类注释。spring-doc.cadn.net.cn

消息可以异步处理,并且@MessageMapping方法可以返回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覆盖此行为并发送给代理。spring-doc.cadn.net.cn

这在什么时候有用?假设代理映射到/topic/queue而 应用程序控制器映射到/app.在此设置中,代理将所有 订阅/topic/queue用于重复广播,以及 应用程序无需参与。客户端还可以订阅 一些/appdestination,控制器可以返回一个值来响应该值 订阅而不涉及代理,无需再次存储或使用订阅 (实际上是一次性请求-回复交换)。其中一个用例是填充 UI 在启动时使用初始数据。spring-doc.cadn.net.cn

什么时候这没有用?不要尝试将代理和控制器映射到同一目标 prefix,除非你希望两者独立处理消息,包括订阅, 出于某种原因。入站消息是并行处理的。无法保证是否 代理或控制器首先处理给定的消息。如果目标是通知 当订阅存储并准备好进行广播时,客户端应请求 如果服务器支持(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方法。您可以在注释中声明异常 如果要访问异常实例,则通过方法参数。 以下示例通过方法参数声明异常: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和 用它来发送消息。通常,您可以通过以下方式注入它 类型,如以下示例所示: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. 简单代理

内置的简单消息代理处理来自客户端的订阅请求, 将它们存储在内存中,并将消息广播到具有匹配的连接客户端 目的地。代理支持类似路径的目标,包括订阅 到 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. 外部代理

简单的代理非常适合入门,但仅支持 STOMP 命令(它不支持 acks、receipts 和其他一些功能), 依赖于简单的消息发送循环,不适合聚类。 或者,您可以升级应用程序以使用功能齐全的 消息代理。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 请求处理方法、 业务服务等)也可以向代理中继发送消息,如所述 在发送消息中,将消息广播到订阅的 WebSocket 客户端。spring-doc.cadn.net.cn

实际上,代理中继支持强大且可扩展的消息广播。spring-doc.cadn.net.cn

4.4.10. 连接到代理

STOMP 代理中继维护与代理的单个“系统”TCP 连接。 此连接用于源自服务器端应用程序的邮件 仅用于接收消息。您可以配置 STOMP 凭证(即 STOMP 框架loginpasscodeheaders) 来建立此连接。这暴露了 在 XML 命名空间和 Java 配置中作为systemLoginsystemPasscode默认值为guestguest.spring-doc.cadn.net.cn

STOMP 代理中继还为每个连接的 TCP 创建单独的 TCP 连接 WebSocket 客户端。您可以配置用于所有 TCP 的 STOMP 凭证 代表客户端创建的连接。这在 XML 命名空间中都公开 和 Java 配置作为clientLoginclientPasscode属性 值guestguest.spring-doc.cadn.net.cn

STOMP 代理中继始终将loginpasscode每个CONNECT它代表客户转发给经纪人的框架。因此,WebSocket 客户端 不需要设置这些标头。他们被忽略了。正如身份验证部分所解释的,WebSocket 客户端应该依赖 HTTP 身份验证来保护 WebSocket 端点并建立客户端身份。

STOMP 代理中继还向消息发送和接收检测信号 通过“系统”TCP 连接进行代理。您可以配置发送间隔 并接收心跳(默认情况下每个 10 秒)。如果连接到代理 丢失时,代理中继继续尝试重新连接,每 5 秒一次, 直到它成功。spring-doc.cadn.net.cn

任何 Spring bean 都可以实现ApplicationListener<BrokerAvailabilityEvent>在与代理的“系统”连接丢失时接收通知,并且 重新建立。例如,广播股票报价的股票报价服务可以 当没有活动的“系统”连接时,停止尝试发送消息。spring-doc.cadn.net.cn

默认情况下,STOMP 代理中继始终连接,并在以下情况下根据需要重新连接连接到同一主机和端口。如果您希望提供多个地址,在每次尝试连接时,您可以配置地址提供商,而不是固定主机和端口。以下示例显示了如何执行此作: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. 默认情况下,模式应使用斜杠 () 作为分隔符。这是 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

在前面的示例中,我们没有更改“代理中继”上的前缀,因为这些 完全依赖于外部消息代理。请参阅 STOMP 文档页面 用于查看目标标头支持哪些约定的代理。spring-doc.cadn.net.cn

另一方面,“简单代理”确实依赖于配置的PathMatcher,所以,如果 切换分隔符,该更改也适用于代理和代理匹配方式 从消息到订阅模式的目标。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 协议确实有loginpasscode标头CONNECT框架。 这些最初是为 STOMP over TCP 设计的,也是 STOMP 所必需的。但是,对于 STOMP 通过 WebSocket,默认情况下,Spring 忽略 STOMP 协议的身份验证标头 级别,并假定用户已在 HTTP 传输级别进行身份验证。 预期 WebSocket 或 SockJS 会话包含经过身份验证的用户。spring-doc.cadn.net.cn

4.4.13. Tokens身份验证

Spring Security OAuth支持基于Tokens的安全性,包括JSON Web Token(JWT)。 您可以将其用作 Web 应用程序中的身份验证机制, 包括基于 WebSocket 的 STOMP 交互,如前面所述 部分(即通过基于 cookie 的会话维护身份)。spring-doc.cadn.net.cn

同时,基于 cookie 的会话并不总是最合适的(例如, 在不维护服务器端会话的应用程序中或 通常使用标头进行身份验证的移动应用程序)。spring-doc.cadn.net.cn

WebSocket 协议 RFC 6455“没有规定服务器可以在 WebSocket 握手。然而,在实践中,浏览器客户端只能使用标准 身份验证标头(即基本 HTTP 身份验证)或 cookie,并且不能(例如) 提供自定义标头。同样,SockJS JavaScript 客户端不提供 一种使用 SockJS 传输请求发送 HTTP 标头的方法。请参阅 sockjs-client 问题 196。 相反,它确实允许发送可用于发送Tokens的查询参数, 但这有其自身的缺点(例如,Tokens可能无意中 使用服务器日志中的 URL 记录)。spring-doc.cadn.net.cn

上述限制适用于基于浏览器的客户端,不适用于 基于 Java 的 Spring STOMP 客户端,它确实支持使用 WebSocket 和 SockJS 请求。

因此,希望避免使用 cookie 的应用程序可能没有任何好处 HTTP 协议级别身份验证的替代方案。而不是使用 cookie, 他们可能更喜欢在 STOMP 消息传递协议级别使用标头进行身份验证。 这样做需要两个简单的步骤:spring-doc.cadn.net.cn

  1. 使用 STOMP 客户端在连接时传递身份验证标头。spring-doc.cadn.net.cn

  2. 使用ChannelInterceptor.spring-doc.cadn.net.cn

下一个示例使用服务器端配置来注册自定义身份验证 拦截 器。请注意,拦截器只需要进行身份验证并设置 CONNECT 上的用户标头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 的授权时,目前, 您需要确保身份验证ChannelInterceptor配置是有序的 领先于 Spring Security 的。最好通过在 它自己的实现WebSocketMessageBrokerConfigurer标有@Order(Ordered.HIGHEST_PRECEDENCE + 99).spring-doc.cadn.net.cn

4.4.14. 授权

Spring Security 提供 WebSocket 子协议授权,该授权使用ChannelInterceptor根据邮件中的用户标头授权邮件。 此外,Spring Session 提供 WebSocket 集成,确保用户的 HTTP 会话在 WebSocket 会话仍处于活动状态时不会过期。spring-doc.cadn.net.cn

4.4.15. 用户目标

应用程序可以发送针对特定用户的消息,并且 Spring 的 STOMP 支持 识别以 为前缀的目的地/user/为此目的。 例如,客户端可能会订阅/user/queue/position-updates目的地。UserDestinationMessageHandler处理此目的地并将其转换为 用户会话唯一的目标(例如/queue/position-updates-user123). 这提供了订阅通用命名目标的便利,同时, 同时,确保不会与订阅相同的其他用户发生冲突 目的地,以便每个用户都可以接收唯一的股票头寸更新。spring-doc.cadn.net.cn

使用用户目标时,配置代理和 应用程序目标前缀,如启用 STOMP 中所示,否则 broker 将处理仅应由UserDestinationMessageHandler.

在发送端,可以将消息发送到目标,例如/user/{username}/queue/position-updates,这反过来又被翻译 通过UserDestinationMessageHandler到一个或多个目的地,每个目的地一个 会话。这允许应用程序中的任何组件 发送针对特定用户的消息,而不一定知道更多信息 而不是它们的名称和通用目的地。这也通过 注释和消息传递模板。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 命名空间。(豆名是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());
    }
}
将用户目标与外部消息代理一起使用时,应检查代理 有关如何管理非活动队列的文档,以便当用户会话 结束时,将删除所有唯一用户队列。例如,RabbitMQ 创建自动删除 队列/exchange/amq.direct/position-updates. 因此,在这种情况下,客户端可以订阅/user/exchange/amq.direct/position-updates. 同样,ActiveMQ 具有用于清除非活动目标的配置选项

在多应用程序服务器方案中,用户目标可能保持未解析状态,因为 用户连接到不同的服务器。在这种情况下,您可以配置 destination 广播未解析的消息,以便其他服务器有机会尝试。 这可以通过userDestinationBroadcast属性的MessageBrokerRegistry在 Java 配置中,并且user-destination-broadcast属性 的message-brokerXML 中的元素。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:指示代理何时可用或不可用。 虽然“简单”代理在启动时立即可用,并且在 应用程序正在运行时,STOMP“代理中继”可能会失去连接 到功能齐全的代理(例如,如果代理已重新启动)。经纪人中继 具有重新连接逻辑并重新建立与代理的“系统”连接 当它回来时。因此,每当状态从 connected 更改时,都会发布此事件 断开连接,反之亦然。使用SimpMessagingTemplate应该 订阅此事件,并避免在代理未订阅时发送消息 可用。无论如何,他们应该准备好处理MessageDeliveryException发送消息时。spring-doc.cadn.net.cn

  • SessionConnectEvent:收到新的 STOMP CONNECT 时发布 指示新客户端会话的开始。该事件包含表示 connect,包括会话 ID、用户信息(如果有)以及客户端的任何自定义标头 送。这对于跟踪客户端会话很有用。订阅的组件 到这个事件可以用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 取消订阅时发布。spring-doc.cadn.net.cn

  • SessionDisconnectEvent:在 STOMP 会话结束时发布。断开连接可能会 已从客户端发送,或者当 WebSocket 会话已关闭。在某些情况下,此事件会多次发布 每个会话。组件对于多个断开连接事件应具有幂等性。spring-doc.cadn.net.cn

当您使用功能齐全的代理时,STOMP 的“代理中继”会自动重新连接 “system”连接,如果代理暂时不可用。客户端连接, 但是,不会自动重新连接。假设启用了检测信号,则客户端 通常会注意到代理在 10 秒内没有响应。客户需要 实现自己的重新连接逻辑。

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 会话已关闭。在某些情况下,拦截器可能会拦截此内容 消息。组件应在 多个断开连接事件。spring-doc.cadn.net.cn

4.4.19. STOMP 客户端

Spring 提供了 STOMP over WebSocket 客户端和 STOMP over TCP 客户端。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) {
        // ...
    }
}

会话建立后,可以发送任何有效负载,并且 序列化,配置的MessageConverter,如以下示例所示:spring-doc.cadn.net.cn

session.send("/topic/something", "payload");

您还可以订阅目标。这subscribe方法需要处理程序 for 订阅上的消息,并返回Subscription处理你可以的 用于取消订阅。对于每条收到的消息,处理程序可以指定目标Object有效负载应反序列化到的类型,如以下示例所示: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仅在不活动的情况下发送检测信号,即当没有 发送其他消息。这在使用外部代理时可能会带来挑战 由于具有非代理目标的消息代表活动,但实际上并非如此 转发给经纪人。在这种情况下,您可以配置TaskScheduler初始化外部代理时,确保 检测信号也会转发到代理,同时只有非代理的消息 destination 被发送。spring-doc.cadn.net.cn

当您使用WebSocketStompClient用于模拟数千人的性能测试 来自同一台机器的客户端,请考虑关闭检测信号,因为每个 connection 计划自己的检测信号任务,但未针对 在同一台机器上运行的大量客户端。

STOMP 协议还支持收据,其中客户端必须添加receipt标头,服务器在发送或 订阅。为了支持这一点,该StompSession提供setAutoReceipt(boolean)这会导致receiptheader 设置为 添加到每个后续的发送或订阅事件中。 或者,您也可以手动将收据标题添加到StompHeaders. send 和 subscribe 都返回一个实例Receiptable可用于注册收据成功和失败回调。 对于此功能,您必须使用TaskScheduler以及收据到期前的时间量(默认为 15 秒)。spring-doc.cadn.net.cn

请注意StompSessionHandler本身是一个StompFrameHandler,这允许 除了handleException回调 处理消息的异常和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 会话都长。因此,您需要使用 范围代理模式,如以下示例所示: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 中的时间 session 属性。随后返回同一实例,直到会话 结束。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

请参阅ThreadPoolExecutor了解这些属性的工作原理,以及 了解各种排队策略。spring-doc.cadn.net.cn

clientOutboundChannel这方面,都是关于向 WebSocket 发送消息 客户。如果客户端在快速网络上,则线程数应 保持接近可用处理器的数量。如果它们很慢或开启 低带宽,它们需要更长的时间来消耗消息并给 线程池。因此,增加线程池大小变得必要。spring-doc.cadn.net.cn

虽然clientInboundChannel可以预测——毕竟,它是基于应用程序所做的事情——如何配置 “clientOutboundChannel”更难,因为它基于超出 应用程序的控制。因此,另外两个 属性与消息的发送有关:sendTimeLimitsendBufferSizeLimit.您可以使用这些方法来配置 send 允许获取以及发送时可以缓冲多少数据 消息发送给客户端。spring-doc.cadn.net.cn

总体思路是,在任何给定时间,只能使用单个线程 发送给客户端。同时,所有其他消息都会被缓冲,并且您 可以使用这些属性来决定允许发送消息多长时间 获取以及在此期间可以缓冲多少数据。请参阅 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>

关于扩展的一个重要点涉及使用多个应用程序实例。 目前,您无法使用简单的经纪人来做到这一点。 但是,当您使用功能齐全的代理(例如 RabbitMQ)时,每个应用程序 实例连接到代理,消息从一个应用程序广播 实例可以通过代理广播到连接的 WebSocket 客户端 通过任何其他应用程序实例。spring-doc.cadn.net.cn

4.4.22. 监控

当您使用@EnableWebSocketMessageBroker<websocket:message-broker>钥匙 基础结构组件会自动收集统计信息和计数器,这些统计信息和计数器提供 对应用程序内部状态的重要见解。配置 还声明了类型为WebSocketMessageBrokerStats汇集了所有 可用信息集中在一个地方,默认情况下将其记录在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

超出发送限制

超过配置的发送后关闭的会话 超时或发送缓冲区限制,这可能发生在慢速客户端上 (见上一节)。spring-doc.cadn.net.cn

传输错误

传输错误后关闭的会话,例如 无法读取或写入 WebSocket 连接,或 HTTP 请求或响应。spring-doc.cadn.net.cn

STOMP 框架

CONNECT、CONNECTED 和 DISCONNECT 帧的总数 已处理,指示在 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 测试测试处理 HTTP 请求的控制器的方法 框架——也就是说,不运行 Servlet 容器,而是依赖于 Spring Framework 以调用带注释的控制器。与 Spring MVC 测试一样,您有两个 这里可能的替代方案是,使用“基于上下文”或使用“独立”设置:spring-doc.cadn.net.cn

  • 借助 Spring TestContext 框架,注入clientInboundChannel作为测试字段,以及 使用它来发送要由控制器方法处理的消息。spring-doc.cadn.net.cn

  • 手动设置调用所需的最低 Spring 框架基础设施 控制器(即SimpAnnotationMethodMessageHandler) 并传递消息 控制器直接发送到它。spring-doc.cadn.net.cn

这两种设置方案都在股票投资组合示例应用程序的测试中进行了演示。spring-doc.cadn.net.cn

第二种方法是创建端到端集成测试。为此,您需要 以嵌入式模式运行 WebSocket 服务器并作为 WebSocket 客户端连接到它 发送包含 STOMP 帧的 WebSocket 消息。 股票投资组合示例应用程序的测试还通过使用 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 层只是众多层之一。它作为 入口点到服务器端应用程序,它委托给服务对象 (门面),以满足特定于业务的(和 与演示技术无关)用例。在 Spring 中,这些服务对象,任何其他 特定于业务的对象、数据访问对象和其他对象存在于一个不同的“业务”中 context“,其中不包含 Web 或表示层对象(表示对象、 例如 Spring MVC 控制器,通常配置在一个不同的“表示 上下文“)。本节详细介绍了如何配置 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);

WebApplicationContextUtils类是为了方便起见,所以你不需要记住ServletContext属性。其getWebApplicationContext()方法返回null如果对象 在WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE钥匙。而不是冒险获得NullPointerExceptions在您的应用中,它更好 使用getRequiredWebApplicationContext()方法。此方法抛出异常 当ApplicationContext不见了。spring-doc.cadn.net.cn

一旦你引用了WebApplicationContext,您可以通过其 名称或类型。大多数开发人员按名称检索 bean,然后将它们转换为其中一个 实现的接口。spring-doc.cadn.net.cn

幸运的是,本节中的大多数框架都有更简单的查找 Bean 的方法。 它们不仅可以轻松地从 Spring 容器中获取豆子,而且还可以让您 在其控制器上使用依赖注入。每个 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符合 JSF 标准ELResolver实现 与 JSF 和 JSP 使用的标准 Unified EL 集成。它委托给 Spring 的“业务上下文”WebApplicationContext首先,然后到 底层 JSF 实现的默认解析器。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将属性映射到faces-config.xml,但是,有时,您可能需要显式地抓取一个 bean。 这FacesContextUtils班级让这一切变得容易。它类似于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 软件基金会托管。当时,它大大简化了 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. 更多资源

以下链接可转到有关 中描述的各种 Web 框架的更多资源 本章。spring-doc.cadn.net.cn