Web 响应式

1. 春季 WebFlux

Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是 专为 Servlet API 和 Servlet 容器构建。响应式堆栈 Web 框架 Spring WebFlux,后来在 5.0 版本中添加。它是完全无阻塞的,支持反应流背压,并在以下服务器上运行 Netty、Undertow 和 Servlet 3.1+ 容器。spring-doc.cadn.net.cn

两个 Web 框架都镜像其源模块的名称 (spring-webmvcspring-webflux)并并存在 Spring 框架。每个模块都是可选的。应用程序可以使用一个模块或另一个模块, 在某些情况下,两者兼而有之——例如,具有响应式WebClient.spring-doc.cadn.net.cn

1.1. 概述

为什么要创建 Spring WebFlux?spring-doc.cadn.net.cn

部分答案是需要一个非阻塞 Web 堆栈来处理并发性 线程数少,以更少的硬件资源进行扩展。Servlet 3.1 确实提供了 用于非阻塞 I/O 的 API。但是,使用它会导致远离 Servlet API 的其余部分, 其中合约是同步的 (Filter,Servlet) 或阻塞 (getParameter,getPart).这就是新的通用 API 作为基础的动机 任何非阻塞运行时。这很重要,因为服务器(例如 Netty)是 在异步、非阻塞领域建立良好。spring-doc.cadn.net.cn

答案的另一部分是函数式编程。就像添加注释一样 在 Java 5 中创造了机会(例如带注释的 REST 控制器或单元测试),添加 的 lambda 表达式为 Java 中的函数式 API 创造了机会 8。 这对于非阻塞应用程序和延续式 API(如普及的那样)来说是一个福音 由CompletableFutureReactiveX),允许声明式 异步逻辑的组成。在编程模型级别,Java 8 启用了 Spring WebFlux 提供功能性 Web 端点以及带注释的控制器。spring-doc.cadn.net.cn

1.1.1. 定义“响应式”

我们谈到了“非阻塞”和“功能性”,但响应式是什么意思?spring-doc.cadn.net.cn

术语“响应式”是指围绕对变化做出反应而构建的编程模型——对 I/O 事件做出反应的网络组件、对鼠标事件做出反应的 UI 控制器等等。 从这个意义上说,非阻塞是被动的,因为我们现在不是被阻塞,而是处于模式 在作完成或数据可用时对通知做出反应。spring-doc.cadn.net.cn

我们还 Spring 团队将另一个重要机制与“响应式”联系在一起 这就是非阻塞背压。在同步命令式代码中,阻止调用 作为一种自然形式的背压,迫使呼叫者等待。在非阻塞中 代码中,控制事件的速率变得很重要,这样快速生产者就不会 压倒了它的目的地。spring-doc.cadn.net.cn

Reactive Streams 是一个小规范(在 Java 9 中也采用) 这定义了异步组件与背压之间的交互。 例如,数据存储库(充当发布者) 可以生成 HTTP 服务器(充当订阅者)的数据 然后可以写入响应。Reactive Streams 的主要目的是让 订阅者控制发布者生成数据的速度或速度。spring-doc.cadn.net.cn

常见问题:如果发布商不能放慢速度怎么办?
反应流的目的只是建立机制和边界。 如果发布者无法减慢速度,则必须决定是缓冲、丢弃还是失败。

1.1.2. 响应式 API

反应流在互作性方面发挥着重要作用。图书馆对此感兴趣 和基础设施组件,但作为应用程序 API 不太有用,因为它太有用了 低级。应用程序需要更高级别、更丰富的功能 API 来 compose 异步逻辑 — 类似于 Java 8StreamAPI,但不仅适用于集合。 这就是响应式库所扮演的角色。spring-doc.cadn.net.cn

Reactor 是首选的反应性库 Spring WebFlux 的 WebFlux。它提供了MonoFluxAPI 类型 处理 0..1 (Mono) 和 0..N (Flux)通过一组丰富的运算符与 ReactiveX 运算符词汇表。 Reactor 是一个 Reactive Streams 库,因此,它的所有运算符都支持非阻塞背压。 Reactor 非常注重服务器端 Java。它是在密切合作下开发的 与Spring。spring-doc.cadn.net.cn

WebFlux 需要 Reactor 作为核心依赖项,但它可以与其他响应式互作 通过反应流的库。作为一般规则,WebFlux API 接受普通Publisher作为输入,在内部将其适配为 Reactor 类型,使用它,并返回FluxMono作为输出。因此,您可以传递任何Publisher作为输入,您可以应用 作,但您需要调整输出以与另一个响应式库一起使用。 只要可行(例如,带注释的控制器),WebFlux 就会透明地适应使用 RxJava 或其他响应式库。有关更多详细信息,请参阅响应式库spring-doc.cadn.net.cn

除了响应式 API 之外,WebFlux 还可以与 Kotlin 中的协程 API 一起使用,从而提供更命令性的编程风格。 协程 API 将提供以下 Kotlin 代码示例。

1.1.3. 编程模型

spring-web模块包含作为 Spring WebFlux 基础的响应式基础, 包括 HTTP 抽象、支持的响应式流适配器 服务器、编解码器和核心WebHandler应用程序接口可与 Servlet API,但具有非阻塞合约。spring-doc.cadn.net.cn

在此基础上,Spring WebFlux 提供了两种编程模型的选择:spring-doc.cadn.net.cn

  • 带注释的控制器:与 Spring MVC 一致并基于相同的注释 从spring-web模块。Spring MVC 和 WebFlux 控制器都支持响应式 (Reactor 和 RxJava) 返回类型,因此,很难区分它们。一个值得注意的 不同之处在于 WebFlux 还支持响应式@RequestBody参数。spring-doc.cadn.net.cn

  • 功能端点:基于 Lambda 的轻量级函数式编程模型。你可以想到 这作为一个小型库或一组实用程序,应用程序可以使用它们来路由和 处理请求。与带注释的控制器的最大区别在于应用程序 负责从头到尾处理请求,而不是通过 注释并被回调。spring-doc.cadn.net.cn

1.1.4. 适用性

Spring MVC 还是 WebFlux?spring-doc.cadn.net.cn

这是一个很自然的问题,但却设置了一个不健全的二分法。实际上,两者 共同努力,扩大可用选项的范围。两者专为 彼此的连续性和一致性,它们并排可用,并且反馈 双方都受益。下图显示了两者的关系,它们是什么 有共同点,以及每个共同点支持的内容:spring-doc.cadn.net.cn

Spring MVC 和 WebFlux 维恩

我们建议您考虑以下具体要点:spring-doc.cadn.net.cn

  • 如果你有一个运行良好的 Spring MVC 应用程序,则无需更改。 命令式编程是编写、理解和调试代码的最简单方法。 您可以最大限度地选择库,因为从历史上看,大多数库都是阻塞的。spring-doc.cadn.net.cn

  • 如果您已经在购买非阻塞 Web 堆栈,Spring WebFlux 提供相同的功能 执行模型与该领域的其他模型一样受益,并且还提供了服务器的选择 (Netty、Tomcat、Jetty、Undertow 和 Servlet 3.1+ 容器),可选择编程模型 (带注释的控制器和功能性 Web 端点),以及响应式库的选择 (Reactor、RxJava 或其他)。spring-doc.cadn.net.cn

  • 如果您对与 Java 8 lambda 一起使用的轻量级、功能齐全的 Web 框架感兴趣 或 Kotlin,您可以使用 Spring WebFlux 功能 Web 端点。这也是一个不错的选择 适用于要求不太复杂的小型应用程序或微服务,可以受益 来自更大的透明度和控制。spring-doc.cadn.net.cn

  • 在微服务架构中,您可以混合使用具有 Spring MVC 的应用程序 或 Spring WebFlux 控制器或 Spring WebFlux 功能端点。获得支持 对于两个框架中相同的基于注释的编程模型,可以更轻松地 重复使用知识,同时为正确的工作选择正确的工具。spring-doc.cadn.net.cn

  • 评估应用程序的一种简单方法是检查其依赖关系。如果您有阻止 持久化 API(JPA、JDBC)或网络 API 使用,Spring MVC 是最佳选择 至少对于通用架构来说是这样。它在技术上是可行的,无论是 Reactor 还是 RxJava 在单独的线程上执行阻塞调用,但您不会将 大部分是非阻塞 Web 堆栈。spring-doc.cadn.net.cn

  • 如果你有一个调用远程服务的 Spring MVC 应用程序,请尝试响应式WebClient. 您可以返回响应式类型(Reactor、RxJava 或其他) 直接来自 Spring MVC 控制器方法。每次调用的延迟越大,或者 呼叫之间的相互依赖性,好处就越显着。Spring MVC 控制器 也可以调用其他响应式组件。spring-doc.cadn.net.cn

  • 如果您有一个庞大的团队,请记住向非阻塞转变的陡峭学习曲线, 函数式编程和声明式编程。无需全开关即可启动的实用方法 就是用响应式WebClient.除此之外,从小处着手并衡量收益。 我们预计,对于广泛的应用,这种转变是不必要的。如果你是 不确定要寻找什么好处,请从了解非阻塞 I/O 的工作原理开始 (例如,单线程Node.js上的并发性)及其效果。spring-doc.cadn.net.cn

1.1.5. 服务器

Tomcat、Jetty、Servlet 3.1+ 容器以及 非 Servlet 运行时,例如 Netty 和 Undertow。所有服务器都适配到低级通用 API,以便可以跨服务器支持更高级别的编程模型spring-doc.cadn.net.cn

Spring WebFlux 没有内置支持来启动或停止服务器。然而,它确实是 从 Spring 配置和 WebFlux 基础设施轻松组装应用程序,并使用几个 代码行。spring-doc.cadn.net.cn

Spring Boot 有一个 WebFlux Starters,可以自动执行这些步骤。默认情况下,Starters使用 Netty,但通过更改 Maven 或 Gradle 依赖项。Spring Boot 默认为 Netty,因为它更广泛 用于异步、非阻塞空间,并允许客户端和服务器共享资源。spring-doc.cadn.net.cn

Tomcat 和 Jetty 可以与 Spring MVC 和 WebFlux 一起使用。但是请记住, 它们的使用方式非常不同。Spring MVC 依赖于 Servlet 阻塞 I/O 和 允许应用程序在需要时直接使用 Servlet API。Spring WebFlux 依赖于 Servlet 3.1 非阻塞 I/O,并在低级后面使用 Servlet API 适配器。它不暴露用于直接使用。spring-doc.cadn.net.cn

对于 Undertow,Spring WebFlux 直接使用 Undertow API,而无需 Servlet API。spring-doc.cadn.net.cn

1.1.6. 性能

性能有很多特征和意义。一般响应式和非阻塞性 不要让应用程序运行得更快。在某些情况下,它们可以(例如,如果使用WebClient并行运行远程调用)。总体而言,它需要做更多的工作 事情以非阻塞的方式进行,这会稍微增加所需的处理时间。spring-doc.cadn.net.cn

响应式和非阻塞的关键预期好处是能够通过小的、 固定线程数和更少的内存。这使得应用程序在负载下更具弹性, 因为它们以更可预测的方式扩展。然而,为了观察这些好处,你 需要有一些延迟(包括缓慢和不可预测的网络 I/O 的混合)。 这就是响应式堆栈开始展示其优势的地方,差异可能是 戏剧性的。spring-doc.cadn.net.cn

1.1.7. 并发模型

Spring MVC 和 Spring WebFlux 都支持带注释的控制器,但有一个键 并发模型的差异以及阻塞和线程的默认假设。spring-doc.cadn.net.cn

在 Spring MVC(以及一般的 servlet 应用程序)中,假设应用程序可以 阻止当前线程(例如,对于远程调用)。因此,servlet 容器 使用大型线程池来吸收请求处理期间的潜在阻塞。spring-doc.cadn.net.cn

在 Spring WebFlux(以及一般的非阻塞服务器)中,假设应用程序 不要阻塞。因此,非阻塞服务器使用小型、固定大小的线程池 (事件循环工作程序)来处理请求。spring-doc.cadn.net.cn

“缩放”和“线程数量少”听起来可能很矛盾,但永远不要阻止 当前线程(并依赖回调)意味着您不需要额外的线程,因为 没有要吸收的阻止呼叫。
调用阻塞 API

如果您确实需要使用阻止库怎么办?Reactor 和 RxJava 都提供了publishOn运算符以继续在不同的线程上进行处理。这意味着有一个 轻松逃生舱口。但是请记住,阻止 API 并不适合 这种并发模型。spring-doc.cadn.net.cn

可变状态

在 Reactor 和 RxJava 中,你通过运算符声明逻辑。在运行时,响应式 管道是在不同阶段按顺序处理数据的地方形成的。一个主要优势 其中,它使应用程序不必保护可变状态,因为 该管道中的应用程序代码永远不会同时调用。spring-doc.cadn.net.cn

线程模型

您应该期望在运行 Spring WebFlux 的服务器上看到哪些线程?spring-doc.cadn.net.cn

  • 在“普通”Spring WebFlux 服务器上(例如,没有数据访问或其他可选的 dependencies),您可以期望服务器有一个线程,请求需要其他几个线程 处理(通常与 CPU 内核数一样多)。然而,Servlet 容器 可以从更多线程开始(例如,Tomcat 上的 10 个线程),以支持 servlet(阻塞)I/O 和 servlet 3.1(非阻塞)I/O 使用。spring-doc.cadn.net.cn

  • 响应式WebClient以事件循环样式运行。所以你可以看到一个小的,固定的 与之相关的处理线程数(例如,reactor-http-nio-与反应堆 Netty 连接器)。但是,如果 Reactor Netty 同时用于客户端和服务器,则 默认共享事件循环资源。spring-doc.cadn.net.cn

  • Reactor 和 RxJava 提供了线程池抽象,称为调度器,可与publishOn运算符,用于将处理切换到不同的线程池。 调度程序的名称建议特定的并发策略,例如,“parallel” (对于线程数量有限的 CPU 绑定工作)或“弹性”(用于 I/O 绑定工作 大量线程)。如果您看到此类线程,则表示某些代码正在使用 特定线程池Scheduler策略。spring-doc.cadn.net.cn

  • 数据访问库和其他第三方依赖项也可以创建和使用线程 他们自己的。spring-doc.cadn.net.cn

配置

Spring Framework 不支持启动和停止服务器。要为服务器配置线程模型, 您需要使用特定于服务器的配置 API,或者,如果您使用 Spring Boot, 检查每个服务器的 Spring Boot 配置选项。您可以配置WebClient径直。 对于所有其他库,请参阅其各自的文档。spring-doc.cadn.net.cn

1.2. 响应式内核

spring-web模块包含以下对响应式 Web 的基础支持 应用:spring-doc.cadn.net.cn

1.2.1.HttpHandler

HttpHandler 是一个简单的合约,具有处理请求和响应的单一方法。是的 故意最小化,其主要和唯一目的是成为最小抽象 通过不同的 HTTP 服务器 API。spring-doc.cadn.net.cn

下表介绍了支持的服务器 API:spring-doc.cadn.net.cn

服务器名称 使用的服务器 API 响应式流支持

spring-doc.cadn.net.cn

Netty APIspring-doc.cadn.net.cn

反应器网spring-doc.cadn.net.cn

Undertowspring-doc.cadn.net.cn

暗流 APIspring-doc.cadn.net.cn

spring-web:Undertow 到 Reactive Streams 桥接器spring-doc.cadn.net.cn

Tomcatspring-doc.cadn.net.cn

Servlet 3.1 非阻塞 I/O;用于读写 ByteBuffers 的 Tomcat API 与 byte[]spring-doc.cadn.net.cn

spring-web:Servlet 3.1 非阻塞 I/O 到 Reactive Streams 桥接器spring-doc.cadn.net.cn

Jettyspring-doc.cadn.net.cn

Servlet 3.1 非阻塞 I/O;用于编写 ByteBuffers 的 Jetty API 与 byte[]spring-doc.cadn.net.cn

spring-web:Servlet 3.1 非阻塞 I/O 到 Reactive Streams 桥接器spring-doc.cadn.net.cn

Servlet 3.1 容器spring-doc.cadn.net.cn

Servlet 3.1 非阻塞 I/Ospring-doc.cadn.net.cn

spring-web:Servlet 3.1 非阻塞 I/O 到 Reactive Streams 桥接器spring-doc.cadn.net.cn

下表描述了服务器依赖项(另请参阅支持的版本):spring-doc.cadn.net.cn

服务器名称 组 ID 神器名称

反应器网spring-doc.cadn.net.cn

io.projectreactor.nettyspring-doc.cadn.net.cn

反应器网spring-doc.cadn.net.cn

Undertowspring-doc.cadn.net.cn

io.undertowspring-doc.cadn.net.cn

暗拖核心spring-doc.cadn.net.cn

Tomcatspring-doc.cadn.net.cn

org.apache.tomcat.embedspring-doc.cadn.net.cn

tomcat-嵌入核心spring-doc.cadn.net.cn

Jettyspring-doc.cadn.net.cn

org.eclipse.Jettyspring-doc.cadn.net.cn

jetty-server, jetty-servletspring-doc.cadn.net.cn

下面的代码片段显示了使用HttpHandler适配器与每个服务器 API 一起使用:spring-doc.cadn.net.cn

Java
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();
Kotlin
val handler: HttpHandler = ...
val adapter = ReactorHttpHandlerAdapter(handler)
HttpServer.create().host(host).port(port).handle(adapter).bind().block()
Java
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();
Kotlin
val handler: HttpHandler = ...
val adapter = UndertowHttpHandlerAdapter(handler)
val server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build()
server.start()
Java
HttpHandler handler = ...
Servlet servlet = new TomcatHttpHandlerAdapter(handler);

Tomcat server = new Tomcat();
File base = new File(System.getProperty("java.io.tmpdir"));
Context rootContext = server.addContext("", base.getAbsolutePath());
Tomcat.addServlet(rootContext, "main", servlet);
rootContext.addServletMappingDecoded("/", "main");
server.setHost(host);
server.setPort(port);
server.start();
Kotlin
val handler: HttpHandler = ...
val servlet = TomcatHttpHandlerAdapter(handler)

val server = Tomcat()
val base = File(System.getProperty("java.io.tmpdir"))
val rootContext = server.addContext("", base.absolutePath)
Tomcat.addServlet(rootContext, "main", servlet)
rootContext.addServletMappingDecoded("/", "main")
server.host = host
server.setPort(port)
server.start()
Java
HttpHandler handler = ...
Servlet servlet = new JettyHttpHandlerAdapter(handler);

Server server = new Server();
ServletContextHandler contextHandler = new ServletContextHandler(server, "");
contextHandler.addServlet(new ServletHolder(servlet), "/");
contextHandler.start();

ServerConnector connector = new ServerConnector(server);
connector.setHost(host);
connector.setPort(port);
server.addConnector(connector);
server.start();
Kotlin
val handler: HttpHandler = ...
val servlet = JettyHttpHandlerAdapter(handler)

val server = Server()
val contextHandler = ServletContextHandler(server, "")
contextHandler.addServlet(ServletHolder(servlet), "/")
contextHandler.start();

val connector = ServerConnector(server)
connector.host = host
connector.port = port
server.addConnector(connector)
server.start()

Servlet 3.1+ 容器spring-doc.cadn.net.cn

要作为 WAR 部署到任何 Servlet 3.1+ 容器,您可以扩展并包含AbstractReactiveWebInitializer在战争中。该类包装了HttpHandlerServletHttpHandlerAdapter和寄存器 作为Servlet.spring-doc.cadn.net.cn

1.2.2.WebHandler应用程序接口

org.springframework.web.server包构建在HttpHandler合同 提供通用 Web API,用于通过多个链处理请求WebExceptionHandler倍数WebFilter,并且单个WebHandler元件。链条可以 与WebHttpHandlerBuilder只需指向弹簧ApplicationContext其中组件是自动检测的,和/或通过注册组件 与架构商。spring-doc.cadn.net.cn

HttpHandler有一个简单的目标,即抽象使用不同的 HTTP 服务器,WebHandlerAPI 旨在提供 Web 应用程序中常用的更广泛的功能集 如:spring-doc.cadn.net.cn

特殊Beans

下表列出了以下组件:WebHttpHandlerBuilder可以在Spring ApplicationContext 中自动检测,或者可以直接向其注册:spring-doc.cadn.net.cn

豆子名称 Beans 计数 描述

<任何>spring-doc.cadn.net.cn

WebExceptionHandlerspring-doc.cadn.net.cn

0..Nspring-doc.cadn.net.cn

提供对来自WebFilter实例和目标WebHandler. 有关更多详细信息,请参阅例外。spring-doc.cadn.net.cn

<任何>spring-doc.cadn.net.cn

WebFilterspring-doc.cadn.net.cn

0..Nspring-doc.cadn.net.cn

将拦截样式逻辑应用于过滤器链的其余部分之前和之后,以及目标WebHandler. 有关更多详细信息,请参阅过滤器spring-doc.cadn.net.cn

webHandlerspring-doc.cadn.net.cn

WebHandlerspring-doc.cadn.net.cn

1spring-doc.cadn.net.cn

请求的处理程序。spring-doc.cadn.net.cn

webSessionManagerspring-doc.cadn.net.cn

WebSessionManagerspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

经理WebSession通过ServerWebExchange.DefaultWebSessionManager默认情况下。spring-doc.cadn.net.cn

serverCodecConfigurerspring-doc.cadn.net.cn

ServerCodecConfigurerspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

用于访问HttpMessageReader用于解析表单数据和多部分数据的实例,然后通过方法公开ServerWebExchange.ServerCodecConfigurer.create()默认情况下。spring-doc.cadn.net.cn

localeContextResolverspring-doc.cadn.net.cn

LocaleContextResolverspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

的解析器LocaleContext通过ServerWebExchange.AcceptHeaderLocaleContextResolver默认情况下。spring-doc.cadn.net.cn

forwardedHeaderTransformerspring-doc.cadn.net.cn

ForwardedHeaderTransformerspring-doc.cadn.net.cn

0..1spring-doc.cadn.net.cn

用于处理转发的类型标头,通过提取和删除它们或仅删除它们。 默认情况下不使用。spring-doc.cadn.net.cn

表单数据

ServerWebExchange公开了以下访问表单数据的方法:spring-doc.cadn.net.cn

Java
Mono<MultiValueMap<String, String>> getFormData();
Kotlin
suspend fun getFormData(): MultiValueMap<String, String>

DefaultServerWebExchange使用配置的HttpMessageReader解析表单数据 (application/x-www-form-urlencoded) 转换为MultiValueMap.默认情况下,FormHttpMessageReader配置为ServerCodecConfigurer豆 (请参阅 Web 处理程序 API)。spring-doc.cadn.net.cn

多部分数据

ServerWebExchange公开了以下用于访问多部分数据的方法:spring-doc.cadn.net.cn

Java
Mono<MultiValueMap<String, Part>> getMultipartData();
Kotlin
suspend fun getMultipartData(): MultiValueMap<String, Part>

DefaultServerWebExchange使用配置的HttpMessageReader<MultiValueMap<String, Part>>解析multipart/form-data内容 变成一个MultiValueMap. 默认情况下,这是DefaultPartHttpMessageReader,没有任何第三方 依赖。 或者,SynchronossPartHttpMessageReader可以使用,它基于 Synchronoss NIO Multipart 库。 两者都是通过ServerCodecConfigurer豆 (请参阅 Web 处理程序 API)。spring-doc.cadn.net.cn

要以流式方式解析多部分数据,您可以使用Flux<Part>HttpMessageReader<Part>相反。例如,在带注释的控制器中,使用@RequestPart意味 着Map-like 按名称访问单个部件,因此需要 完整解析多部分数据。相比之下,您可以使用@RequestBody解码 内容设置为Flux<Part>而不收集到MultiValueMap.spring-doc.cadn.net.cn

请求头转发

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

ForwardedHeaderTransformer是修改主机、端口和方案的组件 请求,然后删除这些标头。如果您声明 它作为一个豆子,名字forwardedHeaderTransformer,它将被检测和使用。spring-doc.cadn.net.cn

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

在 5.1 中ForwardedHeaderFilter已被弃用并被ForwardedHeaderTransformer因此,请求头转发可以更早地处理,在 交换被创建。如果仍然配置了过滤器,则它将从 过滤器,以及ForwardedHeaderTransformer而是使用。

1.2.3. 过滤器

WebHandler应用程序接口,您可以使用WebFilter应用拦截样式 过滤器和目标处理链其余部分之前和之后的逻辑WebHandler.使用 WebFlux Config 时,注册WebFilter就这么简单 将其声明为 Spring bean 并(可选)通过使用@Order上 bean 声明或通过实现Ordered.spring-doc.cadn.net.cn

CORS

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

请参阅有关 CORSCORSWebFilter了解更多详情。spring-doc.cadn.net.cn

1.2.4. 例外

WebHandler应用程序接口,您可以使用WebExceptionHandler处理 链中的例外WebFilter实例和目标WebHandler.使用 WebFlux Config 时,注册WebExceptionHandler就像将其声明为 Spring bean 和(可选)通过使用@Order在 bean 声明上或 通过实现Ordered.spring-doc.cadn.net.cn

下表描述了可用的WebExceptionHandler实现:spring-doc.cadn.net.cn

异常处理程序 描述

ResponseStatusExceptionHandlerspring-doc.cadn.net.cn

提供对类型ResponseStatusException通过将响应设置为异常的 HTTP 状态代码。spring-doc.cadn.net.cn

WebFluxResponseStatusExceptionHandlerspring-doc.cadn.net.cn

扩展ResponseStatusExceptionHandler也可以确定 HTTP 状态 代码@ResponseStatus任何异常的注释。spring-doc.cadn.net.cn

此处理程序在 WebFlux 配置中声明。spring-doc.cadn.net.cn

1.2.5. 编解码器

spring-webspring-core模块支持序列化和 通过非阻塞 I/O 将字节内容反序列化到更高级别对象或从更高级别对象反序列化 反应流背压。下面介绍了此支持:spring-doc.cadn.net.cn

spring-core模块提供byte[],ByteBuffer,DataBuffer,ResourceString编码器和解码器实现。这spring-web模块提供 Jackson JSON、Jackson Smile、JAXB2、协议缓冲区和其他编码器和解码器以及 表单数据、多部分内容、 服务器发送的事件等。spring-doc.cadn.net.cn

ClientCodecConfigurerServerCodecConfigurer通常用于配置和自定义要在应用程序中使用的编解码器。请参阅有关配置 HTTP 消息编解码器的部分。spring-doc.cadn.net.cn

Jackson JSON

JSON 和二进制 JSON(微笑)是 当 Jackson 库存在时,两者都受支持。spring-doc.cadn.net.cn

Jackson2Decoder工作原理如下:spring-doc.cadn.net.cn

  • Jackson 的异步、非阻塞解析器用于聚合字节块流 到TokenBuffer的每个代表一个 JSON 对象。spring-doc.cadn.net.cn

  • TokenBuffer传给Jackson的ObjectMapper以创建更高级别的对象。spring-doc.cadn.net.cn

  • 当解码到单值发布者(例如Mono),有一个TokenBuffer.spring-doc.cadn.net.cn

  • 解码到多值发布者时(例如Flux),每个TokenBuffer被传递给 这ObjectMapper一旦为完全格式的对象接收到足够的字节。 这 输入内容可以是 JSON 数组,也可以是任何以行分隔的 JSON 格式,例如 NDJSON、JSON 行或 JSON 文本序列。spring-doc.cadn.net.cn

Jackson2Encoder工作原理如下:spring-doc.cadn.net.cn

  • 对于单一价值发布商(例如Mono),只需通过ObjectMapper.spring-doc.cadn.net.cn

  • 对于具有application/json,默认情况下使用Flux#collectToList(),然后序列化生成的集合。spring-doc.cadn.net.cn

  • 对于具有流媒体类型(如application/x-ndjsonapplication/stream+x-jackson-smile、编码、写入和使用以行分隔的 JSON 格式单独刷新每个值。 其他 流媒体类型可以注册到编码器。spring-doc.cadn.net.cn

  • 对于 SSE,Jackson2Encoder每个事件调用并刷新输出以确保立即交付。spring-doc.cadn.net.cn

默认情况下,两者Jackson2EncoderJackson2Decoder不支持类型String. 相反,默认假设是一个字符串或字符串序列表示序列化的 JSON 内容,由CharSequenceEncoder. 如果什么你需要的是从Flux<String>Flux#collectToList()和 对Mono<List<String>>.spring-doc.cadn.net.cn

表单数据

FormHttpMessageReaderFormHttpMessageWriter支持解码和编码application/x-www-form-urlencoded内容。spring-doc.cadn.net.cn

在经常需要从多个地方访问表单内容的服务器端,ServerWebExchange提供专用的getFormData()解析内容的方法 通过FormHttpMessageReader然后缓存结果以供重复访问。请参阅 表单数据 中的WebHandler应用程序接口部分。spring-doc.cadn.net.cn

一次getFormData()原始原始内容,则无法再从请求正文中读取原始内容。因此,应用程序应通过ServerWebExchange一致地访问缓存的表单数据,而不是从原始请求正文中读取。spring-doc.cadn.net.cn

多部分

MultipartHttpMessageReaderMultipartHttpMessageWriter支持解码和对“multipart/form-data”内容进行编码。 挨次MultipartHttpMessageReader委托给 另一个HttpMessageReader用于实际解析为Flux<Part>然后简单地将零件收集到MultiValueMap. 默认情况下,DefaultPartHttpMessageReader,但这可以通过ServerCodecConfigurer. 有关DefaultPartHttpMessageReader,请参阅javadoc 的DefaultPartHttpMessageReader.spring-doc.cadn.net.cn

在服务器端,可能需要从多个 地方ServerWebExchange提供专用的getMultipartData()解析的方法 内容通过MultipartHttpMessageReader然后缓存结果以供重复访问。 请参阅WebHandler应用程序接口部分。spring-doc.cadn.net.cn

一次getMultipartData()使用时,无法再从 请求正文。因此,应用程序必须始终使用getMultipartData()用于重复、类似地图的部件访问,或以其他方式依赖SynchronossPartHttpMessageReader一次性访问Flux<Part>.spring-doc.cadn.net.cn

限制

DecoderHttpMessageReader缓冲部分或全部输入的实现 stream 可以配置对内存中缓冲的最大字节数进行限制。 在某些情况下,缓冲是因为输入被聚合并表示为单个 object — 例如,具有@RequestBody byte[],x-www-form-urlencoded数据,依此类推。缓冲也可能发生在流式处理中,当 拆分输入流 - 例如,分隔文本、JSON 对象流和 依此类推。对于这些流式处理情况,限制适用于关联的字节数 在流中有一个对象。spring-doc.cadn.net.cn

要配置缓冲区大小,您可以检查给定的DecoderHttpMessageReader公开一个maxInMemorySize属性,如果是这样,Javadoc 将包含有关默认值的详细信息 值。在服务器端,ServerCodecConfigurer提供从哪里到 设置所有编解码器,请参阅 HTTP 消息编解码器。在客户端,限制 所有编解码器都可以在 WebClient.Builder 中更改。spring-doc.cadn.net.cn

对于多部分解析maxInMemorySize属性限制 非文件部件的大小。对于文件部件,它确定部件的阈值 写入磁盘。对于写入磁盘的文件部分,还有一个额外的maxDiskUsagePerPart属性来限制每个部分的磁盘空间量。还有 一个maxParts属性来限制多部分请求中的部分总数。 要在 WebFlux 中配置所有三个,您需要提供一个预配置的实例MultipartHttpMessageReaderServerCodecConfigurer.spring-doc.cadn.net.cn

流式传输到 HTTP 响应时(例如text/event-stream,application/x-ndjson),定期发送数据非常重要,以便 尽早可靠地检测断开连接的客户端。这样的发送可能是 仅注释、空 SSE 事件或任何其他“无作”数据,这些数据将有效地充当 心跳。spring-doc.cadn.net.cn

DataBuffer

DataBuffer是 WebFlux 中字节缓冲区的表示形式。的 Spring Core 部分 此参考在数据缓冲区和编解码器部分中有更多相关内容。要理解的关键点是,在某些 像 Netty 这样的服务器,字节缓冲区被池化并对引用进行计数,并且必须释放 使用时以避免内存泄漏。spring-doc.cadn.net.cn

WebFlux 应用程序通常不需要关注此类问题,除非它们 直接使用或生成数据缓冲区,而不是依赖编解码器进行转换 以及来自更高级别的对象,或者除非他们选择创建自定义编解码器。对于这样的 情况请查看数据缓冲区和编解码器中的信息, 尤其是关于使用 DataBuffer 的部分。spring-doc.cadn.net.cn

1.2.6. 日志记录

DEBUGSpring WebFlux 中的级别日志记录被设计为紧凑、最小和 人性化。它侧重于对 与其他仅在调试特定问题时有用的其他问题相比。spring-doc.cadn.net.cn

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

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

日志 ID

在 WebFlux 中,单个请求可以在多个线程上运行,并且线程 ID 对于关联属于特定请求的日志消息没有用处。这就是为什么 默认情况下,WebFlux 日志消息以特定于请求的 ID 为前缀。spring-doc.cadn.net.cn

在服务器端,日志 ID 存储在ServerWebExchange属性 (LOG_ID_ATTRIBUTE), 而基于该 ID 的完整格式化前缀可从ServerWebExchange#getLogPrefix().在WebClientside,日志 ID 存储在ClientRequest属性 (LOG_ID_ATTRIBUTE) ,而完全格式化的前缀可从ClientRequest#logPrefix().spring-doc.cadn.net.cn

敏感数据

DEBUGTRACE日志记录可以记录敏感信息。这就是为什么 form 参数和 默认情况下,标头是屏蔽的,您必须显式启用其完全日志记录。spring-doc.cadn.net.cn

以下示例显示了如何对服务器端请求执行此作:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
    }
}
Kotlin
@Configuration
@EnableWebFlux
class MyConfig : WebFluxConfigurer {

    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        configurer.defaultCodecs().enableLoggingRequestDetails(true)
    }
}

以下示例演示如何对客户端请求执行此作:spring-doc.cadn.net.cn

Java
Consumer<ClientCodecConfigurer> consumer = configurer ->
        configurer.defaultCodecs().enableLoggingRequestDetails(true);

WebClient webClient = WebClient.builder()
        .exchangeStrategies(strategies -> strategies.codecs(consumer))
        .build();
Kotlin
val consumer: (ClientCodecConfigurer) -> Unit  = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) }

val webClient = WebClient.builder()
        .exchangeStrategies({ strategies -> strategies.codecs(consumer) })
        .build()
附加者

SLF4J 和 Log4J 2 等日志记录库提供异步记录器,避免 阻塞。虽然这些都有其自身的缺点,例如可能会丢弃消息 无法排队记录,它们是目前最好的可用选项 用于响应式、非阻塞应用程序。spring-doc.cadn.net.cn

自定义编解码器

应用程序可以注册自定义编解码器以支持其他媒体类型, 或默认编解码器不支持的特定行为。spring-doc.cadn.net.cn

开发人员表达的某些配置选项在默认编解码器上强制执行。 自定义编解码器可能希望有机会与这些偏好保持一致, 例如强制执行缓冲限制记录敏感数据spring-doc.cadn.net.cn

以下示例演示如何对客户端请求执行此作:spring-doc.cadn.net.cn

Java
WebClient webClient = WebClient.builder()
        .codecs(configurer -> {
                CustomDecoder decoder = new CustomDecoder();
                configurer.customCodecs().registerWithDefaultConfig(decoder);
        })
        .build();
Kotlin
val webClient = WebClient.builder()
        .codecs({ configurer ->
                val decoder = CustomDecoder()
                configurer.customCodecs().registerWithDefaultConfig(decoder)
         })
        .build()

1.3.DispatcherHandler

Spring WebFlux 与 Spring MVC 类似,是围绕前端控制器模式设计的, 其中中心WebHandlerDispatcherHandler,为 请求处理,而实际工作由可配置的委托组件执行。 该模型灵活并支持多种工作流程。spring-doc.cadn.net.cn

DispatcherHandler从 Spring 配置中发现它需要的委托组件。 它还被设计成 Spring bean 本身和工具ApplicationContextAware以访问它运行的上下文。如果DispatcherHandler用 bean 声明 名称webHandler,反过来,它又被发现WebHttpHandlerBuilder, 它将请求处理链放在一起,如WebHandler应用程序接口.spring-doc.cadn.net.cn

WebFlux 应用程序中的 Spring 配置通常包含:spring-doc.cadn.net.cn

配置被赋予WebHttpHandlerBuilder构建加工链, 如以下示例所示:spring-doc.cadn.net.cn

Java
ApplicationContext context = ...
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
Kotlin
val context: ApplicationContext = ...
val handler = WebHttpHandlerBuilder.applicationContext(context).build()

由此产生的HttpHandler已准备好与服务器适配器一起使用。spring-doc.cadn.net.cn

1.3.1. 特殊Beans类型

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

下表列出了DispatcherHandler.请注意 在较低级别还检测到一些其他 Bean(请参阅 Web 处理程序 API 中的特殊 Bean 类型)。spring-doc.cadn.net.cn

Beans 解释

HandlerMappingspring-doc.cadn.net.cn

将请求映射到处理程序。映射基于一些标准,详细信息 差异为HandlerMapping实现 — 带注释的控制器,简单 URL 模式映射等。spring-doc.cadn.net.cn

主要HandlerMapping实现是RequestMappingHandlerMapping@RequestMapping注释方法,RouterFunctionMapping对于功能端点routes,以及SimpleUrlHandlerMapping用于显式注册 URI 路径模式 和WebHandler实例。spring-doc.cadn.net.cn

HandlerAdapterspring-doc.cadn.net.cn

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

HandlerResultHandlerspring-doc.cadn.net.cn

处理处理程序调用的结果并完成响应。 请参阅结果处理spring-doc.cadn.net.cn

1.3.2. WebFlux 配置

应用程序可以声明基础架构 Bean(列在 Web 处理程序 APIDispatcherHandler)来处理请求。但是,在大多数情况下,WebFlux Config 是最好的起点。它声明了required bean,并提供了更高级别的配置回调 API 来自定义它。spring-doc.cadn.net.cn

Spring Boot 依靠 WebFlux 配置来配置 Spring WebFlux,并且还提供许多额外的方便选项。

1.3.3. 处理

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

  • HandlerMapping被要求查找匹配的处理程序,并使用第一个匹配项。spring-doc.cadn.net.cn

  • 如果找到处理程序,则通过适当的HandlerAdapter哪 将执行的返回值公开为HandlerResult.spring-doc.cadn.net.cn

  • HandlerResult被给予适当的HandlerResultHandler完成通过直接写入响应或使用视图进行渲染来处理。spring-doc.cadn.net.cn

1.3.4. 结果处理

调用处理程序的返回值,通过HandlerAdapter,被包装作为HandlerResult,以及一些额外的上下文,并传递给第一个HandlerResultHandler声称支持它。下表显示了可用的HandlerResultHandler实现,所有这些都在 WebFlux Config 中声明:spring-doc.cadn.net.cn

结果处理程序类型 返回值 默认顺序

ResponseEntityResultHandlerspring-doc.cadn.net.cn

ResponseEntity,通常来自@Controller实例。spring-doc.cadn.net.cn

0spring-doc.cadn.net.cn

ServerResponseResultHandlerspring-doc.cadn.net.cn

ServerResponse,通常来自功能端点。spring-doc.cadn.net.cn

0spring-doc.cadn.net.cn

ResponseBodyResultHandlerspring-doc.cadn.net.cn

处理来自@ResponseBodymethods 或@RestController类。spring-doc.cadn.net.cn

100spring-doc.cadn.net.cn

ViewResolutionResultHandlerspring-doc.cadn.net.cn

CharSequence,View型号Map渲染, 或任何其他Object被视为模型属性。spring-doc.cadn.net.cn

另请参见视图分辨率spring-doc.cadn.net.cn

Integer.MAX_VALUEspring-doc.cadn.net.cn

1.3.5. 例外

HandlerResultHandlerAdapter可以公开错误函数 基于某些特定于处理程序的机制进行处理。如果出现以下情况,则调用此错误函数:spring-doc.cadn.net.cn

错误函数可以更改响应(例如,更改为错误状态),只要错误 信号发生在从处理程序返回的响应式类型生成任何数据项之前。spring-doc.cadn.net.cn

就是这样@ExceptionHandler方法@Controller支持类。 相比之下,Spring MVC 中对相同内容的支持是建立在HandlerExceptionResolver. 这通常无关紧要。但是,请记住,在 WebFlux 中,您不能使用@ControllerAdvice处理在选择处理程序之前发生的异常。spring-doc.cadn.net.cn

另请参阅“带注释的控制器”部分中的管理异常或 WebHandler API 部分中的异常spring-doc.cadn.net.cn

1.3.6. 视图分辨率

视图分辨率允许渲染到具有 HTML 模板的浏览器和没有 HTML 模板的模型 将您绑定到特定的视图技术。在 Spring WebFlux 中,视图分辨率为 通过专用的 HandlerResultHandler 支持,该 HandlerResultHandler 使用ViewResolver实例将 String(表示逻辑视图名称)映射到View实例。这View然后用于呈现响应。spring-doc.cadn.net.cn

处理

HandlerResult传递给ViewResolutionResultHandler包含返回值 从处理程序和包含请求期间添加的属性的模型 处理。返回值按以下方式之一进行处理:spring-doc.cadn.net.cn

  • String,CharSequence:要解析为View通过 已配置的列表ViewResolver实现。spring-doc.cadn.net.cn

  • void:根据请求路径选择默认视图名称,减去前导和 尾随斜杠,并将其解析为View.当视图名称 未提供(例如,返回 model 属性)或异步返回值 (例如,Mono已完成空)。spring-doc.cadn.net.cn

  • 渲染:API 查看分辨率方案。通过代码补全探索 IDE 中的选项。spring-doc.cadn.net.cn

  • Model,Map:要为请求添加到模型中的额外模型属性。spring-doc.cadn.net.cn

  • 任何其他:任何其他返回值(简单类型除外,由 BeanUtils#isSimpleProperty 确定)被视为要添加到模型中的模型属性。属性名称派生使用约定从类名,除非处理程序方法@ModelAttribute注释存在。spring-doc.cadn.net.cn

该模型可以包含异步响应式类型(例如,来自 Reactor 或 RxJava)。 事先 渲染,AbstractView将此类模型属性解析为具体值并更新模型。单值响应式类型解析为单个值或无值(如果为空),而多值响应式类型(例如Flux<T>) 是 收集并解析为List<T>.spring-doc.cadn.net.cn

要配置视图分辨率,只需添加一个ViewResolutionResultHandler豆 到你的 Spring 配置。WebFlux Config 提供了一个 用于视图解析的专用配置 API。spring-doc.cadn.net.cn

有关与 Spring WebFlux 集成的视图技术的更多信息,请参阅视图技术。spring-doc.cadn.net.cn

重 定向

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

净效果与控制器返回RedirectViewRendering.redirectTo("abc").build(),但现在控制器本身可以 根据逻辑视图名称进行作。视图名称,例如redirect:/some/resource是相对于当前应用程序的,而视图名称(例如redirect:https://example.com/arbitrary/path重定向到绝对 URL。spring-doc.cadn.net.cn

内容协商

ViewResolutionResultHandler支持内容协商。它比较请求 媒体类型,其中包含每个所选媒体支持的媒体类型View.第一个View使用支持请求的媒体类型 () 。spring-doc.cadn.net.cn

为了支持 JSON 和 XML 等媒体类型,Spring WebFlux 提供了HttpMessageWriterView,这是一个特殊的View通过 HttpMessageWriter 呈现。通常,您会将这些配置为默认值 通过 WebFlux 配置进行视图。默认视图为 如果它们与请求的媒体类型匹配,则始终选择并使用。spring-doc.cadn.net.cn

1.4. 带注释的控制器

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

以下列表显示了一个基本示例:spring-doc.cadn.net.cn

Java
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String handle() {
        return "Hello WebFlux";
    }
}
Kotlin
@RestController
class HelloController {

    @GetMapping("/hello")
    fun handle() = "Hello WebFlux"
}

在前面的示例中,该方法返回一个String写入响应正文。spring-doc.cadn.net.cn

1.4.1.@Controller

您可以使用标准 Spring Bean 定义来定义控制器 Bean。 这@Controllerstereotype 允许自动检测,并与 Spring 通用支持用于检测@Component类路径中的类和自动注册 Bean 定义。它还充当带注释的类的构造型,表明它作为Web 组件的角色。spring-doc.cadn.net.cn

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

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

    // ...
}
1 扫描org.example.web包。
Kotlin
@Configuration
@ComponentScan("org.example.web") (1)
class WebConfig {

    // ...
}
1 扫描org.example.web包。

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

1.4.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 模式

您可以使用 glob 模式和通配符来映射请求:spring-doc.cadn.net.cn

模式 描述 示例

?spring-doc.cadn.net.cn

匹配一个字符spring-doc.cadn.net.cn

"/pages/t?st.html"比赛"/pages/test.html""/pages/t3st.html"spring-doc.cadn.net.cn

*spring-doc.cadn.net.cn

匹配路径段中的零个或多个字符spring-doc.cadn.net.cn

"/resources/*.png"比赛"/resources/file.png"spring-doc.cadn.net.cn

"/projects/*/versions"比赛"/projects/spring/versions"但不匹配"/projects/spring/boot/versions"spring-doc.cadn.net.cn

**spring-doc.cadn.net.cn

匹配零个或多个路径段,直到路径结束spring-doc.cadn.net.cn

"/resources/**"比赛"/resources/file.png""/resources/images/file.png"spring-doc.cadn.net.cn

"/resources/**/file.png"无效,因为仅允许在路径末尾使用。**spring-doc.cadn.net.cn

{name}spring-doc.cadn.net.cn

匹配路径段并将其捕获为名为“name”的变量spring-doc.cadn.net.cn

"/projects/{project}/versions"比赛"/projects/spring/versions"和捕获project=springspring-doc.cadn.net.cn

{name:[a-z]+}spring-doc.cadn.net.cn

匹配正则表达式"[a-z]+"作为名为“name”的路径变量spring-doc.cadn.net.cn

"/projects/{project:[a-z]+}/versions"比赛"/projects/spring/versions"但不是"/projects/spring1/versions"spring-doc.cadn.net.cn

{*path}spring-doc.cadn.net.cn

匹配零个或多个路径段,直到路径末尾,并将其捕获为名为“path”的变量spring-doc.cadn.net.cn

"/resources/{*file}"比赛"/resources/images/file.png"和捕获file=/images/file.pngspring-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}") (1)
public class OwnerController {

    @GetMapping("/pets/{petId}") (2)
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}
1 类级 URI 映射。
2 方法级 URI 映射。
Kotlin
@Controller
@RequestMapping("/owners/{ownerId}") (1)
class OwnerController {

    @GetMapping("/pets/{petId}") (2)
    fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
        // ...
    }
}
1 类级 URI 映射。
2 方法级 URI 映射。

URI 变量会自动转换为适当的类型或TypeMismatchException被提高。简单类型 (int,long,Date,依此类推)默认支持 寄存器支持任何其他数据类型。 请参阅类型转换DataBinder.spring-doc.cadn.net.cn

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

语法{*varName}声明一个与零个或多个剩余路径匹配的 URI 变量 段。例如/resources/{*path}匹配下的所有文件/resources/"path"变量捕获/resources.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 version, @PathVariable String ext) {
    // ...
}
Kotlin
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
fun handle(@PathVariable version: String, @PathVariable ext: String) {
    // ...
}

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

Spring WebFlux 使用PathPatternPathPatternParser用于 URI 路径匹配支持。 这两个类都位于spring-web并且专门设计用于 HTTP URL Web 应用程序中的路径,其中运行时匹配大量 URI 路径模式。

Spring WebFlux 不支持后缀模式匹配——与 Spring MVC 不同,其中 映射,例如/person也匹配为/person.*.对于基于 URL 的内容 协商,如果需要,我们建议使用查询参数,这样更简单,更多 显式的,并且不易受到基于 URL 路径的漏洞的攻击。spring-doc.cadn.net.cn

模式比较

当多个模式与 URL 匹配时,必须比较它们才能找到最佳匹配。这已经完成了 跟PathPattern.SPECIFICITY_COMPARATOR,它会查找更具体的模式。spring-doc.cadn.net.cn

对于每种模式,都会根据 URI 变量和通配符的数量计算一个分数, 其中 URI 变量的得分低于通配符。总分较低的模式 获胜。如果两个模式具有相同的分数,则选择较长的模式。spring-doc.cadn.net.cn

包罗万象的模式(例如,**{*varName}) 被排除在评分之外,并且始终是 而是最后排序。如果两个模式都是包罗万象的,则选择较长的模式。spring-doc.cadn.net.cn

耗材类型

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

Java
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}
Kotlin
@PostMapping("/pets", consumes = ["application/json"])
fun addPet(@RequestBody pet: Pet) {
    // ...
}

consumes 属性还支持否定表达式,例如!text/plain表示任何 内容类型以外的text/plain.spring-doc.cadn.net.cn

您可以声明共享的consumes属性。与大多数其他请求不同 但是,当在类级别使用时,映射属性是方法级别的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")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}
Kotlin
@GetMapping("/pets/{petId}", produces = ["application/json"])
@ResponseBody
fun getPet(@PathVariable petId: String): Pet {
    // ...
}

媒体类型可以指定字符集。支持否定表达式 — 例如!text/plain表示除text/plain.spring-doc.cadn.net.cn

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

MediaType为常用媒体类型提供常量——例如APPLICATION_JSON_VALUE,APPLICATION_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) {
    // ...
}
1 检查myHeader等于myValue.
HTTP 头, 选项

@GetMapping@RequestMapping(method=HttpMethod.GET)支持 HTTP HEAD 透明地用于请求映射目的。控制器方法无需更改。 响应包装器,应用于HttpHandler服务器适配器,确保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 WebFlux 支持使用组合注释进行请求映射。这些注释本身是元注释的@RequestMapping并组成以重新声明@RequestMapping具有更窄、更具体用途的属性。spring-doc.cadn.net.cn

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

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

显式注册

您可以以编程方式注册 Handler 方法,该方法可用于动态 注册或高级情况,例如同一处理程序的不同实例 在不同的 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.4.3. 处理程序方法

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

方法参数

下表显示了支持的控制器方法参数。spring-doc.cadn.net.cn

响应式类型(Reactor、RxJava 或其他)是 在需要阻止 I/O(例如,读取请求正文)的参数上支持 被解决。这在“描述”列中标记。不预期响应类型 在不需要阻止的参数上。spring-doc.cadn.net.cn

JDK 1.8 的java.util.Optional支持作为方法参数与 具有required属性(例如,@RequestParam,@RequestHeader, 等)等,相当于required=false.spring-doc.cadn.net.cn

控制器方法参数 描述

ServerWebExchangespring-doc.cadn.net.cn

访问完整的ServerWebExchange— HTTP 请求和响应的容器, 请求和会话属性,checkNotModified方法等。spring-doc.cadn.net.cn

ServerHttpRequest,ServerHttpResponsespring-doc.cadn.net.cn

访问 HTTP 请求或响应。spring-doc.cadn.net.cn

WebSessionspring-doc.cadn.net.cn

访问会话。这不会强制启动新会话,除非属性 被添加。支持响应式类型。spring-doc.cadn.net.cn

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

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

org.springframework.http.HttpMethodspring-doc.cadn.net.cn

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

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

当前请求区域设置,由最具体的LocaleResolver可用 — 在 effect,则配置的LocaleResolver/LocaleContextResolver.spring-doc.cadn.net.cn

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

与当前请求关联的时区,由LocaleContextResolver.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

用于访问查询参数。参数值转换为声明的方法参数 类型。看@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 请求正文。正文内容转换为声明的方法 参数类型,使用HttpMessageReader实例。支持响应式类型。 看@RequestBody.spring-doc.cadn.net.cn

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

用于访问请求标头和正文。身体被转换成HttpMessageReader实例。 支持响应式类型。看HttpEntity.spring-doc.cadn.net.cn

@RequestPartspring-doc.cadn.net.cn

要访问multipart/form-data请求。支持响应式类型。 请参阅多部分内容多部分数据spring-doc.cadn.net.cn

java.util.Map,org.springframework.ui.Modelorg.springframework.ui.ModelMap.spring-doc.cadn.net.cn

用于访问 HTML 控制器中使用的模型,并公开给模板作为 视图渲染的一部分。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论点。一ErrorsBindingResult参数必须声明 紧接在已验证的方法参数之后。spring-doc.cadn.net.cn

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

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

UriComponentsBuilderspring-doc.cadn.net.cn

用于准备相对于当前请求的主机、端口、方案和 上下文路径。请参阅 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

如果方法参数与上述任何参数不匹配,则默认情况下,它被解析为 一个@RequestParam如果它是简单类型,由 BeanUtils#isSimpleProperty 确定, 或作为@ModelAttribute否则。spring-doc.cadn.net.cn

返回值

下表显示了支持的控制器方法返回值。请注意,响应式 Reactor、RxJava 或其他库中的类型是 通常支持所有返回值。spring-doc.cadn.net.cn

控制器方法返回值 描述

@ResponseBodyspring-doc.cadn.net.cn

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

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

返回值指定完整的响应,包括 HTTP 标头,并且正文被编码 通过HttpMessageWriter实例并写入响应。 看ResponseEntity.spring-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

要添加到隐式模型的属性,其中视图名称是隐式确定的 基于请求路径。spring-doc.cadn.net.cn

@ModelAttributespring-doc.cadn.net.cn

要添加到模型的属性,视图名称隐式确定 在请求路径上。spring-doc.cadn.net.cn

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

Renderingspring-doc.cadn.net.cn

用于模型和视图呈现方案的 API。spring-doc.cadn.net.cn

voidspring-doc.cadn.net.cn

具有void,可能是异步的(例如,Mono<Void>)、返回类型(或null返回 value)如果它还具有ServerHttpResponse, 一个ServerWebExchange参数,或@ResponseStatus注解。同样如此 如果控制器已进行正 ETag 或lastModified时间戳检查。 有关详细信息,请参阅控制器spring-doc.cadn.net.cn

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

Flux<ServerSentEvent>,Observable<ServerSentEvent>或其他反应型spring-doc.cadn.net.cn

发出服务器发送的事件。这ServerSentEvent当只有数据需要时,可以省略 wrapper 要写(但是,text/event-stream必须在映射中请求或声明 通过produces属性)。spring-doc.cadn.net.cn

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

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

类型转换

一些带注释的控制器方法参数表示基于字符串的请求输入(例如,@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

矩阵变量

RFC 3986 讨论了 路径段。在 Spring WebFlux 中,我们根据 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

与 Spring MVC 不同,在 WebFlux 中,URL 中矩阵变量的存在与否确实如此 不影响请求映射。换句话说,您不需要使用 URI 变量 以掩盖可变内容。也就是说,如果你想从 controller 方法,需要在路径段中添加一个 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
@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]
}
@RequestParam

您可以使用@RequestParam注释,将查询参数绑定到方法参数 控制器。以下代码片段显示了用法: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.
Kotlin
import org.springframework.ui.set

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

    // ...

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

    // ...
}
1 @RequestParam.
Servlet API“请求参数”概念将查询参数、形式 数据,并多部分合二为一。但是,在 WebFlux 中,每个都可以通过ServerWebExchange.而@RequestParam绑定到查询参数,您可以使用 数据绑定,将查询参数、表单数据和多部分应用于命令对象

使用@RequestParam注释是默认必需的,但 您可以通过设置@RequestParamfalse或者通过使用java.util.Optional包装纸。spring-doc.cadn.net.cn

如果目标方法参数类型不是String.请参阅类型转换spring-doc.cadn.net.cn

@RequestParam注释在Map<String, String>MultiValueMap<String, String>参数,则映射将填充所有查询参数。spring-doc.cadn.net.cn

请注意,使用@RequestParam是可选的——例如,设置其属性。由 default,任何简单值类型的参数(由 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 获取 cookie 值。
Kotlin
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) { (1)
    //...
}
1 获取 cookie 值。

如果目标方法参数类型不是String.请参阅类型转换spring-doc.cadn.net.cn

@ModelAttribute

您可以使用@ModelAttribute方法参数上的注释以从 模型或实例化它(如果不存在)。model 属性也覆盖了 名称与字段名称匹配的查询参数和表单字段的值。这是 称为数据绑定,它使您不必处理解析和 转换单个查询参数和表单字段。以下示例绑定Pet:spring-doc.cadn.net.cn

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

Pet实例的解析方法如下:spring-doc.cadn.net.cn

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

数据绑定可能会导致错误。默认情况下,一个WebExchangeBindException被抬高,但是, 要检查控制器方法中的此类错误,您可以添加一个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.
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
1 添加一个BindingResult.

您可以通过添加javax.validation.Valid注释或 Spring 的@Validated注释(另见 Bean 验证Spring 验证)。以下示例使用@Valid注解: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 @Valid在模型属性参数上。
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
    if (result.hasErrors()) {
        return "petForm"
    }
    // ...
}
1 @Valid在模型属性参数上。

与 Spring MVC 不同,Spring WebFlux 支持模型中的响应式类型——例如Mono<Account>io.reactivex.Single<Account>.您可以声明一个@ModelAttribute论点 无论是否带有响应式包装器,它都会被相应地解析, 如有必要,设置为实际值。但是,请注意,要使用BindingResult参数,则必须声明@ModelAttribute没有响应式的参数 类型 wrapper,如前所示。或者,您可以通过 reactive 类型,如以下示例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
    return petMono
        .flatMap(pet -> {
            // ...
        })
        .onErrorResume(ex -> {
            // ...
        });
}
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") petMono: Mono<Pet>): Mono<String> {
    return petMono
            .flatMap { pet ->
                // ...
            }
            .onErrorResume{ ex ->
                // ...
            }
}

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

@SessionAttributes

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

请考虑以下示例: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,被添加到模型中,它会自动提升并保存在WebSession. 它一直留在那里,直到另一个控制器方法使用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) { (2)
        if (errors.hasErrors()) {
            // ...
        }
            status.setComplete();
            // ...
        }
    }
}
1 使用@SessionAttributes注解。
2 使用SessionStatus变量。
Kotlin
@Controller
@SessionAttributes("pet") (1)
class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String { (2)
        if (errors.hasErrors()) {
            // ...
        }
        status.setComplete()
        // ...
    }
}
1 使用@SessionAttributes注解。
2 使用SessionStatus变量。
@SessionAttribute

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

Java
@GetMapping("/")
public String handle(@SessionAttribute User user) { (1)
    // ...
}
1 @SessionAttribute.
Kotlin
@GetMapping("/")
fun handle(@SessionAttribute user: User): String { (1)
    // ...
}
1 @SessionAttribute.

对于需要添加或删除会话属性的用例,请考虑注入WebSession进入控制器方法。spring-doc.cadn.net.cn

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

@RequestAttribute

类似于@SessionAttribute,您可以使用@RequestAttribute注释到 访问之前创建的预先存在的请求属性(例如,通过WebFilter), 如以下示例所示: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.
多部分内容

Multipart Data 中所述,ServerWebExchange提供对 Multipart 的访问 内容。在控制器中处理文件上传表单(例如,从浏览器)的最佳方式 是通过与命令对象的数据绑定, 如以下示例所示: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) {
        // ...
    }

}
Kotlin
class MyForm(
        val name: String,
        val file: MultipartFile)

@Controller
class FileUploadController {

    @PostMapping("/form")
    fun handleFormUpload(form: MyForm, errors: BindingResult): String {
        // ...
    }

}

您还可以在 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 ...

您可以使用@RequestPart,如以下示例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, (1)
        @RequestPart("file-data") FilePart file) { (2)
    // ...
}
1 @RequestPart以获取元数据。
2 @RequestPart以获取文件。
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata, (1)
        @RequestPart("file-data") FilePart file): String { (2)
    // ...
}
1 @RequestPart以获取元数据。
2 @RequestPart以获取文件。

反序列化原始部分内容(例如,反序列化为 JSON - 类似于@RequestBody), 您可以声明一个具体的目标Object而不是Part,如以下示例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { (1)
    // ...
}
1 @RequestPart以获取元数据。
Kotlin
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String { (1)
    // ...
}
1 @RequestPart以获取元数据。

您可以使用@RequestPart结合使用javax.validation.Valid或 Spring 的@Validated注释,这会导致应用标准 Bean 验证。验证 错误导致WebExchangeBindException这会导致 400 (BAD_REQUEST) 的响应。 异常包含一个BindingResult带有错误详细信息,也可以处理 在控制器方法中,使用异步包装器声明参数,然后使用 错误相关运算符:spring-doc.cadn.net.cn

Java
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
    // use one of the onError* operators...
}
Kotlin
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
    // ...
}

要将所有多部分数据作为MultiValueMap,您可以使用@RequestBody, 如以下示例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) { (1)
    // ...
}
1 @RequestBody.
Kotlin
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String { (1)
    // ...
}
1 @RequestBody.

要以流式方式按顺序访问多部分数据,您可以使用@RequestBodyFlux<Part>(或Flow<Part>在 Kotlin 中),如以下示例所示:spring-doc.cadn.net.cn

Java
@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) { (1)
    // ...
}
1 @RequestBody.
Kotlin
@PostMapping("/")
fun handle(@RequestBody parts: Flow<Part>): String { (1)
    // ...
}
1 @RequestBody.
@RequestBody

您可以使用@RequestBody注释,让请求正文读取并反序列化为Object通过 HttpMessageReader。 以下示例使用@RequestBody论点:spring-doc.cadn.net.cn

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

与 Spring MVC 不同,在 WebFlux 中,@RequestBodymethod 参数支持响应式类型以及完全非阻塞读取和(客户端到服务器)流式传输。spring-doc.cadn.net.cn

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

您可以使用 WebFlux ConfigHTTP 消息编解码器选项来配置或自定义消息阅读器。spring-doc.cadn.net.cn

您可以使用@RequestBody结合使用javax.validation.Valid或 Spring 的@Validated注释,这会导致应用标准 Bean 验证。 验证 错误会导致WebExchangeBindException,这会导致 400 (BAD_REQUEST) 响应。异常包含一个BindingResult带有错误详细信息,并且可以在controller 方法中处理,方法是使用异步包装器声明参数,然后使用 error相关运算符:spring-doc.cadn.net.cn

Java
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Mono<Account> account) {
    // use one of the onError* operators...
}
Kotlin
@PostMapping("/accounts")
fun handle(@Valid @RequestBody account: Mono<Account>) {
    // ...
}
HttpEntity

HttpEntity与使用@RequestBody但基于 container 对象,用于公开请求标头和正文。以下示例使用HttpEntity:spring-doc.cadn.net.cn

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

您可以使用@ResponseBody将返回序列化的方法上的注释 通过 HttpMessageWriter 发送到响应正文。以下内容 示例显示了如何执行此作: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支持响应式类型,这意味着您可以返回 Reactor 或 RxJava 类型,并将它们生成的异步值呈现到响应中。 有关其他详细信息,请参阅流式处理JSON 渲染spring-doc.cadn.net.cn

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

您可以使用 WebFlux ConfigHTTP 消息编解码器选项来 配置或自定义消息写入。spring-doc.cadn.net.cn

ResponseEntity

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: String = ...
    val etag: String = ...
    return ResponseEntity.ok().eTag(etag).build(body)
}

WebFlux 支持使用单值响应式类型来 生成ResponseEntity异步和/或单值和多值响应式类型 对于身体。这允许使用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

  • Mono<ResponseEntity<Mono<T>>>Mono<ResponseEntity<Flux<T>>>是另一个 可能的,尽管不太常见的替代方案。它们提供响应状态和标头 首先异步,然后是响应体,其次也是异步的。spring-doc.cadn.net.cn

Jackson JSON

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

JSON 视图

Spring WebFlux 为 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 {
        return 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 方法。如果需要激活多个视图,请使用复合界面。

1.4.4.Model

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

本节讨论@ModelAttribute方法,或上一个列表中的第二项。 控制器可以有任意数量的@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);
}
如果未显式指定名称,则根据类型选择默认名称 如 Javadoc 中所述Conventions. 始终可以使用重载的addAttributemethod 或 通过 name 属性@ModelAttribute(对于返回值)。

与 Spring MVC 不同,Spring WebFlux 在模型中显式支持响应式类型 (例如,Mono<Account>io.reactivex.Single<Account>).这样的异步模型 属性可以透明地解析(并更新模型)为其实际值 当时@RequestMapping调用,提供@ModelAttribute参数是 声明时没有包装器,如以下示例所示:spring-doc.cadn.net.cn

Java
@ModelAttribute
public void addAccount(@RequestParam String number) {
    Mono<Account> accountMono = accountRepository.findAccount(number);
    model.addAttribute("account", accountMono);
}

@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
    // ...
}
Kotlin
import org.springframework.ui.set

@ModelAttribute
fun addAccount(@RequestParam number: String) {
    val accountMono: Mono<Account> = accountRepository.findAccount(number)
    model["account"] = accountMono
}

@PostMapping("/accounts")
fun handle(@ModelAttribute account: Account, errors: BindingResult): String {
    // ...
}

此外,任何具有响应式类型包装器的模型属性都将解析为其 实际值(和模型更新)在视图渲染之前。spring-doc.cadn.net.cn

您还可以使用@ModelAttribute作为方法级注释@RequestMapping方法,在这种情况下,返回值@RequestMapping方法被解释为 model 属性。这通常不是必需的,因为它是 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.4.5.DataBinder

@Controller@ControllerAdvice类可以有@InitBinder方法,设置为 初始化WebDataBinder.反过来,这些用于:spring-doc.cadn.net.cn

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

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

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

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

@InitBinder方法支持许多相同的参数@RequestMapping方法 do,但@ModelAttribute(命令对象)参数。通常,它们被声明为 使用WebDataBinder参数,用于注册,以及void返回值。以下示例使用@InitBinder注解: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))
    }

    // ...
}

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

Java
@Controller
public class FormController {

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

    // ...
}
1 添加自定义格式化程序(DateFormatter,在本例中)。
Kotlin
@Controller
class FormController {

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

    // ...
}
1 添加自定义格式化程序(DateFormatter,在本例中)。
模型设计

在 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.4.6. 管理异常

@Controller并且@ControllerAdvice类可以有@ExceptionHandler处理来自控制器方法的异常的方法。以下内容 示例包括这样的处理程序方法:spring-doc.cadn.net.cn

Java
@Controller
public class SimpleController {

    // ...

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

    // ...

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

异常可以与正在传播的顶级异常(即直接IOException被抛出)或针对顶级包装器中的直接原因 异常(例如,一个IOException包裹在一个IllegalStateException).spring-doc.cadn.net.cn

对于匹配的异常类型,最好将目标异常声明为方法参数, 如前面的示例所示。或者,注释声明可以缩小 异常类型。我们通常建议在 参数签名,并在@ControllerAdvice以相应的顺序优先。 有关详细信息,请参阅 MVC 部分spring-doc.cadn.net.cn

@ExceptionHandler方法支持相同的方法参数和 返回值作为@RequestMapping方法,但请求正文除外- 和@ModelAttribute-related 方法参数。

@ExceptionHandlerSpring WebFlux 中的方法由HandlerAdapter@RequestMapping方法。看DispatcherHandler了解更多详情。spring-doc.cadn.net.cn

REST API 异常

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

请注意,Spring WebFlux 没有 Spring MVC 的等效项ResponseEntityExceptionHandler,因为 WebFlux 仅ResponseStatusException(或其子类),并且不需要将其转换为 HTTP 状态代码。

1.4.7. 控制器建议

通常,@ExceptionHandler,@InitBinder@ModelAttribute方法适用 在@Controllerclass(或类层次结构),其中声明了它们。如果你 希望此类方法更全局(跨控制器)应用,您可以在 类用@ControllerAdvice@RestControllerAdvice.spring-doc.cadn.net.cn

@ControllerAdvice注释为@Component,这意味着此类类可以是 通过组件扫描注册为 Spring bean。@RestControllerAdvice是带有注释的组合注释 两者兼而有之@ControllerAdvice@ResponseBody,这本质上意味着@ExceptionHandler方法通过消息转换呈现到响应正文 (与视图分辨率或模板渲染相比)。spring-doc.cadn.net.cn

启动时,的基础设施类@RequestMapping@ExceptionHandler方法检测带有@ControllerAdvice然后应用他们的 方法。全球@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])
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 {}

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

1.5. 功能端点

Spring WebFlux 包括 WebFlux.fn,这是一个轻量级函数式编程模型,其中函数 用于路由和处理请求,合约设计为不可变性。 它是基于注释的编程模型的替代方案,但在其他方面运行在 相同的响应式核心基础。spring-doc.cadn.net.cn

1.5.1. 概述

在 WebFlux.fn 中,HTTP 请求使用HandlerFunction:一个接受ServerRequest并返回延迟的ServerResponse(即Mono<ServerResponse>). 请求和响应对象都有不可变的合约,提供 JDK 8 友好 访问 HTTP 请求和响应。HandlerFunction相当于一个@RequestMapping方法 基于注释的编程模型。spring-doc.cadn.net.cn

传入请求被路由到带有RouterFunction:一个函数 需要ServerRequest并返回延迟的HandlerFunction(即Mono<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.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.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 Mono<ServerResponse> listPeople(ServerRequest request) {
        // ...
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) {
        // ...
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) {
        // ...
    }
}
Kotlin
val repository: PersonRepository = ...
val handler = PersonHandler(repository)

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


class PersonHandler(private val repository: PersonRepository) {

    // ...

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

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

    suspend fun getPerson(request: ServerRequest): ServerResponse {
        // ...
    }
}
1 使用协程路由器 DSL 创建路由器,也可以通过以下方式获得响应式替代方案router { }.

运行RouterFunction就是把它变成一个HttpHandler并安装它 通过内置服务器适配器之一:spring-doc.cadn.net.cn

大多数应用程序都可以通过 WebFlux Java 配置运行,请参阅运行服务器spring-doc.cadn.net.cn

1.5.2. 处理程序函数

ServerRequestServerResponse是不可变接口,提供 JDK 8 友好访问 HTTP 请求和响应。请求和响应都提供反应流背压针对正文流。请求正文用 Reactor 表示FluxMono. 响应正文用任何响应式流表示Publisher包括FluxMono. 有关更多信息,请参阅响应式库spring-doc.cadn.net.cn

服务器请求

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

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

Java
Mono<String> string = request.bodyToMono(String.class);
Kotlin
val string = request.awaitBody<String>()

以下示例将正文提取为Flux<Person>(或Flow<Person>在 Kotlin 中), 哪里Person对象从某种序列化形式解码,例如 JSON 或 XML:spring-doc.cadn.net.cn

Java
Flux<Person> people = request.bodyToFlux(Person.class);
Kotlin
val people = request.bodyToFlow<Person>()

前面的示例是使用更通用的ServerRequest.body(BodyExtractor), 它接受BodyExtractor功能策略界面。实用程序类BodyExtractors提供对多个实例的访问。例如,前面的示例可以 也写如下:spring-doc.cadn.net.cn

Java
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
Kotlin
    val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle()
    val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()

以下示例演示如何访问表单数据:spring-doc.cadn.net.cn

Java
Mono<MultiValueMap<String, String>> map = request.formData();
Kotlin
val map = request.awaitFormData()

以下示例演示如何以地图形式访问多部分数据:spring-doc.cadn.net.cn

Java
Mono<MultiValueMap<String, Part>> map = request.multipartData();
Kotlin
val map = request.awaitMultipartData()

以下示例演示如何以流式处理方式一次访问一个多部分:spring-doc.cadn.net.cn

Java
Flux<Part> parts = request.body(BodyExtractors.toParts());
Kotlin
val parts = request.body(BodyExtractors.toParts()).asFlow()
服务器响应

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

Java
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
Kotlin
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)

以下示例演示如何使用Locationheader 且无正文:spring-doc.cadn.net.cn

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

根据使用的编解码器,可以传递提示参数来自定义 正文是序列化或反序列化的。例如,要指定 Jackson JSON 视图,请执行以下作:spring-doc.cadn.net.cn

Java
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
Kotlin
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
处理程序类

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

Java
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().bodyValue("Hello World");
Kotlin
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("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 Mono<ServerResponse> listPeople(ServerRequest request) { (1)
        Flux<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) { (2)
        Mono<Person> person = request.bodyToMono(Person.class);
        return ok().build(repository.savePerson(person));
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) { (3)
        int personId = Integer.valueOf(request.pathVariable("id"));
        return repository.getPerson(personId)
            .flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}
1 listPeople是一个处理程序函数,它返回所有Person在存储库中找到的对象作为 JSON的。
2 createPerson是一个处理程序函数,用于存储新的Person包含在请求正文中。 请注意PersonRepository.savePerson(Person)返回Mono<Void>:一个空的Mono发出的 从请求中读取并存储此人时的完成信号。所以我们使用build(Publisher<Void>)方法在收到该完成信号时发送响应(即, 当Person已被保存)。
3 getPerson是一个处理程序函数,返回单个人,由id路径 变量。我们检索Person并创建 JSON 响应(如果是) 发现。如果找不到,我们使用switchIfEmpty(Mono<T>)返回 404 Not Found 响应。
Kotlin
class PersonHandler(private val repository: PersonRepository) {

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

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

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

    }
}
1 listPeople是一个处理程序函数,它返回所有Person在存储库中找到的对象作为 JSON的。
2 createPerson是一个处理程序函数,用于存储新的Person包含在请求正文中。 请注意PersonRepository.savePerson(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 Mono<ServerResponse> createPerson(ServerRequest request) {
        Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); (2)
        return ok().build(repository.savePerson(person));
    }

    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)

    // ...

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

    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.5.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().bodyValue("Hello World")).build();
Kotlin
val route = coRouter {
    GET("/hello-world", accept(TEXT_PLAIN)) {
        ServerResponse.ok().bodyValueAndAwait("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.reactive.function.server.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

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

val otherRoute: RouterFunction<ServerResponse> = coRouter {  }

val route = coRouter {
    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.在 WebFlux.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
val route = coRouter {
    "/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
val route = coRouter {
    "/person".nest {
        accept(APPLICATION_JSON).nest {
            GET("/{id}", handler::getPerson)
            GET(handler::listPeople)
            POST(handler::createPerson)
        }
    }
}

1.5.4. 运行服务器

如何在 HTTP 服务器中运行路由器功能?一个简单的选择是转换路由器 function 设置为HttpHandler通过使用以下选项之一:spring-doc.cadn.net.cn

然后,您可以使用返回的HttpHandler使用多个服务器适配器,请按照 HttpHandler 获取特定于服务器的说明。spring-doc.cadn.net.cn

Spring Boot 也使用一个更典型的选项是使用DispatcherHandler基于 WebFlux Config 的设置,该设置使用 Spring 配置来声明 处理请求所需的组件。WebFlux 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

  • ServerResponseResultHandler:处理调用HandlerFunction通过调用writeTo方法ServerResponse.spring-doc.cadn.net.cn

上述组件允许功能端点适合DispatcherHandler请求 处理生命周期,并且(可能)与带注释的控制器并行运行,如果 any 被声明。这也是 Spring Boot WebFlux 启用功能端点的方式 起动机。spring-doc.cadn.net.cn

以下示例显示了 WebFlux Java 配置(有关如何运行它,请参阅 DispatcherHandler):spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

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

    // ...

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // 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
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

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

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

    // ...

    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        // configure message conversion...
    }

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

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

1.5.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
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
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 支持通过专用的CorsWebFilter.

1.6. URI 链接

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

1.6.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.6.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.6.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.7. CORS

Spring WebFlux 允许您处理 CORS(跨域资源共享)。本节 描述了如何执行此作。spring-doc.cadn.net.cn

1.7.1. 简介

出于安全原因,浏览器禁止对当前源之外的资源进行 AJAX 调用。 例如,您的银行账户可能位于一个选项卡中,而 evil.com 位于另一个选项卡中。脚本 从 evil.com 应该无法使用 凭据——例如,从您的账户中取款!spring-doc.cadn.net.cn

跨域资源共享 (CORS) 是大多数浏览器实现的 W3C 规范,允许您指定 什么样的跨域请求被授权,而不是使用不太安全、更少 基于 IFRAME 或 JSONP 的强大解决方法。spring-doc.cadn.net.cn

1.7.2. 处理

CORS 规范区分了预检请求、简单请求和实际请求。 要了解 CORS 的工作原理,您可以阅读这篇文章,其中 许多其他,或有关更多详细信息,请参阅规范。spring-doc.cadn.net.cn

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

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

HandlerMapping可以使用基于 URL 模式的单独配置CorsConfiguration映射。在大多数情况下,应用程序 使用 WebFlux Java 配置声明此类映射,这会导致单个 全局地图传递给所有HandlerMapping实现。spring-doc.cadn.net.cn

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

组合全局和本地配置的规则通常是累加的,例如, 所有全局和所有本地原点。对于那些只能使用单个值的属性 accepted,例如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 Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    suspend fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    suspend 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在类级别也受支持,并被所有方法继承。 以下示例指定某个域,并将maxAge到一个小时:spring-doc.cadn.net.cn

Java
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}
Kotlin
@CrossOrigin("https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {

    @GetMapping("/{id}")
    suspend fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    suspend fun remove(@PathVariable id: Long) {
        // ...
    }
}

您可以使用@CrossOrigin在类和方法级别, 如以下示例所示:spring-doc.cadn.net.cn

Java
@CrossOrigin(maxAge = 3600) (1)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("https://domain2.com") (2)
    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}
1 @CrossOrigin在班级层面。
2 @CrossOrigin在方法层面。
Kotlin
@CrossOrigin(maxAge = 3600) (1)
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin("https://domain2.com") (2)
    @GetMapping("/{id}")
    suspend fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    suspend fun remove(@PathVariable id: Long) {
        // ...
    }
}
1 @CrossOrigin在班级层面。
2 @CrossOrigin在方法层面。

1.7.5. 全局配置

除了细粒度的控制器方法级配置之外,您可能还希望 还定义一些全局 CORS 配置。您可以设置基于 URLCorsConfiguration在任何HandlerMapping.但是,大多数应用程序都使用 WebFlux Java 配置来做到这一点。spring-doc.cadn.net.cn

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

allowedCredentials默认情况下不启用,因为这会建立信任级别 暴露敏感的用户特定信息(例如 cookie 和 CSRF Tokens),以及 应仅在适当的情况下使用。启用时allowOrigins必须是 设置为一个或多个特定域(但不是特殊值)或 这"*"allowOriginPatterns属性可用于匹配一组动态源。spring-doc.cadn.net.cn

maxAge设置为 30 分钟。spring-doc.cadn.net.cn

要在 WebFlux Java 配置中启用 CORS,您可以使用CorsRegistry回调 如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @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
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

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

1.7.6. CORSWebFilter

您可以通过内置的CorsWebFilter,这是一个 与功能端点配合良好。spring-doc.cadn.net.cn

如果您尝试使用CorsFilter使用 Spring Security,请记住 Spring 安全性内置了对 科斯。

要配置过滤器,您可以声明CorsWebFilterbean 并传递一个CorsConfigurationSource到其构造函数,如以下示例所示:spring-doc.cadn.net.cn

Java
@Bean
CorsWebFilter corsFilter() {

    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);

    return new CorsWebFilter(source);
}
Kotlin
@Bean
fun corsFilter(): CorsWebFilter {

    val config = CorsConfiguration()

    // Possibly...
    // config.applyPermitDefaultValues()

    config.allowCredentials = true
    config.addAllowedOrigin("https://domain1.com")
    config.addAllowedHeader("*")
    config.addAllowedMethod("*")

    val source = UrlBasedCorsConfigurationSource().apply {
        registerCorsConfiguration("/**", config)
    }
    return CorsWebFilter(source)
}

1.8. 网络安全

Spring Security 项目提供支持 用于保护 Web 应用程序免受恶意攻击。查看 Spring Security性 参考文档,包括:spring-doc.cadn.net.cn

1.9. 查看技术

在 Spring WebFlux 中使用视图技术是可插拔的。无论您决定使用 Thymeleaf、FreeMarker 或其他一些视图技术,主要是配置更改的问题。本章介绍与 Spring 集成的视图技术WebFlux。我们假设您已经熟悉视图分辨率spring-doc.cadn.net.cn

1.9.1. 胸腺叶

Thymeleaf 是一个强调自然 HTML 的现代服务器端 Java 模板引擎 可以通过双击在浏览器中预览的模板,这非常 有助于独立处理 UI 模板(例如,由设计人员)而无需 正在运行的服务器。Thymeleaf 提供了一组广泛的功能,并且正在积极开发中 并维护。如需更完整的介绍,请参阅 Thymeleaf 项目主页。spring-doc.cadn.net.cn

Thymeleaf 与 Spring WebFlux 的集成由 Thymeleaf 项目管理。这 配置涉及一些 bean 声明,例如SpringResourceTemplateResolver,SpringWebFluxTemplateEngineThymeleafReactiveViewResolver.有关更多详细信息,请参阅 Thymeleaf+Spring 和 WebFlux 集成公告spring-doc.cadn.net.cn

1.9.2. 自由标记

Apache FreeMarker 是一个模板引擎,用于生成任何 从 HTML 到电子邮件等的文本输出。Spring 框架内置了 将 Spring WebFlux 与 FreeMarker 模板一起使用的集成。spring-doc.cadn.net.cn

查看配置

以下示例展示了如何将 FreeMarker 配置为视图技术:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();
    }

    // Configure FreeMarker...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates/freemarker");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()
    }

    // Configure FreeMarker...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("classpath:/templates/freemarker")
    }
}

您的模板需要存储在FreeMarkerConfigurer, 如前面的示例所示。给定上述配置,如果您的控制器返回视图名称,welcome,解析器会查找classpath:/templates/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

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // ...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        Map<String, Object> variables = new HashMap<>();
        variables.put("xml_escape", new XmlEscape());

        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates");
        configurer.setFreemarkerVariables(variables);
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    // ...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("classpath:/templates")
        setFreemarkerVariables(mapOf("xml_escape" to XmlEscape()))
    }
}

有关设置和变量的详细信息,请参阅 FreeMarker 文档,因为它们适用于 这Configuration对象。spring-doc.cadn.net.cn

表单处理

Spring 提供了一个用于 JSP 的标签库,其中包含一个<spring:bind/>元素。此元素主要允许表单显示来自 form-backing 对象,并显示来自Validator在 Web 或业务层。Spring 也支持 FreeMarker 中的相同功能, 具有用于生成表单输入元素本身的额外便利宏。spring-doc.cadn.net.cn

绑定宏

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

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

有关绑定支持的其他详细信息,请参阅简单 Spring MVC 的绑定。spring-doc.cadn.net.cn

表单宏

有关 Spring 对 FreeMarker 模板的表单宏支持的详细信息,请参阅以下内容 Spring MVC 文档的各个部分。spring-doc.cadn.net.cn

1.9.3. 脚本视图

Spring Framework 有一个内置的集成,用于将 Spring WebFlux 与任何 可以在 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
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @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
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.scriptTemplate()
    }

    @Bean
    fun configurer() = ScriptTemplateConfigurer().apply {
        engineName = "nashorn"
        setScripts("mustache.js")
        renderObject = "Mustache"
        renderFunction = "render"
    }
}

render函数使用以下参数调用:spring-doc.cadn.net.cn

Mustache.render()与此签名原生兼容,因此您可以直接调用它。spring-doc.cadn.net.cn

如果您的模板技术需要一些自定义,您可以提供一个脚本来实现自定义渲染函数。例如,Handlerbars 需要在使用模板之前编译模板,并且需要填充才能模拟一些浏览器工具在服务器端脚本引擎中不可用。以下示例展示了如何设置自定义渲染函数:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @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
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    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使用非线程安全时需要具有模板库的脚本引擎,例如在 Nashorn 上运行的 Handlebars 或React。在这种情况下,由于此错误,需要 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.9.4. JSON和 XML

出于内容协商的目的,能够交替在使用 HTML 模板或其他格式(例如 JSON 或 XML)渲染模型之间,取决于客户端请求的内容类型。为了支持这样做,Spring WebFlux提供了HttpMessageWriterView,您可以使用它来插入任何可用的编解码器spring-webJackson2JsonEncoder,Jackson2SmileEncoder, 或Jaxb2XmlEncoder.spring-doc.cadn.net.cn

与其他视图技术不同,HttpMessageWriterView不需要ViewResolver而是配置为默认视图。 您可以 配置一个或多个此类默认视图,将不同的HttpMessageWriter实例 或Encoder实例。 与请求的内容类型匹配的在运行时使用。spring-doc.cadn.net.cn

在大多数情况下,一个模型包含多个属性。要确定要序列化哪一个, 您可以配置HttpMessageWriterView替换为要用于的 Model 属性的名称 渲染。如果模型仅包含一个属性,则使用该属性。spring-doc.cadn.net.cn

1.10. HTTP 缓存

HTTP 缓存可以显着提高 Web 应用程序的性能。HTTP 缓存围绕Cache-Controlresponse 标头和后续条件请求标头,例如Last-ModifiedETag.Cache-Control建议私有(例如浏览器)和公共(例如代理)缓存如何缓存和重用响应。 一ETag标头发出条件请求,可能导致没有正文的 304 (NOT_MODIFIED),如果内容没有改变。ETag可以看作是更复杂的继任者 这Last-Modified页眉。spring-doc.cadn.net.cn

本节介绍 Spring WebFlux 中可用的 HTTP 缓存相关选项。spring-doc.cadn.net.cn

1.10.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()

1.10.2. 控制器

控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为lastModifiedETag需要先计算资源的值,然后才能对其进行比较与条件请求标头。控制器可以添加ETagCache-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(ServerWebExchange exchange, Model model) {

    long eTag = ... (1)

    if (exchange.checkNotModified(eTag)) {
        return null; (2)
    }

    model.addAttribute(...); (3)
    return "myViewName";
}
1 特定于应用程序的计算。
2 响应已设置为 304 (NOT_MODIFIED)。没有进一步处理。
3 继续处理请求。
Kotlin
@RequestMapping
fun myHandleMethod(exchange: ServerWebExchange, model: Model): String? {

    val eTag: Long = ... (1)

    if (exchange.checkNotModified(eTag)) {
        return null(2)
    }

    model.addAttribute(...) (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.10.3. 静态资源

您应该使用Cache-Control和条件响应标头 以获得最佳性能。请参阅有关配置静态资源的部分。spring-doc.cadn.net.cn

1.11. WebFlux 配置

WebFlux Java 配置声明需要处理的组件 请求,并提供了一个 API 自定义配置。这意味着您不需要了解底层 由 Java 配置创建的 bean。但是,如果你想了解它们, 您可以在WebFluxConfigurationSupport或阅读更多关于它们是什么的信息 在特殊Beans中spring-doc.cadn.net.cn

对于配置 API 中不可用的更高级自定义项,您可以 通过高级配置模式完全控制配置。spring-doc.cadn.net.cn

1.11.1. 启用 WebFlux 配置

您可以使用@EnableWebFlux注释,如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig {
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig

前面的示例注册了许多 Spring WebFlux 基础设施 bean 并适应依赖项在类路径上可用 — 用于 JSON、XML 等。spring-doc.cadn.net.cn

1.11.2. WebFlux 配置 API

在 Java 配置中,您可以实现WebFluxConfigurer接口 如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // Implement configuration methods...
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    // Implement configuration methods...
}

1.11.3. 转换、格式化

默认情况下,将安装各种数字和日期类型的格式化程序,并支持 用于定制@NumberFormat@DateTimeFormat在田野上。spring-doc.cadn.net.cn

要在 Java 配置中注册自定义格式化程序和转换器,请使用以下命令:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addFormatters(registry: FormatterRegistry) {
        // ...
    }
}

默认情况下,Spring WebFlux 在解析和格式化日期时会考虑请求 Locale 值。这适用于日期表示为带有“输入”形式的字符串的表单 领域。但是,对于“日期”和“时间”表单字段,浏览器使用定义的固定格式 在 HTML 规范中。对于这种情况,可以按如下方式自定义日期和时间格式:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addFormatters(registry: FormatterRegistry) {
        val registrar = DateTimeFormatterRegistrar()
        registrar.setUseIsoFormat(true)
        registrar.registerFormatters(registry)
    }
}
FormatterRegistrarSPIFormattingConversionServiceFactoryBean有关何时 用FormatterRegistrar实现。

1.11.4. 验证

默认情况下,如果存在 Bean Validation 在类路径(例如,Hibernate Validator)上,该LocalValidatorFactoryBean注册为全局验证器,用于@Valid@Validated@Controllermethod 参数。spring-doc.cadn.net.cn

在 Java 配置中,您可以自定义全局Validator实例 如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public Validator getValidator() {
        // ...
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun getValidator(): Validator {
        // ...
    }

}

请注意,您也可以注册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. 内容类型解析器

您可以配置 Spring WebFlux 如何确定请求的媒体类型@Controller实例。默认情况下,只有Accept标头被选中, 但您也可以启用基于查询参数的策略。spring-doc.cadn.net.cn

以下示例演示如何自定义请求的内容类型解析:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureContentTypeResolver(builder: RequestedContentTypeResolverBuilder) {
        // ...
    }
}

1.11.6. HTTP 消息编解码器

以下示例演示如何自定义请求和响应正文的读取和写入方式:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().maxInMemorySize(512 * 1024);
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        // ...
    }
}

ServerCodecConfigurer提供了一组默认的读取器和写入器。您可以使用它来添加 更多的读写器,自定义默认的,或完全替换默认的。spring-doc.cadn.net.cn

对于 Jackson JSON 和 XML,请考虑使用Jackson2ObjectMapperBuilder, 它使用以下属性自定义 Jackson 的默认属性:spring-doc.cadn.net.cn

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

1.11.7. 视图解析器

以下示例演示如何配置视图分辨率:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // ...
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        // ...
    }
}

ViewResolverRegistry具有 Spring Framework 使用的视图技术的快捷方式 集成。以下示例使用 FreeMarker(这也需要配置 底层 FreeMarker 视图技术):spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();
    }

    // Configure Freemarker...

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("classpath:/templates");
        return configurer;
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()
    }

    // Configure Freemarker...

    @Bean
    fun freeMarkerConfigurer() = FreeMarkerConfigurer().apply {
        setTemplateLoaderPath("classpath:/templates")
    }
}

您还可以插入任何ViewResolver实现,如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        ViewResolver resolver = ... ;
        registry.viewResolver(resolver);
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        val resolver: ViewResolver = ...
        registry.viewResolver(resolver
    }
}

支持内容协商和呈现其他格式 通过视图解析(除了 HTML),您可以配置一个或多个基于 在HttpMessageWriterView实现,它接受来自spring-web.以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();

        Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
        registry.defaultViews(new HttpMessageWriterView(encoder));
    }

    // ...
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {


    override fun configureViewResolvers(registry: ViewResolverRegistry) {
        registry.freeMarker()

        val encoder = Jackson2JsonEncoder()
        registry.defaultViews(HttpMessageWriterView(encoder))
    }

    // ...
}

有关与 Spring WebFlux 集成的视图技术的更多信息,请参阅视图技术spring-doc.cadn.net.cn

1.11.8. 静态资源

此选项提供了一种方便的方式来提供列表中的静态资源Resource基于位置。spring-doc.cadn.net.cn

在下一个示例中,给定一个以/resources,相对路径为 用于查找和提供相对于/static在类路径上。资源 未来到期时间为一年,以确保最大限度地利用浏览器缓存 以及减少浏览器发出的 HTTP 请求。这Last-Modifiedheader 也是 评估,如果存在,则304状态代码。以下列表显示 示例:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public", "classpath:/static/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
    }
}

资源处理程序还支持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
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }

}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(VersionResourceResolver().addContentVersionStrategy("/**"))
    }

}

您可以使用ResourceUrlProvider重写 URL 并应用完整的解析器链和 转换器(例如,插入版本)。WebFlux 配置提供了一个ResourceUrlProvider这样就可以注射到别人身上。spring-doc.cadn.net.cn

与 Spring MVC 不同,目前在 WebFlux 中,没有办法透明地重写静态 资源 URL,因为没有可以使用非阻塞链的视图技术 旋转转换器和转换器。仅提供本地资源时,解决方法是使用ResourceUrlProvider直接(例如,通过自定义元素)和块。spring-doc.cadn.net.cn

请注意,当同时使用EncodedResourceResolver(例如,Gzip、Brotli 编码)和VersionedResourceResolver,它们必须按该顺序注册,以确保基于内容 版本始终根据未编码的文件可靠地计算。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.9. 路径匹配

您可以自定义与路径匹配相关的选项。有关各个选项的详细信息,请参阅PathMatchConfigurerjavadoc 的文档。 以下示例演示如何使用PathMatchConfigurer:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setUseCaseSensitiveMatch(true)
            .setUseTrailingSlashMatch(false)
            .addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController.class));
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    @Override
    fun configurePathMatch(configurer: PathMatchConfigurer) {
        configurer
            .setUseCaseSensitiveMatch(true)
            .setUseTrailingSlashMatch(false)
            .addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController::class.java))
    }
}

Spring WebFlux 依赖于名为RequestPath用于访问解码的路径段值,删除分号内容 (即路径或矩阵变量)。这意味着,与 Spring MVC 不同,您不需要指示 是否解码请求路径,也是否删除 路径匹配目的。spring-doc.cadn.net.cn

Spring WebFlux 也不支持后缀模式匹配,这与 Spring MVC 不同,在 Spring MVC 中,我们也建议远离依赖它。spring-doc.cadn.net.cn

1.11.10. WebSocket服务

WebFlux Java 配置声明了WebSocketHandlerAdapterbean 提供支持 WebSocket 处理程序的调用。这意味着剩下要做的就是为了处理 WebSocket 握手请求,只需将WebSocketHandler到 URL 通过SimpleUrlHandlerMapping.spring-doc.cadn.net.cn

在某些情况下,可能需要创建WebSocketHandlerAdapterbean 与 提供WebSocketService允许配置 WebSocket 服务器属性的服务。 例如:spring-doc.cadn.net.cn

Java
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public WebSocketService getWebSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}
Kotlin
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {

    @Override
    fun webSocketService(): WebSocketService {
        val strategy = TomcatRequestUpgradeStrategy().apply {
            setMaxSessionIdleTimeout(0L)
        }
        return HandshakeWebSocketService(strategy)
    }
}

1.11.11. 高级配置模式

@EnableWebFlux进口DelegatingWebFluxConfiguration那:spring-doc.cadn.net.cn

对于高级模式,您可以删除@EnableWebFlux并直接从DelegatingWebFluxConfiguration而不是实现WebFluxConfigurer, 如以下示例所示:spring-doc.cadn.net.cn

Java
@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {

    // ...
}
Kotlin
@Configuration
class WebConfig : DelegatingWebFluxConfiguration {

    // ...
}

您可以将现有方法保留在WebConfig,但您现在也可以覆盖 bean 声明 来自基类,并且仍然具有任意数量的其他WebMvcConfigurer实现 类路径。spring-doc.cadn.net.cn

1.12. HTTP/2

Reactor Netty、Tomcat、Jetty 和 Undertow 支持 HTTP/2。但是,有 与服务器配置相关的注意事项。有关更多详细信息,请参阅 HTTP/2 wiki 页面spring-doc.cadn.net.cn

2. 网络客户端

Spring WebFlux 包括一个用于执行 HTTP 请求的客户端。WebClient有一个 基于 Reactor 的功能性、流畅的 API,请参阅响应式库, 它支持异步逻辑的声明性组合,而无需处理 线程或并发。它是完全无阻塞的,它支持流媒体,并依赖于 同样用于编码和 在服务器端解码请求和响应内容。spring-doc.cadn.net.cn

WebClient需要一个 HTTP 客户端库来执行请求。有内置的 支持以下内容:spring-doc.cadn.net.cn

2.1. 配置

创建WebClient是通过静态工厂方法之一:spring-doc.cadn.net.cn

您还可以使用WebClient.builder()还有更多选项:spring-doc.cadn.net.cn

Java
WebClient client = WebClient.builder()
        .codecs(configurer -> ... )
        .build();
Kotlin
val webClient = WebClient.builder()
        .codecs { configurer -> ... }
        .build()

构建后,一个WebClient是不可变的。但是,您可以克隆它并构建一个 修改副本如下:spring-doc.cadn.net.cn

Java
WebClient client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build();

WebClient client2 = client1.mutate()
        .filter(filterC).filter(filterD).build();

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD
Kotlin
val client1 = WebClient.builder()
        .filter(filterA).filter(filterB).build()

val client2 = client1.mutate()
        .filter(filterC).filter(filterD).build()

// client1 has filterA, filterB

// client2 has filterA, filterB, filterC, filterD

2.1.1. MaxInMemory大小

编解码器对缓冲数据有限制 内存以避免应用程序内存问题。默认情况下,这些设置为 256KB。 如果这还不够,您将收到以下错误:spring-doc.cadn.net.cn

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer

若要更改默认编解码器的限制,请使用以下命令:spring-doc.cadn.net.cn

Java
WebClient webClient = WebClient.builder()
        .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
        .build();
Kotlin
val webClient = WebClient.builder()
        .codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024) }
        .build()

2.1.2. 反应器内蒂

要自定义 Reactor Netty 设置,请提供预配置的HttpClient:spring-doc.cadn.net.cn

Java
HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);

WebClient webClient = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();
Kotlin
val httpClient = HttpClient.create().secure { ... }

val webClient = WebClient.builder()
    .clientConnector(ReactorClientHttpConnector(httpClient))
    .build()
资源

默认情况下,HttpClient参与全球Reactor Netty资源reactor.netty.http.HttpResources,包括事件循环线程和连接池。 这是推荐的模式,因为固定的共享资源是事件循环的首选 并发。在此模式下,全局资源将保持活动状态,直到进程退出。spring-doc.cadn.net.cn

如果服务器与进程定时,则通常不需要显式 关闭。但是,如果服务器可以在进程内启动或停止(例如,Spring MVC 应用程序部署为 WAR),您可以声明类型为ReactorResourceFactoryglobalResources=true(默认值)以确保 Reactor Netty 全局资源在 Spring 时关闭ApplicationContext关闭, 如以下示例所示:spring-doc.cadn.net.cn

Java
@Bean
public ReactorResourceFactory reactorResourceFactory() {
    return new ReactorResourceFactory();
}
Kotlin
@Bean
fun reactorResourceFactory() = ReactorResourceFactory()

您也可以选择不参与全局 Reactor Netty 资源。然而 在此模式下,您有责任确保所有 Reactor Netty 客户端和服务器 实例使用共享资源,如以下示例所示:spring-doc.cadn.net.cn

Java
@Bean
public ReactorResourceFactory resourceFactory() {
    ReactorResourceFactory factory = new ReactorResourceFactory();
    factory.setUseGlobalResources(false); (1)
    return factory;
}

@Bean
public WebClient webClient() {

    Function<HttpClient, HttpClient> mapper = client -> {
        // Further customizations...
    };

    ClientHttpConnector connector =
            new ReactorClientHttpConnector(resourceFactory(), mapper); (2)

    return WebClient.builder().clientConnector(connector).build(); (3)
}
1 创建独立于全局资源的资源。
2 使用ReactorClientHttpConnector带有资源工厂的构造函数。
3 将连接器插入WebClient.Builder.
Kotlin
@Bean
fun resourceFactory() = ReactorResourceFactory().apply {
    isUseGlobalResources = false (1)
}

@Bean
fun webClient(): WebClient {

    val mapper: (HttpClient) -> HttpClient = {
        // Further customizations...
    }

    val connector = ReactorClientHttpConnector(resourceFactory(), mapper) (2)

    return WebClient.builder().clientConnector(connector).build() (3)
}
1 创建独立于全局资源的资源。
2 使用ReactorClientHttpConnector带有资源工厂的构造函数。
3 将连接器插入WebClient.Builder.
超时

要配置连接超时,请执行以下作:spring-doc.cadn.net.cn

Java
import io.netty.channel.ChannelOption;

HttpClient httpClient = HttpClient.create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

WebClient webClient = WebClient.builder()
        .clientConnector(new ReactorClientHttpConnector(httpClient))
        .build();
Kotlin
import io.netty.channel.ChannelOption

val httpClient = HttpClient.create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);

val webClient = WebClient.builder()
        .clientConnector(ReactorClientHttpConnector(httpClient))
        .build();

要配置读取或写入超时,请执行以下作:spring-doc.cadn.net.cn

Java
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;

HttpClient httpClient = HttpClient.create()
        .doOnConnected(conn -> conn
                .addHandlerLast(new ReadTimeoutHandler(10))
                .addHandlerLast(new WriteTimeoutHandler(10)));

// Create WebClient...
Kotlin
import io.netty.handler.timeout.ReadTimeoutHandler
import io.netty.handler.timeout.WriteTimeoutHandler

val httpClient = HttpClient.create()
        .doOnConnected { conn -> conn
                .addHandlerLast(ReadTimeoutHandler(10))
                .addHandlerLast(WriteTimeoutHandler(10))
        }

// Create WebClient...

要为所有请求配置响应超时,请执行以下作:spring-doc.cadn.net.cn

Java
HttpClient httpClient = HttpClient.create()
        .responseTimeout(Duration.ofSeconds(2));

// Create WebClient...
Kotlin
val httpClient = HttpClient.create()
        .responseTimeout(Duration.ofSeconds(2));

// Create WebClient...

要为特定请求配置响应超时,请执行以下作:spring-doc.cadn.net.cn

Java
WebClient.create().get()
        .uri("https://example.org/path")
        .httpRequest(httpRequest -> {
            HttpClientRequest reactorRequest = httpRequest.getNativeRequest();
            reactorRequest.responseTimeout(Duration.ofSeconds(2));
        })
        .retrieve()
        .bodyToMono(String.class);
Kotlin
WebClient.create().get()
        .uri("https://example.org/path")
        .httpRequest { httpRequest: ClientHttpRequest ->
            val reactorRequest = httpRequest.getNativeRequest<HttpClientRequest>()
            reactorRequest.responseTimeout(Duration.ofSeconds(2))
        }
        .retrieve()
        .bodyToMono(String::class.java)

2.1.3. Jetty

以下示例显示如何自定义 JettyHttpClient设置:spring-doc.cadn.net.cn

Java
HttpClient httpClient = new HttpClient();
httpClient.setCookieStore(...);

WebClient webClient = WebClient.builder()
        .clientConnector(new JettyClientHttpConnector(httpClient))
        .build();
Kotlin
val httpClient = HttpClient()
httpClient.cookieStore = ...

val webClient = WebClient.builder()
        .clientConnector(JettyClientHttpConnector(httpClient))
        .build();

默认情况下,HttpClient创建自己的资源 (Executor,ByteBufferPool,Scheduler), 在进程退出之前保持活动状态,或者stop()被称为。spring-doc.cadn.net.cn

您可以在 Jetty 客户端(和服务器)的多个实例之间共享资源,并且 确保在 SpringApplicationContext由 声明类型为JettyResourceFactory,如以下示例所示 显示:spring-doc.cadn.net.cn

Java
@Bean
public JettyResourceFactory resourceFactory() {
    return new JettyResourceFactory();
}

@Bean
public WebClient webClient() {

    HttpClient httpClient = new HttpClient();
    // Further customizations...

    ClientHttpConnector connector =
            new JettyClientHttpConnector(httpClient, resourceFactory()); (1)

    return WebClient.builder().clientConnector(connector).build(); (2)
}
1 使用JettyClientHttpConnector带有资源工厂的构造函数。
2 将连接器插入WebClient.Builder.
Kotlin
@Bean
fun resourceFactory() = JettyResourceFactory()

@Bean
fun webClient(): WebClient {

    val httpClient = HttpClient()
    // Further customizations...

    val connector = JettyClientHttpConnector(httpClient, resourceFactory()) (1)

    return WebClient.builder().clientConnector(connector).build() (2)
}
1 使用JettyClientHttpConnector带有资源工厂的构造函数。
2 将连接器插入WebClient.Builder.

2.1.4. Http组件

以下示例演示如何自定义 Apache HttpComponentsHttpClient设置:spring-doc.cadn.net.cn

Java
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(...);
CloseableHttpAsyncClient client = clientBuilder.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);

WebClient webClient = WebClient.builder().clientConnector(connector).build();
Kotlin
val client = HttpAsyncClients.custom().apply {
    setDefaultRequestConfig(...)
}.build()
val connector = HttpComponentsClientHttpConnector(client)
val webClient = WebClient.builder().clientConnector(connector).build()

2.2.retrieve()

retrieve()方法可用于声明如何提取响应。例如:spring-doc.cadn.net.cn

Java
WebClient client = WebClient.create("https://example.org");

Mono<ResponseEntity<Person>> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .toEntity(Person.class);
Kotlin
val client = WebClient.create("https://example.org")

val result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .toEntity<Person>().awaitSingle()

或者只获得正文:spring-doc.cadn.net.cn

Java
WebClient client = WebClient.create("https://example.org");

Mono<Person> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .bodyToMono(Person.class);
Kotlin
val client = WebClient.create("https://example.org")

val result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .awaitBody<Person>()

要获取解码对象流,请执行以下作:spring-doc.cadn.net.cn

Java
Flux<Quote> result = client.get()
        .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
        .retrieve()
        .bodyToFlux(Quote.class);
Kotlin
val result = client.get()
        .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
        .retrieve()
        .bodyToFlow<Quote>()

默认情况下,4xx 或 5xx 响应会导致WebClientResponseException包括 特定 HTTP 状态代码的子类。自定义错误处理 响应, 使用onStatus处理程序如下所示:spring-doc.cadn.net.cn

Java
Mono<Person> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, response -> ...)
        .onStatus(HttpStatus::is5xxServerError, response -> ...)
        .bodyToMono(Person.class);
Kotlin
val result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError) { ... }
        .onStatus(HttpStatus::is5xxServerError) { ... }
        .awaitBody<Person>()

2.3. 交换

exchangeToMono()exchangeToFlux()方法(或awaitExchange { }exchangeToFlow { }在 Kotlin 中) 对于需要更多控制的更高级情况很有用,例如以不同的方式解码响应 根据响应状态:spring-doc.cadn.net.cn

Java
Mono<Person> entityMono = client.get()
        .uri("/persons/1")
        .accept(MediaType.APPLICATION_JSON)
        .exchangeToMono(response -> {
            if (response.statusCode().equals(HttpStatus.OK)) {
                return response.bodyToMono(Person.class);
            }
            else {
                // Turn to error
                return response.createException().flatMap(Mono::error);
            }
        });
Kotlin
val entity = client.get()
  .uri("/persons/1")
  .accept(MediaType.APPLICATION_JSON)
  .awaitExchange {
        if (response.statusCode() == HttpStatus.OK) {
             return response.awaitBody<Person>()
        }
        else {
             throw response.createExceptionAndAwait()
        }
  }

使用上述时,返回后MonoFluxcompletes,响应正文 被选中,如果未消耗,则将其释放以防止内存和连接泄漏。 因此,响应无法进一步在下游解码。这取决于提供的 函数来声明如何根据需要解码响应。spring-doc.cadn.net.cn

2.4. 请求正文

请求正文可以从由ReactiveAdapterRegistry, 喜欢Mono或 Kotlin 协程Deferred如以下示例所示:spring-doc.cadn.net.cn

Java
Mono<Person> personMono = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body(personMono, Person.class)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val personDeferred: Deferred<Person> = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body<Person>(personDeferred)
        .retrieve()
        .awaitBody<Unit>()

还可以对对象流进行编码,如以下示例所示:spring-doc.cadn.net.cn

Java
Flux<Person> personFlux = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_STREAM_JSON)
        .body(personFlux, Person.class)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val people: Flow<Person> = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .body(people)
        .retrieve()
        .awaitBody<Unit>()

或者,如果您有实际值,则可以使用bodyValue快捷方式, 如以下示例所示:spring-doc.cadn.net.cn

Java
Person person = ... ;

Mono<Void> result = client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(person)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val person: Person = ...

client.post()
        .uri("/persons/{id}", id)
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(person)
        .retrieve()
        .awaitBody<Unit>()

2.4.1. 表单数据

要发送表单数据,您可以提供MultiValueMap<String, String>作为正文。请注意,content 会自动设置为application/x-www-form-urlencoded通过FormHttpMessageWriter. 以下示例演示如何使用MultiValueMap<String, String>:spring-doc.cadn.net.cn

Java
MultiValueMap<String, String> formData = ... ;

Mono<Void> result = client.post()
        .uri("/path", id)
        .bodyValue(formData)
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val formData: MultiValueMap<String, String> = ...

client.post()
        .uri("/path", id)
        .bodyValue(formData)
        .retrieve()
        .awaitBody<Unit>()

您还可以使用BodyInserters,如以下示例所示:spring-doc.cadn.net.cn

Java
import static org.springframework.web.reactive.function.BodyInserters.*;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(fromFormData("k1", "v1").with("k2", "v2"))
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
import org.springframework.web.reactive.function.BodyInserters.*

client.post()
        .uri("/path", id)
        .body(fromFormData("k1", "v1").with("k2", "v2"))
        .retrieve()
        .awaitBody<Unit>()

2.4.2. 多部分数据

要发送多部分数据,您需要提供一个MultiValueMap<String, ?>其值为 也Object表示部件内容的实例或HttpEntity表示内容的实例和headers 的组件。MultipartBodyBuilder提供了一个方便的 API 来准备multipart 请求。以下示例展示了如何创建MultiValueMap<String, ?>:spring-doc.cadn.net.cn

Java
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart1", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
builder.part("myPart", part); // Part from a server request

MultiValueMap<String, HttpEntity<?>> parts = builder.build();
Kotlin
val builder = MultipartBodyBuilder().apply {
    part("fieldPart", "fieldValue")
    part("filePart1", FileSystemResource("...logo.png"))
    part("jsonPart", Person("Jason"))
    part("myPart", part) // Part from a server request
}

val parts = builder.build()

在大多数情况下,您不必指定Content-Type。内容类型是根据HttpMessageWriter选择序列化它或者,在Resource,基于文件扩展名。如有必要,您可以显式提供MediaType通过重载的 架构工人part方法。spring-doc.cadn.net.cn

一次MultiValueMap准备好了,最简单的方法就是把它传递给WebClient是 通过body方法,如以下示例所示:spring-doc.cadn.net.cn

Java
MultipartBodyBuilder builder = ...;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(builder.build())
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
val builder: MultipartBodyBuilder = ...

client.post()
        .uri("/path", id)
        .body(builder.build())
        .retrieve()
        .awaitBody<Unit>()

如果MultiValueMap包含至少一种非Stringvalue,这也可以 表示常规形式数据(即application/x-www-form-urlencoded),您不必 将Content-Typemultipart/form-data.使用MultipartBodyBuilder,这确保了HttpEntity包装纸。spring-doc.cadn.net.cn

作为替代方案MultipartBodyBuilder,还可以提供多部分内容, inline-style,通过内置的BodyInserters,如以下示例所示:spring-doc.cadn.net.cn

Java
import static org.springframework.web.reactive.function.BodyInserters.*;

Mono<Void> result = client.post()
        .uri("/path", id)
        .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
        .retrieve()
        .bodyToMono(Void.class);
Kotlin
import org.springframework.web.reactive.function.BodyInserters.*

client.post()
        .uri("/path", id)
        .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
        .retrieve()
        .awaitBody<Unit>()

2.5. 过滤器

您可以注册客户端过滤器 (ExchangeFilterFunction) 通过WebClient.Builder为了拦截和修改请求,如以下示例所示:spring-doc.cadn.net.cn

Java
WebClient client = WebClient.builder()
        .filter((request, next) -> {

            ClientRequest filtered = ClientRequest.from(request)
                    .header("foo", "bar")
                    .build();

            return next.exchange(filtered);
        })
        .build();
Kotlin
val client = WebClient.builder()
        .filter { request, next ->

            val filtered = ClientRequest.from(request)
                    .header("foo", "bar")
                    .build()

            next.exchange(filtered)
        }
        .build()

这可用于跨领域问题,例如身份验证。以下示例使用 通过静态工厂方法进行基本身份验证的过滤器:spring-doc.cadn.net.cn

Java
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;

WebClient client = WebClient.builder()
        .filter(basicAuthentication("user", "password"))
        .build();
Kotlin
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication

val client = WebClient.builder()
        .filter(basicAuthentication("user", "password"))
        .build()

可以通过更改现有的WebClient实例,结果 在新的WebClient不影响原始实例的实例。例如:spring-doc.cadn.net.cn

Java
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;

WebClient client = webClient.mutate()
        .filters(filterList -> {
            filterList.add(0, basicAuthentication("user", "password"));
        })
        .build();
Kotlin
val client = webClient.mutate()
        .filters { it.add(0, basicAuthentication("user", "password")) }
        .build()

WebClient是围绕过滤器链的薄立面,后跟一个ExchangeFunction.它提供了一个工作流程来发出请求、与更高级别之间进行编码 level 对象,它有助于确保始终使用响应内容。 当过滤器以某种方式处理响应时,必须格外小心,始终使用 其内容或以其他方式将其传播到下游的WebClient这将确保 一样。下面是一个过滤器,用于处理UNAUTHORIZED状态代码,但确保 任何响应内容,无论是否预期,都会被释放:spring-doc.cadn.net.cn

Java
public ExchangeFilterFunction renewTokenFilter() {
    return (request, next) -> next.exchange(request).flatMap(response -> {
        if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) {
            return response.releaseBody()
                    .then(renewToken())
                    .flatMap(token -> {
                        ClientRequest newRequest = ClientRequest.from(request).build();
                        return next.exchange(newRequest);
                    });
        } else {
            return Mono.just(response);
        }
    });
}
Kotlin
fun renewTokenFilter(): ExchangeFilterFunction? {
    return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction ->
        next.exchange(request!!).flatMap { response: ClientResponse ->
            if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) {
                return@flatMap response.releaseBody()
                        .then(renewToken())
                        .flatMap { token: String? ->
                            val newRequest = ClientRequest.from(request).build()
                            next.exchange(newRequest)
                        }
            } else {
                return@flatMap Mono.just(response)
            }
        }
    }
}

2.6. 属性

您可以向请求添加属性。如果您想传递信息,这很方便 通过过滤器链并影响给定请求的过滤器行为。 例如:spring-doc.cadn.net.cn

Java
WebClient client = WebClient.builder()
        .filter((request, next) -> {
            Optional<Object> usr = request.attribute("myAttribute");
            // ...
        })
        .build();

client.get().uri("https://example.org/")
        .attribute("myAttribute", "...")
        .retrieve()
        .bodyToMono(Void.class);

    }
Kotlin
val client = WebClient.builder()
        .filter { request, _ ->
            val usr = request.attributes()["myAttribute"];
            // ...
        }
        .build()

    client.get().uri("https://example.org/")
            .attribute("myAttribute", "...")
            .retrieve()
            .awaitBody<Unit>()

请注意,您可以配置defaultRequestcallback 全局调用WebClient.Builder级别,允许您将属性插入到所有请求中, 例如,可以在 Spring MVC 应用程序中使用它来填充 请求属性ThreadLocal数据。spring-doc.cadn.net.cn

2.7. 上下文

属性提供了一种将信息传递给过滤器的便捷方法 链,但它们只影响当前请求。如果要传递信息 传播到嵌套的其他请求,例如通过flatMap,或在之后执行, 例如通过concatMap,那么你需要使用 ReactorContext.spring-doc.cadn.net.cn

反应堆Context需要在反应链的末尾填充,以便 适用于所有作。例如:spring-doc.cadn.net.cn

Java
WebClient client = WebClient.builder()
        .filter((request, next) ->
                Mono.deferContextual(contextView -> {
                    String value = contextView.get("foo");
                    // ...
                }))
        .build();

client.get().uri("https://example.org/")
        .retrieve()
        .bodyToMono(String.class)
        .flatMap(body -> {
                // perform nested request (context propagates automatically)...
        })
        .contextWrite(context -> context.put("foo", ...));

2.8. 同步使用

WebClient可以通过在末尾阻塞来以同步方式使用:spring-doc.cadn.net.cn

Java
Person person = client.get().uri("/person/{id}", i).retrieve()
    .bodyToMono(Person.class)
    .block();

List<Person> persons = client.get().uri("/persons").retrieve()
    .bodyToFlux(Person.class)
    .collectList()
    .block();
Kotlin
val person = runBlocking {
    client.get().uri("/person/{id}", i).retrieve()
            .awaitBody<Person>()
}

val persons = runBlocking {
    client.get().uri("/persons").retrieve()
            .bodyToFlow<Person>()
            .toList()
}

但是,如果需要进行多个调用,则避免在每个调用上阻塞会更有效 响应,而是等待组合结果:spring-doc.cadn.net.cn

Java
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
        .retrieve().bodyToMono(Person.class);

Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
        .retrieve().bodyToFlux(Hobby.class).collectList();

Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
            Map<String, String> map = new LinkedHashMap<>();
            map.put("person", person);
            map.put("hobbies", hobbies);
            return map;
        })
        .block();
Kotlin
val data = runBlocking {
        val personDeferred = async {
            client.get().uri("/person/{id}", personId)
                    .retrieve().awaitBody<Person>()
        }

        val hobbiesDeferred = async {
            client.get().uri("/person/{id}/hobbies", personId)
                    .retrieve().bodyToFlow<Hobby>().toList()
        }

        mapOf("person" to personDeferred.await(), "hobbies" to hobbiesDeferred.await())
    }

以上只是一个例子。还有很多其他的模式和运算符可用于放置 一起一个响应式管道,该管道进行许多远程调用,可能有一些嵌套的, 相互依存,直到最后都不会阻塞。spring-doc.cadn.net.cn

FluxMono,则永远不必在 Spring MVC 或 Spring WebFlux 控制器中阻塞。 只需从控制器方法返回生成的响应式类型即可。同样的原则也适用于 Kotlin 协程和 Spring WebFlux,只需使用挂起函数或返回Flow在你的 controller 方法。spring-doc.cadn.net.cn

2.9. 测试

要测试使用WebClient,您可以使用模拟 Web 服务器,例如 OkHttp MockWebServer。查看示例 它的用途,请查看WebClientIntegrationTests在 Spring Framework 测试套件或static-server示例。spring-doc.cadn.net.cn

3. Web套接字

参考文档的这一部分涵盖了对响应式堆栈 WebSocket 的支持 消息。spring-doc.cadn.net.cn

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

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

3.1.2. 何时使用 WebSockets

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

3.2. WebSocket API

Spring Framework 提供了一个 WebSocket API,您可以使用它来编写客户端和 处理 WebSocket 消息的服务器端应用程序。spring-doc.cadn.net.cn

3.2.1. 服务器

要创建 WebSocket 服务器,您可以先创建一个WebSocketHandler. 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

Java
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        // ...
    }
}
Kotlin
import org.springframework.web.reactive.socket.WebSocketHandler
import org.springframework.web.reactive.socket.WebSocketSession

class MyWebSocketHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {
        // ...
    }
}

然后,您可以将其映射到 URL:spring-doc.cadn.net.cn

Java
@Configuration
class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/path", new MyWebSocketHandler());
        int order = -1; // before annotated controllers

        return new SimpleUrlHandlerMapping(map, order);
    }
}
Kotlin
@Configuration
class WebConfig {

    @Bean
    fun handlerMapping(): HandlerMapping {
        val map = mapOf("/path" to MyWebSocketHandler())
        val order = -1 // before annotated controllers

        return SimpleUrlHandlerMapping(map, order)
    }
}

如果使用 WebFlux Config,则没有任何内容 进一步执行,否则,如果不使用 WebFlux 配置,则需要声明一个WebSocketHandlerAdapter如下图所示:spring-doc.cadn.net.cn

Java
@Configuration
class WebConfig {

    // ...

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}
Kotlin
@Configuration
class WebConfig {

    // ...

    @Bean
    fun handlerAdapter() =  WebSocketHandlerAdapter()
}

3.2.2.WebSocketHandler

handle方法WebSocketHandler需要WebSocketSession并返回Mono<Void>以指示会话的应用程序处理何时完成。会话已处理 通过两个流,一个用于入站消息,一个用于出站消息。下表 描述了处理流的两种方法:spring-doc.cadn.net.cn

WebSocketSession方法 描述

Flux<WebSocketMessage> receive()spring-doc.cadn.net.cn

提供对入站消息流的访问,并在连接关闭时完成。spring-doc.cadn.net.cn

Mono<Void> send(Publisher<WebSocketMessage>)spring-doc.cadn.net.cn

获取传出消息的源,写入消息,并返回Mono<Void>那 当源代码完成并完成写入时完成。spring-doc.cadn.net.cn

一个WebSocketHandler必须将入站和出站流组合成一个统一的流,并且 返回一个Mono<Void>这反映了该流程的完成。取决于应用 要求时,统一流将在以下情况下完成:spring-doc.cadn.net.cn

当入站和出站消息流组合在一起时,无需 检查连接是否打开,因为反应流表示活动结束。 入站流接收完成或错误信号,出站流 接收取消信号。spring-doc.cadn.net.cn

处理程序最基本的实现是处理入站流的实现。这 以下示例显示了这样的实现:spring-doc.cadn.net.cn

Java
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.receive()            (1)
                .doOnNext(message -> {
                    // ...                  (2)
                })
                .concatMap(message -> {
                    // ...                  (3)
                })
                .then();                    (4)
    }
}
1 访问入站消息流。
2 对每条消息做一些事情。
3 执行使用消息内容的嵌套异步作。
4 返回一个Mono<Void>当接收完成时完成。
Kotlin
class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {
        return session.receive()            (1)
                .doOnNext {
                    // ...                  (2)
                }
                .concatMap {
                    // ...                  (3)
                }
                .then()                     (4)
    }
}
1 访问入站消息流。
2 对每条消息做一些事情。
3 执行使用消息内容的嵌套异步作。
4 返回一个Mono<Void>当接收完成时完成。
对于嵌套的异步作,您可能需要调用message.retain()在基础上 使用池数据缓冲区的服务器(例如 Netty)。否则,数据缓冲区可能是 在您有机会读取数据之前发布。有关更多背景信息,请参阅数据缓冲区和编解码器

以下实现结合了入站和出站流:spring-doc.cadn.net.cn

Java
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Flux<WebSocketMessage> output = session.receive()               (1)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .map(value -> session.textMessage("Echo " + value));    (2)

        return session.send(output);                                    (3)
    }
}
1 处理入站消息流。
2 创建出站消息,生成组合流。
3 返回一个Mono<Void>当我们继续接收时,这还没有完成。
Kotlin
class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {

        val output = session.receive()                      (1)
                .doOnNext {
                    // ...
                }
                .concatMap {
                    // ...
                }
                .map { session.textMessage("Echo $it") }    (2)

        return session.send(output)                         (3)
    }
}
1 处理入站消息流。
2 创建出站消息,生成组合流。
3 返回一个Mono<Void>当我们继续接收时,这还没有完成。

入站和出站流可以是独立的,并且只能在完成时加入, 如以下示例所示:spring-doc.cadn.net.cn

Java
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Mono<Void> input = session.receive()                                (1)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .then();

        Flux<String> source = ... ;
        Mono<Void> output = session.send(source.map(session::textMessage)); (2)

        return Mono.zip(input, output).then();                              (3)
    }
}
1 处理入站消息流。
2 发送外发消息。
3 加入流并返回Mono<Void>当任一流结束时完成。
Kotlin
class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {

        val input = session.receive()                                   (1)
                .doOnNext {
                    // ...
                }
                .concatMap {
                    // ...
                }
                .then()

        val source: Flux<String> = ...
        val output = session.send(source.map(session::textMessage))     (2)

        return Mono.zip(input, output).then()                           (3)
    }
}
1 处理入站消息流。
2 发送外发消息。
3 加入流并返回Mono<Void>当任一流结束时完成。

3.2.3.DataBuffer

DataBuffer是 WebFlux 中字节缓冲区的表示形式。的 Spring Core 部分 该参考资料在有关数据缓冲区和编解码器的部分中有更多相关内容。要理解的关键点是,在某些 像 Netty 这样的服务器,字节缓冲区被池化并对引用进行计数,并且必须释放 使用时以避免内存泄漏。spring-doc.cadn.net.cn

在 Netty 上运行时,应用程序必须使用DataBufferUtils.retain(dataBuffer)如果他们 希望保留输入数据缓冲区以确保它们不会被释放,并且 随后使用DataBufferUtils.release(dataBuffer)当缓冲区被消耗时。spring-doc.cadn.net.cn

3.2.4. 握手

WebSocketHandlerAdapter委托给WebSocketService.默认情况下,这是一个实例 之HandshakeWebSocketService,它对 WebSocket 请求执行基本检查,并且 然后使用RequestUpgradeStrategy对于正在使用的服务器。目前,有内置的 支持 Reactor Netty、Tomcat、Jetty 和 Undertow。spring-doc.cadn.net.cn

HandshakeWebSocketService公开一个sessionAttributePredicate允许 设置一个Predicate<String>WebSession并插入它们 转换为WebSocketSession.spring-doc.cadn.net.cn

3.2.5. 服务器配置

RequestUpgradeStrategy对于每个服务器,会公开特定于 底层 WebSocket 服务器引擎。使用 WebFlux Java 配置时,您可以自定义 WebFlux 配置的相应部分中显示的此类属性,或者如果 不使用 WebFlux 配置,请使用以下内容:spring-doc.cadn.net.cn

Java
@Configuration
class WebConfig {

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(webSocketService());
    }

    @Bean
    public WebSocketService webSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}
Kotlin
@Configuration
class WebConfig {

    @Bean
    fun handlerAdapter() =
            WebSocketHandlerAdapter(webSocketService())

    @Bean
    fun webSocketService(): WebSocketService {
        val strategy = TomcatRequestUpgradeStrategy().apply {
            setMaxSessionIdleTimeout(0L)
        }
        return HandshakeWebSocketService(strategy)
    }
}

检查服务器的升级策略,查看可用的选项。现在 只有 Tomcat 和 Jetty 公开了这样的选项。spring-doc.cadn.net.cn

3.2.6. CORS

配置 CORS 并限制对 WebSocket 端点的访问的最简单方法是 拥有您的WebSocketHandler实现CorsConfigurationSource并返回一个CorsConfiguration包含允许的来源、标头和其他详细信息。如果你不能 这样,您还可以将corsConfigurations属性SimpleUrlHandler自 通过 URL 模式指定 CORS 设置。如果同时指定了两者,则使用combine方法CorsConfiguration.spring-doc.cadn.net.cn

3.2.7. 客户端

Spring WebFlux 提供了一个WebSocketClient抽象与实现 Reactor Netty、Tomcat、Jetty、Undertow 和标准 Java(即 JSR-356)。spring-doc.cadn.net.cn

Tomcat 客户端实际上是标准 Java 客户端的扩展,但有一些额外的功能 功能WebSocketSession处理以利用特定于 Tomcat 的 API 用于暂停接收消息以进行背压。

要启动 WebSocket 会话,您可以创建客户端的实例并使用其execute方法:spring-doc.cadn.net.cn

Java
WebSocketClient client = new ReactorNettyWebSocketClient();

URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
        session.receive()
                .doOnNext(System.out::println)
                .then());
Kotlin
val client = ReactorNettyWebSocketClient()

        val url = URI("ws://localhost:8080/path")
        client.execute(url) { session ->
            session.receive()
                    .doOnNext(::println)
            .then()
        }

一些客户端,例如 Jetty,实现Lifecycle需要停止和启动 在您可以使用它们之前。所有客户端都有与配置相关的构造函数选项 底层 WebSocket 客户端的。spring-doc.cadn.net.cn

4. 测试

spring-test模块提供了ServerHttpRequest,ServerHttpResponseServerWebExchange. 请参阅 Spring Web Reactive 的 模拟对象的讨论。spring-doc.cadn.net.cn

WebTestClient基于这些模拟请求和 响应对象,以支持在没有 HTTP 的情况下测试 WebFlux 应用程序 服务器。您可以使用WebTestClient也适用于端到端集成测试。spring-doc.cadn.net.cn

5. RS袜子

本节介绍 Spring Framework 对 RSocket 协议的支持。spring-doc.cadn.net.cn

5.1. 概述

RSocket 是一种用于通过 TCP 进行多路复用、双工通信的应用协议, WebSocket 和其他字节流传输,使用以下交互之一 模型:spring-doc.cadn.net.cn

建立初始连接后,“客户端”与“服务器”的区别将丢失,因为 双方变得对称,双方都可以发起上述交互之一。 这就是为什么在协议中将参与方称为“请求者”和“响应者” 而上述交互称为“请求流”或简称为“请求”。spring-doc.cadn.net.cn

以下是 RSocket 协议的主要功能和优点:spring-doc.cadn.net.cn

  • 跨网络边界的响应式流语义 - 用于流式请求,例如Request-StreamChannel、背压信号 在请求者和响应者之间移动,允许请求者减慢响应者的速度 源,从而减少对网络层拥塞控制的依赖,以及需求 用于网络级别或任何级别的缓冲。spring-doc.cadn.net.cn

  • 请求限制 — 此功能在LEASE框架 可以从两端发送,以限制另一端允许的请求总数 在给定的时间内。租约会定期续签。spring-doc.cadn.net.cn

  • 会话恢复 — 这是为失去连接而设计的,需要一些状态 待维护。状态管理对应用程序是透明的,并且运行良好 结合背压,可以在可能的情况下停止生产者并降低 所需的状态量。spring-doc.cadn.net.cn

  • 大消息的碎片化和重组。spring-doc.cadn.net.cn

  • Keepalive(心跳)。spring-doc.cadn.net.cn

RSocket 有多种语言的实现Java 库基于 Project Reactor 构建, 和用于运输的 Reactor Netty。这意味着 来自应用程序中响应式流发布者的信号透明传播 通过网络中的 RSocket。spring-doc.cadn.net.cn

5.1.1. 协议

RSocket 的好处之一是它在线路上具有明确定义的行为,并且 易于阅读的规范以及一些协议扩展。因此,它是 阅读规范是个好主意,独立于语言实现和更高级别 框架 API。本节提供了一个简洁的概述,以建立一些上下文。spring-doc.cadn.net.cn

最初,客户端通过一些低级流传输连接到服务器,例如 作为 TCP 或 WebSocket 并发送一个SETUPframe 设置 连接。spring-doc.cadn.net.cn

服务器可能会拒绝SETUP帧,但通常是在发送后(对于客户端) 和 received(对于服务器),双方都可以开始发出请求,除非SETUP指示使用租赁语义来限制请求数,在这种情况下 双方都必须等待LEASE框架从另一端允许提出请求。spring-doc.cadn.net.cn

一旦建立连接,双方都可以通过其中一个 框架REQUEST_RESPONSE,REQUEST_STREAM,REQUEST_CHANNELREQUEST_FNF.每个 这些帧将一条消息从请求者传送到响应者。spring-doc.cadn.net.cn

然后,响应者可能会返回PAYLOAD带有响应消息的帧,并且在 之REQUEST_CHANNEL请求者还可以发送PAYLOAD要求较多的框架 消息。spring-doc.cadn.net.cn

当请求涉及消息流时,例如Request-StreamChannel, 响应方必须尊重来自请求方的请求信号。需求表示为 消息数。初始需求在REQUEST_STREAMREQUEST_CHANNEL框架。后续需求通过REQUEST_N框架。spring-doc.cadn.net.cn

每一方还可以通过METADATA_PUSH框架,不 与任何个人请求有关,而是与整个连接有关。spring-doc.cadn.net.cn

RSocket 消息包含数据和元数据。元数据可用于发送路由、安全Tokens等。数据和元数据的格式可以不同。每个的 MIME 类型在SETUP框架并应用于给定连接上的所有请求。spring-doc.cadn.net.cn

虽然所有消息都可以有元数据,但通常元数据(如路由)是按请求的 因此仅包含在请求的第一条消息中,即其中一个帧REQUEST_RESPONSE,REQUEST_STREAM,REQUEST_CHANNELREQUEST_FNF.spring-doc.cadn.net.cn

协议扩展定义了用于应用程序的通用元数据格式:spring-doc.cadn.net.cn

5.1.2. Java 实现

RSocket 的 Java 实现基于 Project Reactor 构建。TCP 和 WebSocket 的传输是 基于 Reactor Netty 构建。作为响应式流 库,Reactor 简化了实现协议的工作。对于应用,它是 使用起来很自然FluxMono带有声明性运算符和透明背面 压力支持。spring-doc.cadn.net.cn

RSocket Java 中的 API 是故意最小和基本的。它侧重于协议 功能,并将应用程序编程模型(例如 RPC codegen 与其他 更高层次,独立关注。spring-doc.cadn.net.cn

主合约 io.rsocket.RSocket 对四种请求交互类型进行建模Mono表示 单消息,Flux消息流,以及io.rsocket.Payload实际的 消息,可以作为字节缓冲区访问数据和元数据。这RSocket使用合约 对称。对于请求,应用程序会被赋予一个RSocket执行 请求与。为了响应,应用程序实现RSocket来处理请求。spring-doc.cadn.net.cn

这并不是一个彻底的介绍。在大多数情况下,Spring 应用程序 将不必直接使用其 API。但是,查看或尝试可能很重要 RSocket 独立于 Spring。RSocket Java 存储库包含许多示例应用程序,这些应用程序 演示其 API 和协议功能。spring-doc.cadn.net.cn

5.1.3. 弹簧支撑

spring-messaging模块包含以下内容:spring-doc.cadn.net.cn

spring-web模块包含EncoderDecoderJackson 等实现 RSocket 应用程序可能需要的 CBOR/JSON 和 Protobuf。它还包含PathPatternParser可以插入以实现高效的路由匹配。spring-doc.cadn.net.cn

Spring Boot 2.2 支持通过 TCP 或 WebSocket 建立 RSocket 服务器,包括 在 WebFlux 服务器中通过 WebSocket 公开 RSocket 的选项。还有客户端 支持和自动配置RSocketRequester.BuilderRSocketStrategies. 有关更多详细信息,请参阅 Spring Boot 参考中的 RSocket 部分spring-doc.cadn.net.cn

Spring Security 5.2 提供 RSocket 支持。spring-doc.cadn.net.cn

Spring Integration 5.2 提供了入站和出站网关来与 RSocket 交互客户端和服务器。有关更多详细信息,请参阅 Spring Integration 参考手册。spring-doc.cadn.net.cn

Spring Cloud Gateway 支持 RSocket 连接。spring-doc.cadn.net.cn

5.2. RSocket请求者

RSocketRequester提供了一个流畅的 API 来执行 RSocket 请求,接受和 返回数据和元数据的对象,而不是低级数据缓冲区。它可以使用 对称地,从客户端发出请求,从服务器发出请求。spring-doc.cadn.net.cn

5.2.1. 客户端请求者

要获取RSocketRequester在客户端是连接到一个服务器,其中涉及 发送 RSocketSETUP带有连接设置的框架。RSocketRequester提供一个 构建器,有助于准备io.rsocket.core.RSocketConnector包括连接 的设置SETUP框架。spring-doc.cadn.net.cn

这是连接默认设置的最基本方法:spring-doc.cadn.net.cn

Java
RSocketRequester requester = RSocketRequester.builder().tcp("localhost", 7000);

URI url = URI.create("https://example.org:8080/rsocket");
RSocketRequester requester = RSocketRequester.builder().webSocket(url);
Kotlin
val requester = RSocketRequester.builder().tcp("localhost", 7000)

URI url = URI.create("https://example.org:8080/rsocket");
val requester = RSocketRequester.builder().webSocket(url)

以上不会立即连接。发出请求时,共享连接是 透明地建立和使用。spring-doc.cadn.net.cn

连接设置

RSocketRequester.Builder提供以下内容来自定义初始SETUP框架:spring-doc.cadn.net.cn

对于数据,默认 MIME 类型派生自第一个配置的Decoder.为 metadata,默认 MIME 类型是复合元数据,它允许多个 元数据值和 MIME 类型对。通常,两者都不需要更改。spring-doc.cadn.net.cn

数据和元数据中的SETUP框架是可选的。在服务器端,可以使用@ConnectMapping方法来处理 连接和SETUP框架。元数据可用于连接 级别安全。spring-doc.cadn.net.cn

策略

RSocketRequester.Builder接受RSocketStrategies以配置请求者。 您需要使用它来提供编码器和解码器,用于数据的 (反) 序列化,并且 元数据值。默认情况下,只有来自spring-coreString,byte[]ByteBuffer已注册。添加spring-web提供对更多 可以按以下方式注册:spring-doc.cadn.net.cn

Java
RSocketStrategies strategies = RSocketStrategies.builder()
    .encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
    .decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
    .build();

RSocketRequester requester = RSocketRequester.builder()
    .rsocketStrategies(strategies)
    .tcp("localhost", 7000);
Kotlin
val strategies = RSocketStrategies.builder()
        .encoders { it.add(Jackson2CborEncoder()) }
        .decoders { it.add(Jackson2CborDecoder()) }
        .build()

val requester = RSocketRequester.builder()
        .rsocketStrategies(strategies)
        .tcp("localhost", 7000)

RSocketStrategies专为重复使用而设计。在某些情况下,例如客户端和服务器在 相同的应用程序,最好在 Spring 配置中声明它。spring-doc.cadn.net.cn

客户端响应者

RSocketRequester.Builder可用于配置响应者,以响应来自 服务器。spring-doc.cadn.net.cn

您可以使用带注释的处理程序进行基于相同的客户端响应 在服务器上使用的基础结构,但以编程方式注册,如下所示:spring-doc.cadn.net.cn

Java
RSocketStrategies strategies = RSocketStrategies.builder()
    .routeMatcher(new PathPatternRouteMatcher())  (1)
    .build();

SocketAcceptor responder =
    RSocketMessageHandler.responder(strategies, new ClientHandler()); (2)

RSocketRequester requester = RSocketRequester.builder()
    .rsocketConnector(connector -> connector.acceptor(responder)) (3)
    .tcp("localhost", 7000);
1 PathPatternRouteMatcher如果spring-web存在,以实现高效 路由匹配。
2 从类创建响应者@MessageMapping和/或@ConnectMapping方法。
3 注册响应者。
Kotlin
val strategies = RSocketStrategies.builder()
        .routeMatcher(PathPatternRouteMatcher())  (1)
        .build()

val responder =
    RSocketMessageHandler.responder(strategies, new ClientHandler()); (2)

val requester = RSocketRequester.builder()
        .rsocketConnector { it.acceptor(responder) } (3)
        .tcp("localhost", 7000)
1 PathPatternRouteMatcher如果spring-web存在,以实现高效 路由匹配。
2 从类创建响应者@MessageMapping和/或@ConnectMapping方法。
3 注册响应者。

注意,以上只是为客户端编程注册而设计的快捷方式 反应。 对于替代场景,客户端响应器处于 Spring 配置中,您仍然可以声明RSocketMessageHandler作为 Spring bean,然后按如下方式应用:spring-doc.cadn.net.cn

Java
ApplicationContext context = ... ;
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);

RSocketRequester requester = RSocketRequester.builder()
    .rsocketConnector(connector -> connector.acceptor(handler.responder()))
    .tcp("localhost", 7000);
Kotlin
import org.springframework.beans.factory.getBean

val context: ApplicationContext = ...
val handler = context.getBean<RSocketMessageHandler>()

val requester = RSocketRequester.builder()
        .rsocketConnector { it.acceptor(handler.responder()) }
        .tcp("localhost", 7000)

对于上述内容,您可能还需要使用setHandlerPredicateRSocketMessageHandler自 切换到不同的策略来检测客户端响应者,例如基于自定义注释,例如@RSocketClientResponder与默认@Controller. 这 在客户端和服务器的场景中,或多个客户端在同一个场景中是必需的 应用。spring-doc.cadn.net.cn

另请参阅带注释的响应器,了解有关编程模型的更多信息。spring-doc.cadn.net.cn

高深

RSocketRequesterBuilder提供回调来公开底层io.rsocket.core.RSocketConnector有关 keepalive 的更多配置选项间隔、会话恢复、拦截器等。您可以配置选项在该级别,如下所示:spring-doc.cadn.net.cn

Java
RSocketRequester requester = RSocketRequester.builder()
    .rsocketConnector(connector -> {
        // ...
    })
    .tcp("localhost", 7000);
Kotlin
val requester = RSocketRequester.builder()
        .rsocketConnector {
            //...
        }
        .tcp("localhost", 7000)

5.2.2. 服务器请求者

要从服务器向连接的客户端发出请求,只需获取 来自服务器的连接客户端的请求者。spring-doc.cadn.net.cn

带注释的响应器中,@ConnectMapping@MessageMapping方法支持RSocketRequester论点。使用它来访问连接的请求者。保持在内 请注意@ConnectMapping方法本质上是SETUP框架,其中 必须先处理请求,然后才能开始请求。因此,一开始的请求必须是 与处理分离。例如:spring-doc.cadn.net.cn

Java
@ConnectMapping
Mono<Void> handle(RSocketRequester requester) {
    requester.route("status").data("5")
        .retrieveFlux(StatusReport.class)
        .subscribe(bar -> { (1)
            // ...
        });
    return ... (2)
}
1 异步启动请求,独立于处理。
2 执行处理和退货完成Mono<Void>.
Kotlin
@ConnectMapping
suspend fun handle(requester: RSocketRequester) {
    GlobalScope.launch {
        requester.route("status").data("5").retrieveFlow<StatusReport>().collect { (1)
            // ...
        }
    }
    /// ... (2)
}
1 异步启动请求,独立于处理。
2 在挂起功能中执行处理。

5.2.3. 请求

拥有客户端服务器请求者后,您可以按如下方式发出请求:spring-doc.cadn.net.cn

Java
ViewBox viewBox = ... ;

Flux<AirportLocation> locations = requester.route("locate.radars.within") (1)
        .data(viewBox) (2)
        .retrieveFlux(AirportLocation.class); (3)
1 指定要包含在请求消息元数据中的路由。
2 为请求消息提供数据。
3 声明预期响应。
Kotlin
val viewBox: ViewBox = ...

val locations = requester.route("locate.radars.within") (1)
        .data(viewBox) (2)
        .retrieveFlow<AirportLocation>() (3)
1 指定要包含在请求消息元数据中的路由。
2 为请求消息提供数据。
3 声明预期响应。

交互类型是根据输入的基数隐式确定的,并且 输出。上面的例子是一个Request-Stream因为发送了一个值和一个流 的值被接收。在大多数情况下,只要 输入和输出的选择与 RSocket 交互类型以及输入和 响应者预期的输出。无效组合的唯一示例是多对一。spring-doc.cadn.net.cn

data(Object)方法也接受任何反应流Publisher包括FluxMono,以及在ReactiveAdapterRegistry.对于多值PublisherFlux这会产生 相同类型的值,请考虑使用重载的data避免有 类型检查和Encoder查找每个元素:spring-doc.cadn.net.cn

data(Object producer, Class<?> elementClass);
data(Object producer, ParameterizedTypeReference<?> elementTypeRef);

data(Object)step 是可选的。对于不发送数据的请求,请跳过它:spring-doc.cadn.net.cn

Java
Mono<AirportLocation> location = requester.route("find.radar.EWR"))
    .retrieveMono(AirportLocation.class);
Kotlin
import org.springframework.messaging.rsocket.retrieveAndAwait

val location = requester.route("find.radar.EWR")
    .retrieveAndAwait<AirportLocation>()

如果使用复合元数据(默认值),并且如果 值由注册的Encoder.例如:spring-doc.cadn.net.cn

Java
String securityToken = ... ;
ViewBox viewBox = ... ;
MimeType mimeType = MimeType.valueOf("message/x.rsocket.authentication.bearer.v0");

Flux<AirportLocation> locations = requester.route("locate.radars.within")
        .metadata(securityToken, mimeType)
        .data(viewBox)
        .retrieveFlux(AirportLocation.class);
Kotlin
import org.springframework.messaging.rsocket.retrieveFlow

val requester: RSocketRequester = ...

val securityToken: String = ...
val viewBox: ViewBox = ...
val mimeType = MimeType.valueOf("message/x.rsocket.authentication.bearer.v0")

val locations = requester.route("locate.radars.within")
        .metadata(securityToken, mimeType)
        .data(viewBox)
        .retrieveFlow<AirportLocation>()

Fire-and-Forget使用send()返回Mono<Void>.请注意,Mono仅指示消息已成功发送,而不是已处理。spring-doc.cadn.net.cn

Metadata-Push使用sendMetadata()方法与Mono<Void>返回值。spring-doc.cadn.net.cn

5.3. 带注释的响应者

RSocket 响应器可以实现为@MessageMapping@ConnectMapping方法。@MessageMapping方法处理单个请求,而@ConnectMapping方法 处理 连接级事件(设置和元数据推送)。支持带注释的响应者 对称地,用于从服务器端响应和从客户端响应。spring-doc.cadn.net.cn

5.3.1. 服务器响应器

要在服务器端使用带注释的响应器,请将RSocketMessageHandler到你的Spring 要检测的配置@Controller豆子与@MessageMapping@ConnectMapping方法:spring-doc.cadn.net.cn

Java
@Configuration
static class ServerConfig {

    @Bean
    public RSocketMessageHandler rsocketMessageHandler() {
        RSocketMessageHandler handler = new RSocketMessageHandler();
        handler.routeMatcher(new PathPatternRouteMatcher());
        return handler;
    }
}
Kotlin
@Configuration
class ServerConfig {

    @Bean
    fun rsocketMessageHandler() = RSocketMessageHandler().apply {
        routeMatcher = PathPatternRouteMatcher()
    }
}

然后通过 Java RSocket API 启动一个 RSocket 服务器,并插入RSocketMessageHandler对于响应者,如下所示:spring-doc.cadn.net.cn

Java
ApplicationContext context = ... ;
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);

CloseableChannel server =
    RSocketServer.create(handler.responder())
        .bind(TcpServerTransport.create("localhost", 7000))
        .block();
Kotlin
import org.springframework.beans.factory.getBean

val context: ApplicationContext = ...
val handler = context.getBean<RSocketMessageHandler>()

val server = RSocketServer.create(handler.responder())
        .bind(TcpServerTransport.create("localhost", 7000))
        .awaitSingle()

RSocketMessageHandler默认情况下,支持复合路由元数据。如果您需要切换到 不同的 MIME 类型或注册其他元数据 MIME 类型。spring-doc.cadn.net.cn

您需要将EncoderDecoder元数据和数据所需的实例 格式。您可能需要spring-web用于编解码器实现的模块。spring-doc.cadn.net.cn

默认情况下SimpleRouteMatcher用于匹配路由AntPathMatcher. 我们建议插入PathPatternRouteMatcherspring-web为 高效的路由匹配。RSocket 路由可以是分层的,但不是 URL 路径。 默认情况下,两个路由匹配器都配置为使用“.”作为分隔符,并且没有 URL 与 HTTP URL 一样解码。spring-doc.cadn.net.cn

RSocketMessageHandler可以通过RSocketStrategies如果出现以下情况,这可能会有用 您需要在同一进程中在客户端和服务器之间共享配置:spring-doc.cadn.net.cn

Java
@Configuration
static class ServerConfig {

    @Bean
    public RSocketMessageHandler rsocketMessageHandler() {
        RSocketMessageHandler handler = new RSocketMessageHandler();
        handler.setRSocketStrategies(rsocketStrategies());
        return handler;
    }

    @Bean
    public RSocketStrategies rsocketStrategies() {
        return RSocketStrategies.builder()
            .encoders(encoders -> encoders.add(new Jackson2CborEncoder()))
            .decoders(decoders -> decoders.add(new Jackson2CborDecoder()))
            .routeMatcher(new PathPatternRouteMatcher())
            .build();
    }
}
Kotlin
@Configuration
class ServerConfig {

    @Bean
    fun rsocketMessageHandler() = RSocketMessageHandler().apply {
        rSocketStrategies = rsocketStrategies()
    }

    @Bean
    fun rsocketStrategies() = RSocketStrategies.builder()
            .encoders { it.add(Jackson2CborEncoder()) }
            .decoders { it.add(Jackson2CborDecoder()) }
            .routeMatcher(PathPatternRouteMatcher())
            .build()
}

5.3.2. 客户端响应者

客户端的带注释的响应器需要在RSocketRequester.Builder.有关详细信息,请参阅客户端响应程序spring-doc.cadn.net.cn

5.3.3. @MessageMapping

服务器客户端响应器配置就位后,@MessageMapping方法可以如下使用:spring-doc.cadn.net.cn

Java
@Controller
public class RadarsController {

    @MessageMapping("locate.radars.within")
    public Flux<AirportLocation> radars(MapRequest request) {
        // ...
    }
}
Kotlin
@Controller
class RadarsController {

    @MessageMapping("locate.radars.within")
    fun radars(request: MapRequest): Flow<AirportLocation> {
        // ...
    }
}

以上@MessageMapping方法响应具有 路由“locate.radars.within”。它支持灵活的方法签名,并可选择 使用以下方法参数:spring-doc.cadn.net.cn

方法参数 描述

@Payloadspring-doc.cadn.net.cn

请求的有效负载。这可以是异步类型的具体值,例如MonoFlux.spring-doc.cadn.net.cn

注意:注释的使用是可选的。不是简单类型的方法参数 并且不是任何其他受支持的参数,则假定为预期有效负载。spring-doc.cadn.net.cn

RSocketRequesterspring-doc.cadn.net.cn

向远程端发出请求的请求者。spring-doc.cadn.net.cn

@DestinationVariablespring-doc.cadn.net.cn

根据映射模式中的变量从路由中提取的值,例如@MessageMapping("find.radar.{id}").spring-doc.cadn.net.cn

@Headerspring-doc.cadn.net.cn

按照 MetadataExtractor 中所述注册为提取的元数据值。spring-doc.cadn.net.cn

@Headers Map<String, Object>spring-doc.cadn.net.cn

注册提取的所有元数据值,如 MetadataExtractor 中所述。spring-doc.cadn.net.cn

返回值应是一个或多个要序列化为响应的对象 负载。这可以是异步类型,例如MonoFlux、具体值或 也void或无值异步类型,例如Mono<Void>.spring-doc.cadn.net.cn

RSocket 交互类型,即@MessageMapping方法支座由 输入的基数(即@Payload参数)和输出,其中 基数表示以下内容:spring-doc.cadn.net.cn

基数 描述

1spring-doc.cadn.net.cn

显式值或单值异步类型,例如Mono<T>.spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

多值异步类型,例如Flux<T>.spring-doc.cadn.net.cn

0spring-doc.cadn.net.cn

对于输入,这意味着该方法没有@Payload论点。spring-doc.cadn.net.cn

对于输出,这是void或无值异步类型,例如Mono<Void>.spring-doc.cadn.net.cn

下表显示了所有输入和输出基数组合以及相应的 交互类型:spring-doc.cadn.net.cn

输入基数 输出基数 交互类型

0, 1spring-doc.cadn.net.cn

0spring-doc.cadn.net.cn

即发即忘,请求-响应spring-doc.cadn.net.cn

0, 1spring-doc.cadn.net.cn

1spring-doc.cadn.net.cn

请求-响应spring-doc.cadn.net.cn

0, 1spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

请求流spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

0、1、多spring-doc.cadn.net.cn

请求通道spring-doc.cadn.net.cn

5.3.4. @ConnectMapping

@ConnectMapping处理SETUPframe,以及 RSocket 连接开始时的帧,以及 任何后续元数据推送通知通过METADATA_PUSH框架,即metadataPush(Payload)io.rsocket.RSocket.spring-doc.cadn.net.cn

@ConnectMapping方法支持与 @MessageMapping 相同的参数,但基于元数据和来自SETUPMETADATA_PUSH框架。@ConnectMapping可以有一个模式来缩小处理范围 元数据中具有路由的特定连接,或者未声明任何模式 则所有连接都匹配。spring-doc.cadn.net.cn

@ConnectMapping方法不能返回数据,必须使用voidMono<Void>作为返回值。如果处理返回新 连接,则连接被拒绝。不得将处理搁置以使 请求RSocketRequester连接。有关详细信息,请参阅服务器请求器spring-doc.cadn.net.cn

5.4. 元数据提取器

响应者必须解释元数据。复合元数据允许独立 格式化的元数据值(例如用于路由、安全、跟踪),每个值都有自己的 MIME 类型。应用程序需要一种方法来配置要支持的元数据 MIME 类型,以及一种方法 以访问提取的值。spring-doc.cadn.net.cn

MetadataExtractor是一个获取序列化元数据并返回解码的合约 name-value 对,然后可以像标头一样按名称访问,例如通过@Header在带注释的处理程序方法中。spring-doc.cadn.net.cn

DefaultMetadataExtractor可以给Decoder实例来解码元数据。出 它内置了对“message/x.rsocket.routing.v0”的支持,它解码到String并保存在“路由”键下。对于您需要提供的任何其他 MIME 类型 一个Decoder并注册 MIME 类型,如下所示:spring-doc.cadn.net.cn

Java
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
extractor.metadataToExtract(fooMimeType, Foo.class, "foo");
Kotlin
import org.springframework.messaging.rsocket.metadataToExtract

val extractor = DefaultMetadataExtractor(metadataDecoders)
extractor.metadataToExtract<Foo>(fooMimeType, "foo")

复合元数据可以很好地组合独立的元数据值。然而, 请求者可能不支持复合元数据,或者可能选择不使用它。为此,DefaultMetadataExtractor可能需要自定义逻辑来将解码值映射到输出 地图。以下是将 JSON 用于元数据的示例:spring-doc.cadn.net.cn

Java
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
extractor.metadataToExtract(
    MimeType.valueOf("application/vnd.myapp.metadata+json"),
    new ParameterizedTypeReference<Map<String,String>>() {},
    (jsonMap, outputMap) -> {
        outputMap.putAll(jsonMap);
    });
Kotlin
import org.springframework.messaging.rsocket.metadataToExtract

val extractor = DefaultMetadataExtractor(metadataDecoders)
extractor.metadataToExtract<Map<String, String>>(MimeType.valueOf("application/vnd.myapp.metadata+json")) { jsonMap, outputMap ->
    outputMap.putAll(jsonMap)
}

配置时MetadataExtractor通过RSocketStrategies,你可以让RSocketStrategies.Builder使用配置的解码器创建提取器,以及 只需使用回调即可自定义注册,如下所示:spring-doc.cadn.net.cn

Java
RSocketStrategies strategies = RSocketStrategies.builder()
    .metadataExtractorRegistry(registry -> {
        registry.metadataToExtract(fooMimeType, Foo.class, "foo");
        // ...
    })
    .build();
Kotlin
import org.springframework.messaging.rsocket.metadataToExtract

val strategies = RSocketStrategies.builder()
        .metadataExtractorRegistry { registry: MetadataExtractorRegistry ->
            registry.metadataToExtract<Foo>(fooMimeType, "foo")
            // ...
        }
        .build()

6. 响应式库

spring-webflux取决于reactor-core并在内部使用它来组合异步 逻辑并提供响应式流支持。通常,WebFlux API 返回FluxMono(因为这些是在内部使用的)并宽容地接受任何反应流Publisher实现作为输入。使用FluxMono很重要,因为 它有助于表达基数——例如,无论是单个还是多个异步 值是预期的,这对于做出决策至关重要(例如,当 编码或解码 HTTP 消息)。spring-doc.cadn.net.cn

对于带注释的控制器,WebFlux 透明地适应选择的响应式库由应用程序。这是在ReactiveAdapterRegistry它为响应式库和其他异步类型提供可插拔支持。该注册表内置了对 RxJava 3、Kotlin 协程和 SmallRye Mutiny 的支持,但您也可以注册其他第三方适配器。spring-doc.cadn.net.cn

从 Spring Framework 5.3.11 开始,对 RxJava 1 和 2 的支持已弃用,遵循RxJava 自己的 EOL 建议和对 RxJava 3 的升级建议。spring-doc.cadn.net.cn

对于功能 API(例如功能端点,则WebClient等),一般 适用于 WebFlux API 的规则 —FluxMono作为返回值和响应式流Publisher作为输入。当Publisher,无论是自定义的还是来自其他响应式库, ,则只能将其视为语义未知 (0..N) 的流。但是,如果 语义是已知的,你可以用FluxMono.from(Publisher)相反 传递原始的Publisher.spring-doc.cadn.net.cn

例如,给定一个Publisher这不是Mono,Jackson JSON 消息编写器 期望多个值。如果媒体类型暗示无限流(例如,application/json+stream),值被单独写入和刷新。否则 值被缓冲到列表中并呈现为 JSON 数组。spring-doc.cadn.net.cn