Web 响应式

1. Spring WebFlux

Spring 框架中包含的原始 Web 框架 Spring Web MVC 是 专为 Servlet API 和 Servlet 容器构建。反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版后期添加的。它是完全非阻塞的,支持 Reactive Streams 背压,并运行在 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 的其余部分 其中 Contract 是同步的 (Filter,Servlet) 或阻止 (getParameter,getPart).这就是一个新的通用 API 作为 任何非阻塞运行时。这很重要,因为服务器(例如 Netty)是 在异步、非阻塞空间中建立良好。spring-doc.cadn.net.cn

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

1.1.1. 定义 “Reactive”

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

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

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

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

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

1.1.2. 响应式 API

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

Reactor 是 Spring WebFlux 的 Web Flux 中。它提供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 或其他反应式库。有关更多详细信息,请参阅 Reactive Librariesspring-doc.cadn.net.cn

除了反应式 API 之外,WebFlux 还可以与 Kotlin 中的协程 API 一起使用,后者提供了一种更加命令式的编程风格。 以下 Kotlin 代码示例将随协程 API 一起提供。

1.1.3. 对模型进行编程

spring-web模块包含作为 Spring WebFlux 基础的反应式基础, 包括 HTTP 抽象、支持的 Reactive Streams 适配器 服务器、编解码器和内核WebHandler应用程序接口可比较 Servlet API,但具有非阻塞 Contract。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 的轻量级函数式编程模型。您可以想到 this 作为应用程序可用于路由和 处理请求。带注释控制器的最大区别在于应用程序 负责从头到尾的请求处理,而不是通过 注释和被回调。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. 服务器

Spring WebFlux 在 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

“To scale” 和 “small number of thread” 听起来可能很矛盾,但永远不要阻止 current thread (并依赖于回调) 意味着您不需要额外的线程,因为 没有阻塞调用需要吸收。
调用阻塞 API

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

可变状态

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

线程模型

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

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

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

  • Reactor 和 RxJava 提供了线程池抽象,称为调度程序,可与publishOn运算符,用于将处理切换到其他线程池。 计划程序的名称表示特定的并发策略,例如,“parallel” (对于具有有限线程数的 CPU 绑定工作)或 “elastic” (对于 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-webmodule 包含对反应式 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 Reactive Streams 支持

spring-doc.cadn.net.cn

Netty APIspring-doc.cadn.net.cn

Reactor Nettyspring-doc.cadn.net.cn

Undertowspring-doc.cadn.net.cn

Undertow 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;Tomcat API 读取和写入 ByteBuffers 与 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;Jetty API 写入 ByteBuffers 与 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 项目名称

Reactor Nettyspring-doc.cadn.net.cn

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

反应器-NETTYspring-doc.cadn.net.cn

Undertowspring-doc.cadn.net.cn

io.undertowspring-doc.cadn.net.cn

undertow-corespring-doc.cadn.net.cn

Tomcatspring-doc.cadn.net.cn

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

tomcat-embed-corespring-doc.cadn.net.cn

Jettyspring-doc.cadn.net.cn

org.eclipse.jetty 网站spring-doc.cadn.net.cn

jetty 服务器、jetty-servletspring-doc.cadn.net.cn

下面的代码片段显示了如何使用HttpHandleradapters 的 API 中:spring-doc.cadn.net.cn

Reactor Nettyspring-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通过简单地指向 SpringApplicationContext自动检测组件,和/或通过注册组件 与构建器。spring-doc.cadn.net.cn

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

特殊 bean 类型

下表列出了WebHttpHandlerBuilder可以在 Spring ApplicationContext,或者可以直接使用它注册:spring-doc.cadn.net.cn

Bean 名称 Bean 类型 计数 描述

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

WebExceptionHandlerspring-doc.cadn.net.cn

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

为来自链的异常提供处理WebFilterinstances 和目标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类访问各个部分,因此需要 完整解析多部分数据。相比之下,您可以使用@RequestBody要解码 content 设置为Flux<Part>而不收集到MultiValueMap.spring-doc.cadn.net.cn

请求头转发

当请求通过代理(例如负载均衡器)时,主机、端口和 方案可能会发生变化。从客户的角度来看,这使得创建指向正确 host、port 和 scheme。spring-doc.cadn.net.cn

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

ForwardedHeaderTransformer是一个组件,用于修改 请求,然后删除这些标头。如果您声明 it 作为名为forwardedHeaderTransformer,它将被检测并使用。spring-doc.cadn.net.cn

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

在 5.1 中ForwardedHeaderFilter已弃用并被ForwardedHeaderTransformer因此,请求头转发可以在 Exchange 已创建。如果仍然配置了过滤器,则会将其从 filters 和ForwardedHeaderTransformer

1.2.3. 过滤器

WebHandler应用程序接口,您可以使用WebFilter应用 interception 样式 过滤器其余处理链和目标之前的和之后的逻辑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

请参阅 CORS 部分和CORSWebFilter了解更多详情。spring-doc.cadn.net.cn

1.2.4. 异常

WebHandler应用程序接口,您可以使用WebExceptionHandler处理 异常WebFilterinstances 和目标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 Config 中声明。spring-doc.cadn.net.cn

1.2.5. 编解码器

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

spring-coremodule 提供byte[],ByteBuffer,DataBuffer,ResourceString编码器和解码器实现。这spring-web模块提供 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器以及 用于表单数据、多部分内容、 server-sent 事件等。spring-doc.cadn.net.cn

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

Jackson JSON

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

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

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

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

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

  • 当解码为多值发布者(例如Flux)、每个TokenBuffer传递给 这ObjectMapper一旦收到足够多的字节用于完全形成的对象。这 input 内容可以是 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、encode、write 和 使用以行分隔的 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” 内容进行编码。挨次MultipartHttpMessageReaderdelegates 到 另一个HttpMessageReader对于实际解析为Flux<Part>然后简单地 将零件收集到MultiValueMap. 默认情况下,DefaultPartHttpMessageReader,但这可以通过ServerCodecConfigurer. 有关DefaultPartHttpMessageReader,请参阅javadoc 的DefaultPartHttpMessageReader.spring-doc.cadn.net.cn

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

一次getMultipartData()时,无法再从 请求正文。因此,应用程序必须始终如一地使用getMultipartData()对部分进行重复的、类似 Map 的访问,或者依赖SynchronossPartHttpMessageReader一次性访问Flux<Part>.spring-doc.cadn.net.cn

限制

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

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

对于 Multipart 解析maxInMemorySize属性限制 非文件部分的大小。对于文件部分,它确定部分达到的阈值 已写入磁盘。对于写入磁盘的文件部分,还有一个额外的maxDiskUsagePerPart属性来限制每个部分的磁盘空间量。还有 一个maxParts属性来限制 Multipart 请求中的段总数。 要在 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 的 此参考在 Data Buffers and Codecs 一节中提供了更多相关信息。要理解的关键点是,在某些 像 Netty 这样的服务器,字节缓冲区是池化的和引用计数的,并且必须释放 当使用以避免内存泄漏时。spring-doc.cadn.net.cn

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

1.2.6. 日志记录

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

TRACE级别日志记录通常遵循与DEBUG(例如,还有 不应是 Firehose),但可用于调试任何问题。此外,一些日志 消息可能会在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().在WebClient端,日志 ID 存储在ClientRequest属性 (LOG_ID_ATTRIBUTE) ,而完全格式化的前缀可从ClientRequest#logPrefix().spring-doc.cadn.net.cn

敏感数据

DEBUGTRACE日志记录可以记录敏感信息。这就是为什么表单参数和 默认情况下,标头是屏蔽的,您必须显式启用其日志记录。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 配置中发现它需要的 delegate 组件。 它本身也被设计为 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. 特殊 bean 类型

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

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

Bean 类型 解释

HandlerMappingspring-doc.cadn.net.cn

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

主要的HandlerMappingimplementations 包括RequestMappingHandlerMapping@RequestMapping带注释的方法,RouterFunctionMapping用于功能端点 routes 和SimpleUrlHandlerMapping用于 URI 路径模式的显式注册 和WebHandler实例。spring-doc.cadn.net.cn

HandlerAdapterspring-doc.cadn.net.cn

帮助DispatcherHandler调用映射到请求的处理程序,而不管 处理程序的实际调用方式。例如,调用带注释的控制器 需要解析注释。a 的主要用途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 是最好的起点。它声明 所需的 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完成 通过直接写入响应或使用 View 进行渲染来进行处理。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被视为 model 属性。spring-doc.cadn.net.cn

另请参阅 View Resolutionspring-doc.cadn.net.cn

Integer.MAX_VALUEspring-doc.cadn.net.cn

1.3.5. 异常

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

只要 error 出现 信号发生在从处理程序返回的反应式类型生成任何数据项之前。spring-doc.cadn.net.cn

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

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

1.3.6. 视图分辨率

视图分辨率允许使用 HTML 模板和模型呈现到浏览器,而无需 将您与特定的 View 技术联系起来。在 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 属性)或异步返回值 (例如,Monocompleted empty)。spring-doc.cadn.net.cn

  • 渲染:API for 查看解决方案。探索 IDE 中的代码完成选项。spring-doc.cadn.net.cn

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

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

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

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

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

重 定向

特别的redirect:prefix 允许您执行重定向。这UrlBasedViewResolver(和子类)将此视为一条指令, redirect 是必需的。视图名称的其余部分是重定向 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,该 API 的 IP 地址。spring-doc.cadn.net.cn

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

1.4. 带注解的控制器

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

要启用此类@Controllerbeans 中,你可以将组件扫描添加到 您的 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. 请求映射

@RequestMappingannotation 用于将请求映射到 controllers 方法。它有 按 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

匹配 regexp"[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")),但您可以 如果名称相同,并且您使用调试编译代码,请省略该详细信息 信息或使用-parameterscompiler 标志。spring-doc.cadn.net.cn

语法{*varName}声明与零个或多个剩余路径匹配的 URI 变量 段。例如/resources/{*path}匹配/resources/"path"variable 捕获/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 一起使用而设计 paths 的 Web 应用程序中的 paths,其中在运行时匹配了大量 URI 路径模式。

Spring WebFlux 不支持后缀模式匹配——与 Spring MVC 不同,其中 映射(如/person也匹配到/person.*.对于基于 URL 的内容 negotiation,如果需要,我们建议使用 query 参数,它更简单、更 显式的,并且不易受到基于 URL 路径的漏洞利用。spring-doc.cadn.net.cn

形态比较

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

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

Catch-all 模式(例如 ,**{*varName}) 被排除在评分之外,并且始终 排序为 last。如果两个模式都是 catch-all,则选择较长的模式。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属性。与大多数其他请求不同 mapping 属性,但是,当在类级别使用时,方法级别的consumes属性 覆盖而不是扩展类级声明。spring-doc.cadn.net.cn

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

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

Java
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@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属性。与大多数其他请求不同 mapping 属性,但是,当在类级别使用时,方法级别的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.

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

Java
@GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}
1 检查myHeader等于myValue.
Kotlin
@GetMapping("/pets/{petId}", headers = ["myHeader=myValue"]) (1)
fun findPet(@PathVariable petId: String) {
    // ...
}
1 检查myHeader等于myValue.
HTTP 头, 选项

@GetMapping@RequestMapping(method=HttpMethod.GET)支持 HTTP HEAD 透明地用于请求映射目的。控制器方法不需要更改。 响应包装器,应用于HttpHandlerserver 适配器,确保Content-Lengthheader 设置为写入的字节数,但未实际写入响应。spring-doc.cadn.net.cn

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

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

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

自定义注释

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

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

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

显式注册

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

Java
@Configuration
public class MyConfig {

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

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

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

        mapping.registerMapping(info, handler, method); (4)
    }

}
1 注入 target handlers 和控制器的处理程序 Map。
2 准备请求映射元数据。
3 获取处理程序方法。
4 添加注册。
Kotlin
@Configuration
class MyConfig {

    @Autowired
    fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) { (1)

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

        val method = UserHandler::class.java.getMethod("getUser", Long::class.java) (3)

        mapping.registerMapping(info, handler, method) (4)
    }
}
1 注入 target handlers 和控制器的处理程序 Map。
2 准备请求映射元数据。
3 获取处理程序方法。
4 添加注册。

1.4.3. 处理程序方法

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

方法参数

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

响应式类型(Reactor、RxJava 或其他)是 支持需要阻塞 I/O 的参数(例如,读取请求正文)到 被解决。这在 Description (描述) 列中标记。不需要响应式类型 在不需要阻塞的参数上。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 请求和响应的容器, request 和 session 属性、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

当前经过身份验证的用户 — 可能是特定的Principalimplementation 类(如果已知)。 支持响应式类型。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

当前请求区域设置,由最具体的LocaleResolveravailable — 在 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参数 紧跟在 validated method 参数之后。spring-doc.cadn.net.cn

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

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

UriComponentsBuilderspring-doc.cadn.net.cn

用于准备相对于当前请求的 host、port、scheme 和 context 路径。请参阅 URI 链接spring-doc.cadn.net.cn

@SessionAttributespring-doc.cadn.net.cn

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

@RequestAttributespring-doc.cadn.net.cn

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

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

如果方法参数与上述任何内容都不匹配,则默认情况下,它会解析为 一个@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实例,用于与隐式模型一起进行渲染 — determined 通过 Command Objects 和@ModelAttribute方法。handler 方法还可以 通过声明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返回 值)被视为已完全处理响应(如果它还具有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 Field Formatting)。spring-doc.cadn.net.cn

类型转换中的一个实际问题是空 String 源值的处理。 如果该值变为null作为类型转换的结果。 这可能是这种情况Long,UUID和其他目标类型。如果要允许null要注入,请使用requiredflag 的 Token,或声明 参数设置为@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 变量,其中 matrix 变量是预期的。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

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

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

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

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

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

鉴于所有路径段都可以包含矩阵变量,您有时可能需要 消除矩阵变量预期位于哪个路径变量中的歧义, 如下例所示: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“请求参数”概念将查询参数、表单 data 和 multipart 合二为一。但是,在 WebFlux 中,每个都可以通过ServerWebExchange.而@RequestParam绑定到查询参数,则可以使用 数据绑定,用于将查询参数、表单数据和多部分应用于命令对象

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

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

@RequestParam注解在Map<String, String>MultiValueMap<String, String>参数,则 Map 中填充了所有查询参数。spring-doc.cadn.net.cn

请注意,使用@RequestParam是可选的 — 例如,设置其属性。由 default,任何作为简单值类型的参数(由 BeanUtils#isSimpleProperty 确定) 并且未由任何其他参数解析 resolver 被视为已批注 跟@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页眉。

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

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

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

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

以下示例显示了一个带有 Cookie 的请求:spring-doc.cadn.net.cn

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

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

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

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

@ModelAttribute

您可以使用@ModelAttribute注解来访问来自 model 或实例化它(如果不存在)。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

获取 model 属性实例后,将应用数据绑定。这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在 model 属性参数上。
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在 model 属性参数上。

与 Spring MVC 不同,Spring WebFlux 在模型中支持响应式类型——例如,Mono<Account>io.reactivex.Single<Account>.您可以声明@ModelAttribute论点 有或没有响应式类型包装器,并且它将被相应地解析, 如有必要,调整为实际值。但是,请注意,要使用BindingResult参数,则必须声明@ModelAttribute参数,而没有响应式 type 包装器,如前所述。或者,您可以通过 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 确定) 并且未由任何其他参数解析 resolver 被视为已批注 跟@ModelAttribute.spring-doc.cadn.net.cn

@SessionAttributes

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

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

@RequestAttribute

@SessionAttribute中,您可以使用@RequestAttributeannotation 添加到 访问之前创建的预先存在的请求属性(例如,通过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 的访问 内容。在控制器中处理文件上传表单(例如,从浏览器)的最佳方法 是通过数据绑定到 Command 对象, 如下例所示: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以获取元数据。

您可以使用@RequestPartjavax.validation.Valid或 Spring 的@Validated注解,这会导致应用 Standard Bean Validation。验证 错误会导致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>),如下例所示: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

您可以使用@RequestBodyjavax.validation.Valid或 Spring 的@Validated注解,这会导致应用 Standard Bean Validation。验证 错误会导致WebExchangeBindException,这将产生 400 (BAD_REQUEST) 响应。 异常包含一个BindingResult替换为错误详细信息,并且可以在 controller 方法,方法是使用 async 包装器声明参数,然后使用 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对方法进行注释以序列化返回 传递给响应正文。以下内容 示例展示了如何做到这一点: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方法。 有关详细信息,请参阅 Jackson JSONspring-doc.cadn.net.cn

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

ResponseEntity

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

Java
@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).body(body);
}
Kotlin
@GetMapping("/something")
fun handle(): ResponseEntity<String> {
    val body: 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 的@JsonView注解激活序列化视图类,如下例所示:spring-doc.cadn.net.cn

Java
@RestController
public class UserController {

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

public class User {

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

    private String username;
    private String password;

    public User() {
    }

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

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

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

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView::class)
    fun getUser(): User {
        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.有关更多详细信息,请参阅 Controller Advice 部分。spring-doc.cadn.net.cn

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

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

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

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

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

与 Spring MVC 不同,Spring WebFlux 在模型中显式支持响应式类型 (例如,Mono<Account>io.reactivex.Single<Account>).这种异步模型 属性可以透明地解析(并更新模型)为其实际值 在@RequestMappingininvokecation 中,提供了@ModelAttributeargument 是 在没有包装器的情况下声明,如下例所示: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方法,在这种情况下,@RequestMappingmethod 被解释为 model 属性。这通常不是必需的,因为它是 HTML 中的默认行为 控制器,除非返回值是String否则会被解释为 作为视图名称。@ModelAttribute还可以帮助自定义 Model 属性名称, 如下例所示:spring-doc.cadn.net.cn

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

1.4.5.DataBinder

@Controller@ControllerAdvice类可以具有@InitBinder方法,更改为 初始化 的实例WebDataBinder.反过来,这些用于:spring-doc.cadn.net.cn

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

@InitBinder方法支持许多相同的参数@RequestMapping方法 do 的@ModelAttribute(command 对象) 参数。通常,它们被声明 替换为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 请求的绑定 parameters ((即表单数据或查询参数) ) 添加到模型对象中的属性,以及 其嵌套对象。spring-doc.cadn.net.cn

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

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

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

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

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

public class ChangeEmailForm {

    private String oldEmailAddress;
    private String newEmailAddress;

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

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

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

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

}

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

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

@Controller
public class ChangeEmailController {

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

    // @RequestMapping methods, etc.

}

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

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

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

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

1.4.6. 管理异常

@Controller@ControllerAdvice类可以具有@ExceptionHandler方法处理来自控制器方法的异常。以下内容 example 包含这样的处理程序方法: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被抛出)或针对顶级包装器中的直接原因 exception(例如,IOException包装在IllegalStateException).spring-doc.cadn.net.cn

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

@ExceptionHandlermethod 支持相同的方法参数和 将值作为@RequestMapping方法,请求正文除外 - 和@ModelAttribute-相关的方法参数。

支持@ExceptionHandler方法由HandlerAdapter@RequestMapping方法。看DispatcherHandler了解更多详情。spring-doc.cadn.net.cn

REST API 异常

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

请注意,Spring WebFlux 没有 Spring MVC 的等效项ResponseEntityExceptionHandler,因为 WebFlux 仅引发ResponseStatusException(或其子类),并且这些不需要被翻译成 HTTP 状态代码。

1.4.7. 控制器建议

通常,@ExceptionHandler,@InitBinder@ModelAttribute方法 适用 在@Controller类(或类层次结构)。如果你 希望这些方法更全局地应用(跨控制器),你可以在 类@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) 是 在本地 Bean 的 Swift Controller 之后应用(从@Controller).相比之下,全球@ModelAttribute@InitBinder方法于 local 方法应用。spring-doc.cadn.net.cn

默认情况下,@ControllerAdvice方法适用于每个请求(即所有控制器), 但是,您可以通过在 annotation 中,如下例所示: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,这是一个轻量级函数式编程模型,其中函数 用于路由和处理请求,而 Contract 旨在实现不变性。 它是基于 Comments 的编程模型的替代方案,但在其他方面运行 相同的 Reactive Core 基础。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>). 当 router 函数匹配时,将返回一个处理程序函数;否则为空 Mono。RouterFunction等价于@RequestMapping注解,但带有 major 不同之处在于 router 函数不仅提供数据,还提供行为。spring-doc.cadn.net.cn

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

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.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. HandlerFunction 函数

ServerRequestServerResponse是提供 JDK 8 友好的不可变接口 访问 HTTP 请求和响应。 请求和响应都提供 Reactive Streams 背压 与身体流相反。 请求正文由 Reactor 表示FluxMono. 响应体由任何 Reactive Streams 表示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()

以下示例显示如何以 map 形式访问多部分数据: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方法创建它。您可以使用生成器设置响应状态,以添加响应 headers 或提供正文。以下示例使用 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()

根据所使用的编解码器,可以传递 hint 参数来自定义 body 是序列化的或反序列化的。例如,要指定 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 的 JSON 格式。
2 createPerson是一个处理程序函数,它将新的Person包含在请求正文中。 请注意,PersonRepository.savePerson(Person)返回Mono<Void>:空的Mono那个会发出 从请求中读取并存储人员时的完成信号。所以我们使用build(Publisher<Void>)方法在收到该完成信号时发送响应(即 当Person已保存)。
3 getPerson是一个处理程序函数,它返回一个 person,由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 的 JSON 格式。
2 createPerson是一个处理程序函数,它将新的Person包含在请求正文中。 请注意,PersonRepository.savePerson(Person)是没有返回类型的挂起函数。
3 getPerson是一个处理程序函数,它返回一个 person,由id路径 变量。我们检索该Person并创建 JSON 响应(如果是 发现。如果未找到,我们将返回 404 Not Found 响应。
验证

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

Java
public class PersonHandler {

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

    // ...

    public 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

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

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

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

谓词

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

Java
RouterFunction<ServerResponse> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> ServerResponse.ok().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 时,这一点也很重要,也是如此 稍后描述。 请注意,此行为与基于 Comments 的编程模型不同,其中 “最具体”控制器方法会自动选取。spring-doc.cadn.net.cn

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

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

Java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.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匹配 JSON 的标头将路由到PersonHandler.getPerson
2 GET /person替换为Accept匹配 JSON 的标头将路由到PersonHandler.listPeople
3 POST /person没有其他谓词映射到PersonHandler.createPerson
4 otherRoute是在其他地方创建并添加到 Route built 的 router 函数。
Kotlin
import org.springframework.http.MediaType.APPLICATION_JSON

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匹配 JSON 的标头将路由到PersonHandler.getPerson
2 GET /person替换为Accept匹配 JSON 的标头将路由到PersonHandler.listPeople
3 POST /person没有其他谓词映射到PersonHandler.createPerson
4 otherRoute是在其他地方创建并添加到 Route built 的 router 函数。
嵌套路由

一组 router 函数通常具有共享谓词,例如 共享路径。在上面的示例中,共享谓词将是一个路径谓词,该 比赛/person,由其中 3 条路由使用。使用注释时,您需要删除 此复制通过使用类型级别@RequestMapping注解,映射到/person.在 WebFlux.fn 中,路径谓词可以通过path方法上的 router 函数构建器。例如,上面示例的最后几行可以是 通过使用嵌套路由,通过以下方式进行了改进:spring-doc.cadn.net.cn

Java
RouterFunction<ServerResponse> route = route()
    .path("/person", builder -> builder (1)
        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        .GET(accept(APPLICATION_JSON), handler::listPeople)
        .POST(handler::createPerson))
    .build();
1 请注意,第二个参数path是采用 router builder 的 consumer。
Kotlin
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 服务器中运行路由器功能?一个简单的选择是将路由器 函数添加到HttpHandler使用以下选项之一:spring-doc.cadn.net.cn

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

Spring Boot 也使用一个更典型的选项,是使用DispatcherHandler-based 设置,它使用 Spring 配置来声明 处理请求所需的组件。WebFlux Java 配置声明以下内容 支持功能终端节点的基础设施组件:spring-doc.cadn.net.cn

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

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

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

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

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

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

Java
SecurityManager securityManager = ...

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

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

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

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

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

1.6. URI 链接

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

1.6.1. UriComponents

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

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

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

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

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

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

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

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

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

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

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

1.6.2. Uri生成器

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

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

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

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

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

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

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

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

val restTemplate = RestTemplate()
restTemplate.uriTemplateHandler = factory

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

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

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

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

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

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

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

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

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

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

1.6.3. URI 编码

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.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 WebFluxHandlerMappingimplementations 提供对 CORS 的内置支持。成功后 将请求映射到处理程序,则HandlerMapping检查 CORS 配置的 given request 和 handler 并采取进一步的作。处理印前检查请求 直接,而简单和实际的 CORS 请求被拦截、验证,并具有 所需的 CORS 响应标头集。spring-doc.cadn.net.cn

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

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

您可以在HandlerMappingLevel with More (更多级别) 精细的处理程序级 CORS 配置。例如,带注解的控制器可以使用 类级或方法级@CrossOrigin注解(其他处理程序可以实现CorsConfigurationSource).spring-doc.cadn.net.cn

组合全局配置和本地配置的规则通常是累加的 — 例如, 所有全球和所有本地源。对于只能使用单个值 accepted,例如allowCredentialsmaxAge,则 local 将覆盖 global 值。看CorsConfiguration#combine(CorsConfiguration)了解更多详情。spring-doc.cadn.net.cn

要从源中了解更多信息或进行高级自定义,请参阅:spring-doc.cadn.net.cn

1.7.3. 凭证请求

将 CORS 与凭证请求一起使用需要启用allowedCredentials.请注意, 此选项与配置的域建立高级别的信任,并且还会增加 通过公开敏感的用户特定信息,Web 应用程序的攻击面 例如 Cookie 和 CSRF 令牌。spring-doc.cadn.net.cn

启用凭证还会影响已配置 CORS 通配符的处理方式:"*"spring-doc.cadn.net.cn

  • 通配符在 中未获得授权allowOrigins,但也可以 这allowOriginPatterns属性可用于匹配一组动态的源。spring-doc.cadn.net.cn

  • 当设置为allowedHeadersallowedMethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Methods响应标头的处理方法是将相关的 标头和方法。spring-doc.cadn.net.cn

  • 当设置为exposedHeaders,Access-Control-Expose-Headers响应标头 添加到配置的标头列表或通配符。虽然 CORS 规范 不允许使用通配符Access-Control-Allow-Credentials设置为true,则大多数浏览器都支持它,并且响应标头在 CORS 处理,因此通配符是 指定,而不管allowCredentials财产。spring-doc.cadn.net.cn

虽然这种通配符配置可能很方便,但建议在可能的情况下配置 一组有限的值,以提供更高级的安全性。

1.7.4.@CrossOrigin

@CrossOriginannotation 支持对带注解的控制器方法进行跨域请求,因为 以下示例显示:spring-doc.cadn.net.cn

Java
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public 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 令牌)和 应仅在适当的情况下使用。启用时allowOrigins必须是 设置为一个或多个特定域(但不是 Special Value )或 这"*"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. 全局配置

除了细粒度的 controller 方法级配置之外,您可能还希望 也定义一些全局 CORS 配置。您可以设置基于 URLCorsConfiguration映射HandlerMapping.但是,大多数应用程序都使用 WebFlux Java 配置来执行此作。spring-doc.cadn.net.cn

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

allowedCredentials默认情况下不启用,因为这会建立信任级别 公开敏感的用户特定信息(例如 cookie 和 CSRF 令牌)和 应仅在适当的情况下使用。启用时allowOrigins必须是 设置为一个或多个特定域(但不是 Special Value )或 这"*"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 Security 内置了对 CORS 的。

要配置过滤器,您可以声明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.9. 查看技术

Spring WebFlux 中视图技术的使用是可插拔的。无论您决定 使用 Lymeleaf、FreeMarker 或其他一些视图技术主要是 配置更改。本章介绍了与 Spring 集成的视图技术 WebFlux 的 Web Flux 中。我们假设您已经熟悉 View Resolutionspring-doc.cadn.net.cn

1.9.1. 百里香叶

Thymeleaf 是一个现代的服务器端 Java 模板引擎,强调自然 HTML 可以通过双击在浏览器中预览的模板,这非常 有助于独立处理 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 Framework 内置了 用于将 Spring WebFlux 与 FreeMarker 模板一起使用的集成。spring-doc.cadn.net.cn

View 配置

以下示例显示了如何将 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 的 'Settings' 和 'SharedVariables' 直接传递给 FreeMarkerConfiguration对象(由 Spring 管理)通过设置适当的 bean 属性FreeMarkerConfigurer豆。这freemarkerSettingsproperty 需要 一个java.util.Propertiesobject 和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/>元素。此元素主要允许表单显示来自 表单支持对象,并显示来自Validator在 Web 或业务层。Spring 在 FreeMarker 中也支持相同的功能, 具有用于生成表单输入元素本身的附加便捷宏。spring-doc.cadn.net.cn

Bind 宏

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

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

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

表单宏

有关 Spring 的表单宏对 FreeMarker 模板的支持的详细信息,请查阅以下内容 部分。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

EJSspring-doc.cadn.net.cn

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

雇员再培训局spring-doc.cadn.net.cn

JRubyspring-doc.cadn.net.cn

字符串模板spring-doc.cadn.net.cn

杰顿spring-doc.cadn.net.cn

Kotlin 脚本模板spring-doc.cadn.net.cn

Kotlinspring-doc.cadn.net.cn

集成任何其他脚本引擎的基本规则是,它必须实现ScriptEngineInvocable接口。
要求

您需要在 Classpath 上具有脚本引擎,其详细信息因脚本引擎而异:spring-doc.cadn.net.cn

您需要有脚本模板库。对 JavaScript 执行此作的一种方法是 通过 WebJars 进行。spring-doc.cadn.net.cn

脚本模板

您可以声明ScriptTemplateConfigurerbean 指定要使用的脚本引擎, 要加载的脚本文件、要调用的函数来渲染模板,等等。 以下示例使用 Mustache 模板和 Nashorn JavaScript 引擎:spring-doc.cadn.net.cn

Java
@Configuration
@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"
    }
}

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

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

如果您的模板技术需要一些自定义,则可以提供一个脚本,该脚本 实现自定义 render 函数。例如,Handlerbars 需要在使用模板之前编译模板,并且需要一个 polyfill 来模拟一些 浏览器工具在服务器端脚本引擎中不可用。 以下示例显示如何设置自定义 render 函数: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
    }
}
设置sharedEngineproperty 设置为false在使用非线程安全时是必需的 具有非并发性模板库的脚本引擎,例如 Handlebars 或 React 在 Nashorn 上运行。在这种情况下,由于此错误,需要 Java SE 8 Update 60,但通常是 建议在任何情况下使用最新的 Java SE 补丁版本。

polyfill.js仅定义window对象才能正常运行, 如以下代码段所示:spring-doc.cadn.net.cn

var window = {};

这个基本render.jsimplementation 在使用模板之前对其进行编译。A 生产 ready 实现还应存储和重用缓存的模板或预编译的模板。 这可以在脚本端完成,也可以完成您需要的任何自定义(管理 模板引擎配置)。 以下示例显示了如何编译模板: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

出于 Content Negotiation 目的,能够交替使用 使用 HTML 模板或其他格式(如 JSON 或 XML)渲染模型之间, 取决于客户端请求的内容类型。为了支持这样做,Spring WebFlux 提供HttpMessageWriterView,可用于从spring-webJackson2JsonEncoder,Jackson2SmileEncoder, 或Jaxb2XmlEncoder.spring-doc.cadn.net.cn

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

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

1.10. HTTP 缓存

HTTP 缓存可以显著提高 Web 应用程序的性能。HTTP 缓存 围绕Cache-Control响应标头和后续条件请求 标头,例如Last-ModifiedETag.Cache-Control建议私有(例如,浏览器) 和 public (例如 proxy) caches,了解如何缓存和重用响应。一ETagheader 的 要发出可能导致 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-Controlheader 并被接受为参数 在许多地方:spring-doc.cadn.net.cn

虽然 RFC 7234 描述了所有可能的 指令的Cache-Controlresponse 标头中,CacheControltype 接受 面向用例的方法,侧重于常见场景,如下例所示:spring-doc.cadn.net.cn

Java
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
Kotlin
// Cache for an hour - "Cache-Control: max-age=3600"
val ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS)

// Prevent caching - "Cache-Control: no-store"
val ccNoStore = CacheControl.noStore()

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
val ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic()

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值,或两者兼而有之。对于有条件的GETHEADrequests 中,您可以将响应设置为 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或阅读更多关于它们是什么的信息 在 Special Bean Types 中。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 并适应依赖项 available on the classpath — 用于 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@DateTimeFormaton fields.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 值。这适用于日期表示为带有 “input” 形式的字符串的表单 领域。但是,对于“日期”和“时间”表单字段,浏览器使用定义的固定格式 在 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)
    }
}
FormatterRegistrarSPI 系列FormattingConversionServiceFactoryBean有关何时 用FormatterRegistrar实现。

1.11.4. 验证

默认情况下,如果存在 Bean Validation 在 Classpath(例如,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实例。默认情况下,只有Acceptheader 中, 但您也可以启用基于查询参数的策略。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

如果在 Classpath 中检测到以下众所周知的模块,它还会自动注册它们: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
    }
}

支持 Content Negotiation 和呈现其他格式 通过视图分辨率(除了 HTML),您可以配置一个或多个基于 在HttpMessageWriterViewimplementation 的 API 中,它接受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 集成的视图技术的更多信息,请参见 View Technologiesspring-doc.cadn.net.cn

1.11.8. 静态资源

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

在下一个示例中,给定一个以/resources,则相对路径为 用于查找和提供相对于/static在 Classpath 上。资源 提供一年的未来到期时间,以确保最大限度地使用浏览器缓存 以及减少浏览器发出的 HTTP 请求。这Last-Modifiedheader 也是 已评估,如果存在,则为304返回 status code。以下清单显示了 示例: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 中,没有办法透明地重写 static 资源 URL,因为没有可以使用非阻塞链的视图技术 的旋转转换器。当仅提供本地资源时,解决方法是使用ResourceUrlProvider直接 (例如,通过自定义元素) 和 Block 进行。spring-doc.cadn.net.cn

请注意,当同时使用EncodedResourceResolver(例如,Gzip、Brotli 编码)和VersionedResourceResolver,它们必须按该顺序注册,以确保基于内容的 版本始终基于未编码的文件进行可靠计算。spring-doc.cadn.net.cn

对于 WebJar,/webjars/jquery/1.2.0/jquery.min.js是推荐且最有效的使用它们的方式。 相关资源位置使用 Spring Boot 开箱即用地配置(也可以配置 手动通过ResourceHandlerRegistry),并且不需要添加org.webjars:webjars-locator-coreDependency。spring-doc.cadn.net.cn

无版本的 URL,如/webjars/jquery/jquery.min.js通过WebJarsResourceResolverorg.webjars:webjars-locator-core库存在于类路径中,但代价是 类路径扫描可能会减慢应用程序启动速度。解析器可以将 URL 重写为 包括 jar 的版本,并且还可以与没有版本的传入 URL 进行匹配 — 例如,从/webjars/jquery/jquery.min.js/webjars/jquery/1.2.0/jquery.min.js.spring-doc.cadn.net.cn

基于ResourceHandlerRegistry提供更多选项 进行精细控制,例如上次修改的行为和优化的资源解析。

1.11.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用于访问解码的路径段值,并删除分号内容 (即 path 或 matrix 变量)。这意味着,与 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 处理程序。这意味着剩下的所有工作都要做 order 来处理 WebSocket 握手请求的是将WebSocketHandler到 URL 通过SimpleUrlHandlerMapping.spring-doc.cadn.net.cn

在某些情况下,可能需要创建WebSocketHandlerAdapter带有 提供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上的 implementations 类路径。spring-doc.cadn.net.cn

1.12. HTTP/2 协议

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

2. 网页客户端

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

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

2.1. 配置

创建WebClient是通过 static 工厂方法之一: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. MaxInMemorySize (最大内存大小)

编解码器 memory 以避免应用程序内存问题。默认情况下,这些设置为 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. 反应堆 Netty

要自定义 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 application 部署为 WAR),则可以声明ReactorResourceFactoryglobalResources=true(默认)来确保 Reactor Netty 全局资源在 SpringApplicationContext已关闭, 如下例所示: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由 声明 Spring 管理的 Bean 类型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. HttpComponents

以下示例演示如何自定义 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()method 可用于声明如何提取响应。例如: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>作为 body 进行。请注意, content 会自动设置为application/x-www-form-urlencodedFormHttpMessageWriter.以下示例演示如何使用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. Multipart 数据

要发送多部分数据,您需要提供MultiValueMap<String, ?>其值为 也Object表示部分内容的实例或HttpEntity表示内容和 Headers 的 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对于每个部分。内容 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-样式,通过内置的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 对象,它有助于确保始终使用响应内容。 当 filter 以某种方式处理响应时,必须格外小心地始终使用 其内容,或者以其他方式将其传播到下游的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.Builderlevel 允许您将属性插入到所有请求中, 例如,可以在 Spring MVC 应用程序中使用它来填充 请求属性基于ThreadLocal数据。spring-doc.cadn.net.cn

2.7. 背景信息

属性提供了一种将信息传递给过滤器的便捷方法 chain,但它们只影响当前请求。如果要传递 传播到嵌套的其他请求,例如通过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()
}

但是,如果需要进行多个调用,则避免在每个调用上阻塞会更有效 response 中,而是等待组合的结果: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())
    }

以上只是一个例子。还有许多其他 pattern 和运算符可用于放置 一起进行许多远程调用(可能是一些嵌套的 相互依赖,直到最后都没有阻塞。spring-doc.cadn.net.cn

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

2.9. 测试

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

3. 网络套接字

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

3.1. WebSocket 简介

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

WebSocket 交互以使用 HTTPUpgrade页眉 进行升级,或者在本例中切换到 WebSocket 协议。以下示例 显示了这样的交互:spring-doc.cadn.net.cn

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket (1)
Connection: Upgrade (2)
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
1 Upgrade页眉。
2 使用Upgrade连接。

支持 WebSocket 的服务器返回输出,而不是通常的 200 状态代码 类似于以下内容:spring-doc.cadn.net.cn

HTTP/1.1 101 Switching Protocols (1)
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
1 协议切换

握手成功后,HTTP 升级请求的基础 TCP 套接字将保留 open 以继续发送和接收消息。spring-doc.cadn.net.cn

有关 WebSockets 工作原理的完整介绍超出了本文档的范围。 请参阅 RFC 6455、HTML5 的 WebSocket 章节或许多介绍中的任何一个 Web 上的教程。spring-doc.cadn.net.cn

请注意,如果 WebSocket 服务器在 Web 服务器(例如 nginx)后面运行,则 可能需要对其进行配置,以便将 WebSocket 升级请求传递给 WebSocket 服务器。同样,如果应用程序在云环境中运行,请检查 云提供商与 WebSocket 支持相关的说明。spring-doc.cadn.net.cn

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-Protocol标头。 如果没有这些,他们需要提出自己的惯例。spring-doc.cadn.net.cn

3.1.2. 何时使用 WebSockets

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

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

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

另请记住,在 Internet 上,您无法控制的限制性代理 可能会排除 WebSocket 交互,因为它们未配置为传递Upgrade标头,或者因为它们关闭了看起来空闲的长期连接。这 意味着将 WebSocket 用于防火墙内的内部应用程序是 比面向公众的应用程序更直接的决定。spring-doc.cadn.net.cn

3.2. WebSocket API

Spring Framework 提供了一个 WebSocket API,您可以使用它来编写 client- 和 处理 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 配置,则没有任何内容 进一步作,否则如果不使用 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

handlemethod 的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

当入站和出站消息流组合在一起时,无需 检查连接是否打开,因为 Reactive Streams 向 end activity 发出信号。 入站流接收到完成或错误信号,出站流 接收取消信号。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>,在 receiving complete 时完成。
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>,在 receiving complete 时完成。
对于嵌套的异步作,您可能需要调用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 的 该参考在 Data Buffers and Codecs 一节中有更多关于这方面的内容。要理解的关键点是,在某些 像 Netty 这样的服务器,字节缓冲区是池化的和引用计数的,并且必须释放 当使用以避免内存泄漏时。spring-doc.cadn.net.cn

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

3.2.4. 握手

WebSocketHandlerAdapterdelegates 传递给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 Config 的相应部分所示的属性,或者如果 不使用 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 设置。如果同时指定了这两者,则使用combinemethod 开启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 客户端的 Web 节点。spring-doc.cadn.net.cn

4. 测试

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

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

5. RS锁

本节描述了 Spring 框架对 RSocket 协议的支持。spring-doc.cadn.net.cn

5.1. 概述

RSocket 是一种用于通过 TCP 进行多路复用、双工通信的应用程序协议, WebSocket 和其他字节流传输,使用以下交互之一 模型:spring-doc.cadn.net.cn

建立初始连接后,“client” 与 “server” 的区别将丢失,因为 双方都变得对称,并且每一方都可以发起上述交互之一。 这就是为什么在协议中将参与方称为 “requester” 和 “responder” 的原因 而上述交互称为 “请求流” 或简称为 “请求”。spring-doc.cadn.net.cn

以下是 RSocket 协议的主要功能和优势:spring-doc.cadn.net.cn

  • 跨网络边界的 Reactive Streams 语义 — 对于流式请求,例如Request-StreamChannel、背压信号 在请求者和响应者之间移动,允许请求者在 源,从而减少对网络层拥塞控制的依赖,以及需求 用于网络级别或任何级别的缓冲。spring-doc.cadn.net.cn

  • 请求限制 — 此功能被命名为 “租赁” ,以LEASEframe 那个 可以从每一端发送,以限制另一端允许的请求总数 在给定的时间内。租约会定期续订。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 用于运输。这意味着 应用程序中来自 Reactive Streams Publishers 的信号以透明方式传播 通过 RSocket 跨网络。spring-doc.cadn.net.cn

5.1.1. 协议

RSocket 的好处之一是它在 wire 上具有明确定义的行为,并且 易于阅读的规范以及一些协议扩展。因此它是 阅读规范是个好主意,独立于语言实现和更高级别 框架 API 的 API 中。本节提供了简洁的概述,以建立一些上下文。spring-doc.cadn.net.cn

最初,客户端通过一些低级流传输(如 作为 TCP 或 WebSocket 发送,并发送一个SETUPframe 添加到服务器中,为 连接。spring-doc.cadn.net.cn

服务器可能会拒绝SETUPframe,但通常在发送之后(对于客户端) 和 received(对于服务器),双方都可以开始发出请求,除非SETUP指示使用租赁语义来限制请求数,在这种情况下 双方都必须等待LEASEframe 以允许发出请求。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, 响应方必须遵循来自请求方的需求信号。Demand 表示为 消息数。初始需求在REQUEST_STREAMREQUEST_CHANNEL框架。后续需求通过REQUEST_N框架。spring-doc.cadn.net.cn

每一方还可以通过METADATA_PUSHframe 的 与任何单个请求有关,但与整个连接有关。spring-doc.cadn.net.cn

RSocket 消息包含数据和元数据。元数据可用于发送路由、 证券令牌等数据和元数据的格式可以不同。每个 Mime 类型 在SETUPframe 并应用于给定连接上的所有请求。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使用声明式运算符和透明 back 压力支持。spring-doc.cadn.net.cn

RSocket Java 中的 API 有意做到最小和基本。它侧重于协议 功能,并将应用程序编程模型(例如 RPC codegen 与其他模型)保留为 更高层次,独立关注。spring-doc.cadn.net.cn

主合约 io.rsocket.RSocket 使用Mono表示 单条消息 /Flux消息流,以及io.rsocket.Payload实际的 message 中访问数据和元数据作为字节缓冲区。这RSocket使用 Contract 对称。对于请求,应用程序将获得一个RSocket执行 请求与。为了响应,应用程序实现了RSocket处理请求。spring-doc.cadn.net.cn

这并不是一个详尽的介绍。在大多数情况下,Spring 应用程序 不必直接使用其 API。但是,观察或试验可能很重要 使用 RSocket 独立于 Spring。RSocket Java 存储库包含许多示例应用程序,这些应用程序 演示其 API 和协议功能。spring-doc.cadn.net.cn

5.1.3. Spring 支持

spring-messagingmodule 包含以下内容:spring-doc.cadn.net.cn

spring-webmodule 包含EncoderDecoderJackson 等实现 CBOR/JSON 和 Protobuf 的 RSocket 应用程序可能需要。它还包含PathPatternParser可以插入以进行高效的路由匹配。spring-doc.cadn.net.cn

Spring Boot 2.2 支持通过 TCP 或 WebSocket 建立 RSocket 服务器,包括 在 WebFlux 服务器中通过 WebSocket 公开 RSocket 的选项。还有 Client 支持和自动配置RSocketRequester.BuilderRSocketStrategies. 有关更多详细信息,请参阅 Spring Boot 参考中的 RSocket 部分spring-doc.cadn.net.cn

Spring Security 5.2 提供了 RSocket 支持。spring-doc.cadn.net.cn

Spring 集成 5.2 提供了入站和出站网关来与 RSocket 交互 客户端和服务器。有关更多详细信息,请参见 Spring 集成参考手册。spring-doc.cadn.net.cn

Spring Cloud 网关支持 RSocket 连接。spring-doc.cadn.net.cn

5.2. RSocketRequester

RSocketRequester提供 Fluent API 来执行 RSocket 请求、接受和 返回 data 和 metadata 的对象,而不是低级数据缓冲区。可以使用 对称地,从 Client 端发出请求,以及从 Server 发出请求。spring-doc.cadn.net.cn

5.2.1. 客户端请求者

要获取RSocketRequester在客户端是连接到一个服务器,它涉及 发送 RSocketSETUPFrame 替换为 Connection Settings。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

对于 data,默认 MIME 类型派生自第一个配置的Decoder.为 metadata,默认的 MIME 类型是 Composite metadata,它允许多个 每个请求的元数据值和 MIME 类型对。通常,两者都不需要更改。spring-doc.cadn.net.cn

Data and metadata 中的SETUPframe 是可选的。在服务器端,可以使用@ConnectMapping方法处理 connection 和SETUP框架。元数据可用于连接 级别安全性。spring-doc.cadn.net.cn

策略

RSocketRequester.Builder接受RSocketStrategies以配置请求者。 您需要使用它来提供编码器和解码器,用于数据的 (de) 序列化和 metadata 值。默认情况下,只有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 注册响应方。

请注意,以上只是专为 client 的编程注册而设计的快捷方式 反应。对于客户端响应者处于 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

另请参阅 Annotated Responders ,以了解有关编程模型的更多信息。spring-doc.cadn.net.cn

高深

RSocketRequesterBuilder提供一个回调来暴露底层io.rsocket.core.RSocketConnector有关 Keepalive 的更多配置选项 intervals、session resumption、interceptor 等。您可以配置选项 在该级别,如下所示: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. 服务器请求者

要从服务器向连接的客户端发出请求,只需获取 来自服务器的已连接客户端的 requester。spring-doc.cadn.net.cn

Annotated Responders 中@ConnectMapping@MessageMapping方法支持RSocketRequester论点。使用它来访问连接的请求者。保留 请注意@ConnectMapping方法本质上是SETUPframe which 必须在请求开始之前处理。因此,请求在一开始就必须是 与处理分离。例如: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 在 suspending 函数中执行处理。

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因为发送了一个值和一个流 of values 的值。在大多数情况下,您不需要考虑这个问题,只要 输入和输出的选择与 RSocket 交互类型以及 input 和 响应方所需的输出。无效组合的唯一示例是多对一。spring-doc.cadn.net.cn

data(Object)method 也接受任何 Reactive StreamsPublisher包括FluxMono,以及在ReactiveAdapterRegistry.对于多值PublisherFlux,这将产生 相同类型的值,请考虑使用重载的data避免 type checks 和Encoderlookup on each element: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方法 handle 连接级事件 (Setup 和 Metadata Push)。支持带注释的响应者 对称地,用于从服务器端响应和从客户端响应。spring-doc.cadn.net.cn

5.3.1. 服务器响应器

要在服务器端使用带注释的响应程序,请添加RSocketMessageHandler到你的 Spring 要检测的配置@Controllerbean 替换为@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-webmodule 进行编码实现。spring-doc.cadn.net.cn

默认情况下SimpleRouteMatcher用于通过AntPathMatcher. 我们建议插入PathPatternRouteMatcherspring-web为 高效的路由匹配。RSocket 路由可以是分层的,但不是 URL 路径。 默认情况下,两个路由匹配器都配置为使用 “.” 作为分隔符,并且没有 URL 解码方式与 HTTP URL 一样。spring-doc.cadn.net.cn

RSocketMessageHandler可通过以下方式进行配置RSocketStrategies这可能很有用,如果 您需要在同一进程中在 Client 端和 Server 之间共享配置: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

返回值应为一个或多个要序列化为响应的 Object 负载。这可以是异步类型,例如MonoFlux、具体值或 也void或无值异步类型,例如Mono<Void>.spring-doc.cadn.net.cn

RSocket 交互类型中,一个@MessageMapping方法 Supports 的确定依据 input 的基数(即@Payload参数)和输出,其中 cardinality 的含义如下: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

对于 input,这意味着该方法没有@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

Fire-and-Forget, Request-Responsespring-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_PUSHframe 的metadataPush(Payload)io.rsocket.RSocket.spring-doc.cadn.net.cn

@ConnectMapping方法支持与 @MessageMapping 相同的参数,但基于元数据和数据SETUPMETADATA_PUSH框架。@ConnectMapping可以有一个模式来缩小处理范围 在元数据中具有路由的特定连接,或者如果未声明任何模式 则所有连接都匹配。spring-doc.cadn.net.cn

@ConnectMapping方法不能返回数据,必须使用voidMono<Void>作为返回值。如果处理返回新的 connection,则连接将被拒绝。处理不得搁置以使 请求发送到RSocketRequester对于连接。有关详细信息,请参阅 Server Requesterspring-doc.cadn.net.cn

5.4. 元数据提取器

响应方必须解释元数据。复合元数据允许独立 格式化的元数据值(例如,用于路由、安全、跟踪),每个值都有自己的 MIME 类型。应用程序需要一种方法来配置元数据 MIME 类型以支持,以及一种方法 以访问提取的值。spring-doc.cadn.net.cn

MetadataExtractor是一个合约,用于获取序列化元数据并返回解码 名称-值对,然后可以像 Headers 一样按名称访问,例如通过@Header在带注释的处理程序方法中。spring-doc.cadn.net.cn

DefaultMetadataExtractor可以给出Decoder实例来解码元数据。出 它内置了对 “message/x.rsocket.routing.v0” 的支持,它将其解码为该框String并保存在 “route” 键下。对于任何其他 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并在内部使用它来编写异步 logic 并提供 Reactive Streams 支持。通常,WebFlux API 返回FluxMono(因为它们是内部使用的)并宽容地接受任何 Reactive StreamsPublisherimplementation 作为输入。的使用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作为返回值和一个 Reactive StreamsPublisher作为输入。当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