文档的这一部分涵盖了对基于反应流API构建的反应式堆栈Web应用程序的支持,以在非阻塞服务器(如Netty、Undertow和Servlet容器)上运行。单独的章节涵盖了Spring WebFlux框架,WebClient,对测试的支持,以及反应库。有关Servlet堆栈的Web应用程序,请参阅Web on Servlet Stack

1. Spring WebFlux

Spring框架中包含的原始Web框架Spring Web MVC是专门为Servlet API和Servlet容器构建的。反应式堆栈Web框架Spring WebFlux是后来在5.0版中添加的。它是完全非阻塞的,支持反应流反压力,并在Netty、Undertow和Servlet容器等服务器上运行。

这两个Web框架都反映了它们的源模块的名称(Spring-webmvcSpring-webflow),并在Spring框架中共存。每个模块都是可选的。应用程序可以使用一个或另一个模块,或者在某些情况下,同时使用两个 - ,例如,带有反应式WebClient的Spring MVC控制器。

1.1. Overview

为什么要创建Spring WebFlux?

部分原因是需要一个非阻塞的Web堆栈来处理少量线程的并发,并使用更少的硬件资源进行扩展。Servlet非阻塞I/O与Servlet API的其余部分不同,在Servlet API中,约定是同步的(FilterServlet)或阻塞的(getParametergetPart)。这就是新的通用API作为跨任何非阻塞运行时的基础的动机。这一点很重要,因为服务器(如Netty)在异步、非阻塞空间中已经建立得很好了。

答案的另一部分是函数式编程。就像在Java 5中添加注释创造了机会(如带注释的REST控制器或单元测试)一样,Java 8中添加的lambda表达式为Java中的函数式API创造了机会。这对于允许以声明方式组合异步逻辑的非阻塞应用程序和延续风格的API(如CompletableFutureReactive X所普及的)是一个福音。在编程模型级别,Java 8支持Spring WebFlux在带注释的控制器旁边提供功能齐全的Web端点。

1.1.1. Define “Reactive”

我们谈到了“非阻塞”和“功能性”,但是反应性是什么意思呢?

术语“反应式”是指围绕更改 - 网络组件(对I/O事件做出反应)、UI控制器对鼠标事件作出反应等而构建的编程模型。从这个意义上说,非封锁是被动的,因为我们现在不是被封锁,而是在操作完成或数据可用时对通知做出反应。

还有另一种重要的机制,我们在Spring团队中将其与“反应性”联系在一起,那就是非阻挡反压。在同步的命令式代码中,阻塞调用是迫使调用者等待的一种自然形式的反压力。在非阻塞代码中,控制事件的速率变得很重要,这样快速生成器就不会淹没其目标。

反应流是一个小规范(也是在Java 9中采用的),它定义了具有背压的异步组件之间的交互。例如,数据存储库(充当发布者)可以生成HTTP服务器(充当订阅者)随后可以写入响应的数据。反应流的主要目的是让订阅服务器控制发布服务器生成数据的速度或速度。

Common question: what if a publisher cannot slow down?
The purpose of Reactive Streams is only to establish the mechanism and a boundary. If a publisher cannot slow down, it has to decide whether to buffer, drop, or fail.

1.1.2. Reactive API

反应式流对于互操作性起着重要的作用。它对库和基础设施组件很感兴趣,但作为应用程序API用处较小,因为它级别太低。应用程序需要更高级别和更丰富的功能API来组成类似于Java8<代码>流 API的异步逻辑 - ,但不仅仅是针对集合。这就是反动图书馆播放的作用。

反应器是Spring WebFlux首选的反应库。它提供了 FluxAPI类型,通过与Reactive X操作符词汇表一致的丰富操作符集来处理0..1(Mono)和0..N(Flux)的数据序列。反应器是一个反应流库,因此,它的所有操作员都支持无堵塞反压。Reader非常关注服务器端Java。它是与Spring密切合作开发的。

WebFlux需要反应器作为核心依赖项,但它可以通过反应流与其他反应库进行互操作。一般来说,WebFlux API接受普通发布者作为输入,在内部将其适应于反应器类型,使用该类型,并返回FluxMono作为输出。因此,您可以将任何发布者作为输入传递,并且可以对输出应用操作,但您需要调整输出以与另一个反应库一起使用。只要可行(例如,带注释的控制器),WebFlux就会透明地适应RxJava或其他反应库的使用。有关详细信息,请参阅反应库

In addition to Reactive APIs, WebFlux can also be used with Coroutines APIs in Kotlin which provides a more imperative style of programming. The following Kotlin code samples will be provided with Coroutines APIs.

1.1.3. Programming Models

Spring-web模块包含支撑Spring WebFlux的反应性基础,包括HTTP抽象、用于受支持服务器的反应性流适配器编解码器,以及与Servlet API类似但具有非阻塞契约的核心WebHandlerAPI。

在此基础上,Spring WebFlux提供了两种编程模型可供选择:

  • 带注释的控制器:与Spring MVC一致,并基于来自Spring-web模块的相同注释。Spring MVC和WebFlux控制器都支持反应式(反应和RxJava)返回类型,因此,很难区分它们。一个显著的区别是WebFlux还支持反应性@RequestBody参数。

  • 函数端点:基于Lambda的轻量级函数式编程模型。您可以将其视为一个小型库或一组实用程序,应用程序可以使用它们来路由和处理请求。带注释的控制器的最大区别在于,应用程序从头到尾负责请求处理,而不是通过注释声明意图并被回调。

1.1.4. Applicability

Spring MVC还是WebFlux?

这是一个很自然的问题,但却建立了一种不合理的二分法。实际上,两者共同努力,扩大了可用选项的范围。这两者都是为彼此的连续性和一致性而设计的,它们是并排可用的,双方的反馈对双方都有利。下图显示了两者之间的关系、共同之处以及各自唯一支持的内容:

spring mvc and webflux venn

我们建议您考虑以下具体要点:

  • 如果您有一个运行良好的Spring MVC应用程序,则不需要进行更改。命令式编程是编写、理解和调试代码的最简单方法。您有最多的库可供选择,因为从历史上看,大多数库都是阻塞的。

  • 如果您已经在购买非阻塞Web堆栈,Spring WebFlux提供了与该领域中的其他产品相同的执行模型优势,并且还提供了服务器(Netty、Tomcat、Jetty、Undertow和Servlet容器)、编程模型(带注释的控制器和功能Web端点)和反应库(Reactive、RxJava或其他)的选择。

  • 如果您对用于Java 8 lambdas或Kotlin的轻量级、功能性Web框架感兴趣,您可以使用Spring WebFlux功能性Web端点。对于要求不那么复杂的较小应用程序或微服务来说,这也是一个很好的选择,因为它们可以受益于更高的透明度和控制。

  • 在微服务体系结构中,您可以使用Spring MVC或Spring WebFlux控制器或Spring WebFlux功能端点混合使用应用程序。在这两个框架中都支持相同的基于注释的编程模型,这使得重用知识变得更容易,同时还可以为正确的工作选择正确的工具。

  • 评估应用程序的一种简单方法是检查其依赖项。如果您有阻塞持久化API(JPA、JDBC)或网络API可用,那么Spring MVC至少是常见架构的最佳选择。使用Reader和RxJava在单独的线程上执行阻塞调用在技术上是可行的,但您不会充分利用非阻塞Web堆栈。

  • 如果您有一个调用远程服务的Spring MVC应用程序,请尝试使用反应式WebClient。您可以直接从Spring MVC控制器方法返回反应类型(Reactive、RxJava、或其他)。每个呼叫的延迟越大或呼叫之间的相互依赖越大,好处就越显著。Spring MVC控制器也可以调用其他反应性组件。

  • 如果您有一个庞大的团队,请记住,在向非阻塞、函数式和声明性编程转变的过程中需要经历陡峭的学习曲线。无需完全切换即可启动的一种实用方法是使用反应式WebClient。除此之外,从小处做起,衡量好处。我们预计,对于广泛的应用来说,这种转变是不必要的。如果您不确定要寻找什么好处,可以从了解非阻塞I/O如何工作(例如,单线程Node.js上的并发性)及其影响开始。

1.1.5. Servers

Tomcat、Jetty、Servlet容器以及Netty和Undertow等非Servlet运行时都支持Spring WebFlux。所有服务器都适用于低级别的通用API,因此可以跨服务器支持较高级别的编程模型。

Spring WebFlux没有启动或停止服务器的内置支持。但是,只需几行代码即可从Spring配置和WebFlux基础设施中组装应用程序并运行它。

Spring Boot有一个WebFlux启动器,可以自动执行这些步骤。默认情况下,Starter使用Netty,但通过更改Maven或Gradle依赖项很容易切换到Tomcat、Jetty或Undertow。Spring Boot默认使用Netty,因为它更广泛地用于异步、非阻塞空间,并允许客户端和服务器共享资源。

Tomcat和Jetty可以与Spring MVC和WebFlux一起使用。然而,请记住,它们的使用方式非常不同。Spring MVC依赖于Servlet阻塞I/O,并允许应用程序在需要时直接使用Servlet API。Spring WebFlux依赖于Servlet非阻塞I/O,并在低级适配器后面使用Servlet API。它不会暴露出来供直接使用。

对于Undertow,Spring WebFlux直接使用Undertow API,而不使用Servlet API。

1.1.6. Performance

表演有很多特点和意义。反应性和非阻塞性通常不会使应用程序运行得更快。在某些情况下,它们可以(例如,如果使用WebClient并行运行远程调用)。总体而言,以非阻塞方式做事情需要更多工作,这可能会略微增加所需的处理时间。

反应式和非阻塞的主要预期好处是能够以较少的固定线程数和较少的内存进行扩展。这使应用程序在负载下更具弹性,因为它们以更可预测的方式进行扩展。然而,为了观察这些好处,您需要有一些延迟(包括缓慢和不可预测的网络I/O的混合)。这就是被动堆栈开始展示其优势的地方,差异可能是巨大的。

1.1.7. Concurrency Model

Spring MVC和Spring WebFlux都支持带注释的控制器,但在并发模型和阻塞和线程的默认假设方面有一个关键区别。

在Spring MVC(以及一般的Servlet应用程序)中,假定应用程序可以阻塞当前线程(例如,用于远程调用)。出于这个原因,Servlet容器使用一个大的线程池来吸收请求处理期间的潜在阻塞。

在Spring WebFlux(以及一般的非阻塞服务器)中,假定应用程序不阻塞。因此,非阻塞服务器使用一个小的固定大小的线程池(事件循环工作线程)来处理请求。

“To scale” and “small number of threads” may sound contradictory but to never block the current thread (and rely on callbacks instead) means that you do not need extra threads, as there are no blocking calls to absorb.
Invoking a Blocking API

如果您确实需要使用阻塞库,该怎么办?反应器和RxJava都提供了PublishOn运算符,以在不同的线程上继续处理。这意味着有一个容易的逃生出口。但是,请记住,阻塞API并不适合这种并发模型。

Mutable State

在反应器和RxJava中,您通过操作符声明逻辑。在运行时,会形成一个反应式管道,其中在不同的阶段按顺序处理数据。这样做的一个关键好处是,它使应用程序不必保护可变状态,因为该管道中的应用程序代码永远不会被并发调用。

Threading Model

在运行Spring WebFlux的服务器上,您应该看到哪些线程?

  • 在一个“普通”的Spring WebFlux服务器上(例如,没有数据访问或其他可选的依赖项),您可以预期一个线程用于服务器,多个线程用于请求处理(通常与CPU核心的数量一样多)。然而,为了同时支持Servlet(阻塞)I/O和Servlet 3.1(非阻塞)I/O使用,Servlet容器可能从更多的线程(例如,Tomcat上的10个线程)开始。

  • 被动式WebClient以事件循环样式运行。因此,您可以看到与此相关的少量固定数量的处理线程(例如,反应器-http-nio-和反应器Netty连接器)。但是,如果反应器Netty同时用于客户端和服务器,则默认情况下两者共享事件循环资源。

  • Reactive和RxJava提供线程池抽象,称为调度器,与用于将处理切换到不同线程池的PublishOn操作符一起使用。调度器的名称暗示了特定的并发策略 - ,例如,“并行”(对于CPU受限的工作具有有限数量的线程)或“弹性”(对于具有大量线程的I/O受限的工作)。如果您看到这样的线程,则意味着某些代码正在使用特定的线程池Scheduler策略。

  • 数据访问库和其他第三方依赖项也可以创建和使用它们自己的线程。

Configuring

Spring框架不支持启动和停止服务器。要为服务器配置线程模型,您需要使用特定于服务器的配置API,或者,如果您使用的是Spring Boot,请检查每个服务器的Spring Boot配置选项。您可以直接配置WebClient。有关所有其他库的信息,请参阅各自的文档。

1.2. Reactive Core

Spring-web模块包含对反应式Web应用程序的以下基本支持:

  • 对于服务器请求处理,有两个级别的支持。

    • HttpHandler:用于处理具有非阻塞I/O和反应流反压的HTTP请求的基本契约,以及用于反应器Netty、Undertow、Tomcat、Jetty和任何Servlet容器的适配器。

    • WebHandlerAPI:稍高级别的通用Web API,用于请求处理,在此基础上构建具体的编程模型,如带注释的控制器和功能端点。

  • 对于客户端,有一个基本的ClientHttpConnector契约,用于执行具有非阻塞I/O和反应性流反压的HTTP请求,以及用于反应器网络、反应性Jetty HttpClientapacheHttpComponents的适配器。应用程序中使用的更高级别的WebClient构建在此基本契约之上。

  • 对于客户端和服务器,编解码器用于序列化和反序列化HTTP请求和响应内容。

1.2.1. HttpHandler

HttpHandler是一个简单的协定,只有一个方法来处理请求和响应。它是有意最小化的,其主要目的也是唯一的目的是对不同的HTTP服务器API进行最小限度的抽象。

下表介绍了支持的服务器API:

Server name Server API used Reactive Streams support

内蒂

Netty API

反应堆网

暗流

Untow API

弹簧-网:向反应性河流大桥的回流

汤姆猫

Servlet非阻塞I/O;用于读写ByteBuffers与byte[]的Tomcat API

Spring-Web:Servlet非阻塞I/O到反应式流桥接

码头

Servlet非阻塞I/O;编写ByteBuffer与byte[]的Jetty API

Spring-Web:Servlet非阻塞I/O到反应式流桥接

Servlet容器

Servlet非阻塞I/O

Spring-Web:Servlet非阻塞I/O到反应式流桥接

下表说明了服务器依赖关系(另请参阅支持的版本):

Server name Group id Artifact name

反应堆网

Io.projectreactor.netty

反应堆网

暗流

Io.undertow

底流岩心

汤姆猫

Org.apache.tomcat.embed

Tomcat嵌入式内核

码头

Org.eclipse.jetty

Jetty-服务器、Jetty-Servlet

下面的代码片段显示了将HttpHandler适配器与每个服务器API一起使用:

反应堆网

Java
Kotlin
HttpHandler handler = ...
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create().host(host).port(port).handle(adapter).bind().block();

             

下面

Java
Kotlin
HttpHandler handler = ...
UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
server.start();

             

Tomcat

Java
Kotlin
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();

             

Jetty

Java
Kotlin
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();

             

Servlet容器

要将WAR部署到任何Servlet容器,您可以扩展并在WAR中包含AbstractReactiveWebInitializer。该类使用ServletHttpHandlerAdapter包装HttpHandler,并将其注册为Servlet

1.2.2. WebHandler API

包构建在<代码><代码>HttpHandler契约之上,以提供通用Web API,用于通过多个WebExceptionHandler,多个<代码><代码>WebFilter组件链和单个WebHandler 组件来处理请求。该链可以与WebHttpHandlerBuilder放在一起,只需指向一个SpringApplicationContext,其中的组件是自动检测的,和/或通过向构建器注册组件。

HttpHandler的简单目标是抽象不同HTTP服务器的使用,而WebHandlerAPI的目标是提供Web应用程序中常用的更广泛的功能集,例如:

  • 具有属性的用户会话。

  • 请求属性。

  • 已为请求解析区域设置主体

  • 访问已解析和缓存的表单数据。

  • 多部分数据的抽象。

  • 还有更多..。

Special bean types

下表列出了WebHttpHandlerBuilder可以在Spring ApplicationContext中自动检测的组件,或者可以直接注册到它的组件:

Bean name Bean type Count Description

<;任何>;

WebExceptionHandler

0..N

提供对来自WebFilter实例链和目标WebHandler的异常的处理。有关更多详细信息,请参阅异常

<;任何>;

WebFilter

0..N

将侦听样式逻辑应用于筛选器链的其余部分之前和之后以及目标WebHandler。有关详细信息,请参阅筛选器

WebHandler

WebHandler

1

请求的处理程序。

WebSessionManager

WebSessionManager

0..1

WebSession实例的管理器通过ServerWebExchange上的方法公开。默认情况下为DefaultWebSessionManager

serverCodecConfigurer

ServerCodecConfigurer

0..1

用于访问用于分析表单数据和多部分数据的HttpMessageReader实例,然后通过ServerWebExchange上的方法公开这些数据。默认为ServerCodecConfigurer.create()

本地上下文解析器

LocaleConextResolver

0..1

LocaleContext的冲突解决程序通过ServerWebExchange上的方法公开。默认情况下为AcceptHeaderLocaleContextResolver

ForwardedHeaderTransformer

ForwardedHeaderTransformer

0..1

用于处理转发的类型标头,方法是提取并移除它们或仅移除它们。默认情况下不使用。

Form Data

ServerWebExchange公开以下访问表单数据的方法:

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

              

DefaultServerWebExchange使用配置的HttpMessageReader将表单数据(application/x-www-form-urlencoded)解析为MultiValueMap。默认情况下,FormHttpMessageReader配置为供ServerCodecConfigurerBean使用(请参阅Web Handler API)。

Multipart Data

ServerWebExchange公开了以下访问分块数据的方法:

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

              

DefaultServerWebExchange使用配置的HttpMessageReader<;MultiValueMap<;String,部件多部分/表单数据内容解析为多值映射。默认情况下,这是DefaultPartHttpMessageReader,它没有任何第三方依赖项。或者,也可以使用SynchronossPartHttpMessageReader,它基于Synchronoss NIO多部分库。两者都是通过ServerCodecConfigurerBean配置的(请参阅Web Handler API)。

要以流方式解析多部分数据,可以使用从PartEventHttpMessageReader返回的Flux<;PartEvent>;,而不是使用@RequestPart,因为这意味着Map通过名称类似于访问单个部分,因此需要完整地解析多部分数据。相反,您可以使用@RequestBody将内容解码为Flux<;PartEvent&>,而无需收集到MultiValueMap

Forwarded Headers

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

RFC 7239定义了转发的HTTP标头,代理可以使用该标头提供有关原始请求的信息。还有其他非标准标头,包括X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SSLX-Forwarded-Prefix

ForwardedHeaderTransformer是一个组件,它根据转发的标头修改请求的主机、端口和方案,然后删除这些标头。如果将其声明为名为forwardedHeaderTransformer的Bean,则将检测到并使用它。

转发的标头有安全方面的考虑,因为应用程序无法知道这些标头是由代理按预期添加的,还是由恶意客户端添加的。这就是为什么应该将信任边界处的代理配置为删除来自外部的不受信任的转发流量的原因。您还可以将ForwardedHeaderTransformer配置为emoveOnly=true,在这种情况下,它将移除但不使用标头。

In 5.1 ForwardedHeaderFilter was deprecated and superseded by ForwardedHeaderTransformer so forwarded headers can be processed earlier, before the exchange is created. If the filter is configured anyway, it is taken out of the list of filters, and ForwardedHeaderTransformer is used instead.

1.2.3. Filters

WebHandlerAPI中,您可以使用WebFilter在筛选器的其余处理链和目标WebHandler之前和之后应用截取样式的逻辑。在使用WebFlux配置时,注册WebFilter非常简单,只需将其声明为一个Spring Bean,并(可选)通过在Bean声明上使用@Order或通过实现Order来表示优先级。

CORS

Spring WebFlux通过控制器上的注释为CORS配置提供细粒度支持。然而,当您将其与Spring Security一起使用时,我们建议依赖内置的CorsFilter,它必须排在Spring Security的过滤器链之前。

有关详细信息,请参阅CORSCORSWebFilter一节。

1.2.4. Exceptions

WebHandler接口中,可以使用WebExceptionHandler处理来自WebFilter实例链和目标WebHandler的异常。在使用WebFlux配置时,注册WebExceptionHandler非常简单,只需将其声明为一个Spring Bean,并(可选)通过在Bean声明上使用@Order或通过实现Order来表示优先级。

下表介绍了可用的WebExceptionHandler实现:

Exception Handler Description

ResponseStatusExceptionHandler

通过将响应设置为异常的href=“0”>ResponseStatusException状态代码来提供对

WebFluxResponseStatusExceptionHandler

ResponseStatusExceptionHandler的扩展,它还可以确定任何异常上的@ResponseStatus注释的HTTP状态代码。

此处理程序在WebFlux配置中声明。

1.2.5. Codecs

Spring-webSpring-core模块通过具有反应性流反压力的非阻塞I/O,为序列化和反序列化进出更高级别对象的字节内容提供支持。以下内容描述了此支持:

  • 编码器解码器是独立于HTTP对内容进行编码和解码的低级契约。

  • HttpMessageReaderHttpMessageWriter是对HTTP消息内容进行编码和解码的契约。

  • 编码器可以用EncoderHttpMessageWriter包装以使其适合在Web应用程序中使用,而Decoder可以用DecoderHttpMessageReader包装。

  • DataBuffer抽象了不同的字节缓冲区表示(例如,NettyByteBufjava.nio.ByteBuffer等)。这是所有编解码器都在做的工作。有关此主题的更多信息,请参阅“Spring Core”部分中的数据缓冲区和编解码器

Spring-core模块提供byte[]ByteBufferDataBuffer资源字符串编码器和解码器实现。Spring-web模块为表单数据、多部分内容、服务器发送的事件等提供了Jackson JSON、Jackson SMILE、JAXB2、协议缓冲区和其他编解码器,以及仅Web的HTTP消息读取器和写入器实现。

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

Jackson JSON

如果存在Jackson库,则同时支持JSON和二进制JSON(smile)。

Jackson2Decoder工作方式如下:

  • Jackson的异步、非阻塞解析器用于将字节块的流聚合成TokenBuffer的,每个TokenBuffer表示一个JSON对象。

  • 每个TokenBuffer都被传递给Jackson的对象映射器以创建更高级别的对象。

  • 当解码到单值发布器时(例如Mono),有一个TokenBuffer

  • 当解码到多值发布器(例如,Flux)时,一旦接收到用于完全形成的对象的足够字节,每个TokenBuffer就被传递给对象映射器。输入内容可以是JSON数组,也可以是任何行分隔的JSON格式,如NDJSON、JSON Lines或JSON文本序列。

Jackson2Encode的工作方式如下:

  • 对于单值发布者(例如Mono),只需通过对象映射器将其序列化即可。

  • 对于具有应用程序/json的多值发布程序,默认情况下使用Flux#Collect ToList()收集值,然后序列化结果集合。

  • 对于具有流媒体类型(如应用程序/x-ndjson或application/stream+x-jackson-smile,)的多值发布者,使用行分隔开的JSON格式分别编写和刷新每个值。可以向编码器注册其他流媒体类型。

  • 对于SSE,每个事件都会调用Jackson2Encode,并刷新输出以确保无延迟交付。

默认情况下,Jackson2EncodeJackson2Decoder都不支持字符串类型的元素。相反,默认假设是一个字符串或一个字符串序列表示将由CharSequenceEncode呈现的序列化JSON内容。如果您需要的是从Flux<;字符串&>呈现JSON数组,请使用Flux#Collect tToList()并对单一列表<;字符串&>;进行编码。

Form Data

FormHttpMessageReaderFormHttpMessageWriter支持对application/x-www-form-urlencoded内容进行解码和编码。

在经常需要从多个地方访问表单内容的服务器端,ServerWebExchange提供了一个专用的getFormData()方法,该方法通过FormHttpMessageReader解析内容,然后缓存结果以供重复访问。请参阅WebHandlerAPI部分中的表单数据

一旦使用getFormData(),将无法再从请求正文中读取原始原始内容。出于这个原因,应用程序应该一致地通过ServerWebExchange来访问缓存的表单数据,而不是从原始请求正文中读取。

Multipart

MultipartHttpMessageReaderMultipartHttpMessageWriter支持对多部分/Form-Data内容进行解码和编码。反过来,MultipartHttpMessageReader委托另一个HttpMessageReaderFlux<;Part;进行实际解析,然后将这些部分简单地收集到MultiValueMap中。默认情况下使用DefaultPartHttpMessageReader,但可以通过ServerCodecConfigurer更改。有关DefaultPartHttpMessageReader的详细信息,请参阅DefaultPartHttpMessageReader.的

在可能需要从多个地方访问多部分表单内容的服务器端,ServerWebExchange提供了一个专用的getMultipartData()方法,通过MultipartHttpMessageReader解析内容,然后缓存结果以供重复访问。请参阅WebHandlerAPI部分中的多部分数据

一旦使用getMultipartData(),将无法再从请求正文中读取原始原始内容。为此,应用程序必须始终如一地使用getMultipartData()以重复、类似地图的方式访问部件,或者依赖SynchronossPartHttpMessageReader一次性访问Flux<;Part&>

Limits

DecoderHttpMessageReader缓冲部分或全部输入流的实现可以配置为在内存中缓冲的最大字节数限制。在某些情况下,缓冲是因为输入被聚合并表示为单个对象--例如,具有@RequestBody byte[]x-www-form-urlencode数据的控制器方法,等等。在拆分输入流时,也可以使用流进行缓冲--例如,分隔文本、JSON对象流等。对于这些流情况,该限制适用于与流中的一个对象相关联的字节数。

要配置缓冲区大小,您可以检查给定的DecoderHttpMessageReader是否公开了MaxInMemoySize属性,如果是这样,则Javadoc将具有有关默认值的详细信息。在服务器端,ServerCodecConfigurer提供了设置所有编解码器的单一位置,请参阅HTTP消息编解码器。在客户端,可以在WebClient.Builder中更改所有编解码器的限制。

对于多部分分析MaxInMemoySize属性限制非文件部分的大小。对于文件部分,它确定将部分写入磁盘的阈值。对于写入磁盘的文件部分,有一个附加的MaxDiskUsagePerPart属性来限制每个部分的磁盘空间量。还有一个MaxParts属性用于限制多部分请求中的部分总数。要在WebFlux中配置这三个组件,您需要向ServerCodecConfigurer提供一个预配置的MultipartHttpMessageReader实例。

Streaming

当流到HTTP响应(例如,文本/事件流应用程序/x-ndjson)时,定期发送数据非常重要,以便尽早可靠地检测到断开连接的客户端。这种发送可以是仅供评论的空SSE事件,也可以是有效充当心跳的任何其他“无操作”数据。

DataBuffer

DataBuffer是WebFlux中字节缓冲区的表示形式。本参考的Spring Core部分在数据缓冲区和编解码器一节中介绍了更多内容。要了解的关键点是,在某些服务器(如Netty)上,字节缓冲区是池化和引用计数的,必须在使用时释放,以避免内存泄漏。

WebFlux应用程序通常不需要关心这样的问题,除非它们直接使用或产生数据缓冲区,而不是依赖编解码器与更高级别的对象相互转换,或者除非它们选择创建自定义编解码器。对于这种情况,请查看数据缓冲区和编解码器中的信息,特别是使用DataBuffer部分。

1.2.6. Logging

调试Spring WebFlux中的级别日志被设计为紧凑、最少和友好的。它关注的是一次又一次有用的高价值信息位,而不是只有在调试特定问题时才有用的其他信息。

跟踪级别日志记录通常遵循与调试相同的原则(例如,也不应是消防软管),但可用于调试任何问题。此外,某些日志消息可能在跟踪调试中显示不同级别的详细信息。

好的日志记录来自使用日志的经验。如果您发现任何不符合所述目标的情况,请通知我们。

Log Id

在WebFlux中,单个请求可以在多个线程上运行,线程ID对于关联属于特定请求的日志消息没有用处。这就是WebFlux日志消息在默认情况下使用特定于请求的ID作为前缀的原因。

在服务器端,日志ID存储在ServerWebExchange属性中(LOG_ID_ATTRIBUTE),而基于该ID的完全格式化前缀可以从ServerWebExchange#getLogPrefix().获得在WebClient端,日志ID存储在客户端请求属性(LOG_ID_ATTRIBUTE)中,而完全格式化的前缀可以从客户端请求#logPrefix()获得。

Sensitive Data

调试跟踪日志记录可以记录敏感信息。这就是表单参数和页眉在默认情况下被屏蔽的原因,您必须显式启用它们的完整日志记录。

以下示例显示如何对服务器端请求执行此操作:

Java
Kotlin
@Configuration
@EnableWebFlux
class MyConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().enableLoggingRequestDetails(true);
    }
}

              

以下示例显示如何对客户端请求执行此操作:

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

WebClient webClient = WebClient.builder()
        .exchangeStrategies(strategies -> strategies.codecs(consumer))
        .build();

              
Appenders

SLF4J和Log4J 2等记录库提供了避免阻塞的异步记录器。虽然它们有自己的缺点,比如可能会丢弃无法排队进行日志记录的消息,但它们是目前在反应式、非阻塞应用程序中使用的最佳可用选项。

Custom codecs

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

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

以下示例显示如何对客户端请求执行此操作:

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

              

1.3. DispatcherHandler

Spring WebFlux类似于Spring MVC,是围绕前端控制器模式设计的,其中中央WebHandlerDispatcherHandler为请求处理提供共享算法,而实际工作由可配置的委托组件执行。该模型灵活,支持多种工作流程。

DispatcherHandler从Spring配置中发现它需要的委托组件。它还被设计为一个Spring Bean本身,并实现ApplicationConextAware来访问它运行的上下文。如果使用WebHandler的Bean名称声明Dispatcher Handler,则它又被WebHandlerAPI中描述的组合请求处理链的href=“0”>WebHttpHandlerBuilder,发现。

WebFlux应用程序中的Spring配置通常包含:

配置交给WebHttpHandlerBuilder构建处理链,如下例所示:

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

            

生成的HttpHandler已准备好用于服务器适配器

1.3.1. Special Bean Types

DispatcherHandler委托特殊的Bean处理请求并呈现适当的响应。所谓“特殊的Bean”,我们指的是实现WebFlux框架契约的Spring管理的对象实例。它们通常带有内置的约定,但您可以自定义它们的属性、扩展它们或替换它们。

下表列出了DispatcherHandler检测到的特殊Bean。请注意,在较低级别还检测到一些其他Bean(请参阅Web Handler API中的特殊Bean类型)。

Bean type Explanation

处理程序映射

将请求映射到处理程序。该映射基于一些条件,这些条件的细节因< >Handlermap实现、带 注释的控制器、简单的URL模式映射等而异。

主要的Handlermap实现是用于@Requestmap注释方法的RequestMappingHandlerMapping,用于功能端点路由的RouterFunctionMapping,以及用于显式注册URI路径模式和WebHandler实例的SimpleUrlHandlerMapping

HandlerAdapter

帮助DispatcherHandler调用映射到请求的处理程序,而不管该处理程序实际是如何调用的。例如,调用带注释的控制器需要解析注释。HandlerAdapter的主要目的是保护DispatcherHandler不受此类细节的影响。

HandlerResultHandler

处理处理程序调用的结果并最终确定响应。请参阅结果处理

1.3.2. WebFlux Config

应用程序可以声明处理请求所需的基础架构Bean(在Web Handler API下列出)。但是,在大多数情况下,WebFlux配置是最佳起点。它声明了所需的Bean,并提供了更高级别的配置回调API来定制它。

Spring Boot relies on the WebFlux config to configure Spring WebFlux and also provides many extra convenient options.

1.3.3. Processing

DispatcherHandler处理请求如下:

  • 要求每个Handlermap查找匹配的处理程序,并使用第一个匹配的处理程序。

  • 如果找到处理程序,它将通过适当的HandlerAdapter运行,从而将执行的返回值公开为HandlerResult

  • HandlerResult提供给适当的HandlerResultHandler,以通过直接写入响应或使用视图呈现来完成处理。

1.3.4. Result Handling

通过HandlerAdapter调用处理程序的返回值被包装为HandlerResult以及一些附加上下文,并传递给第一个声明支持它的HandlerResultHandler。下表显示了可用的HandlerResultHandler实现,所有这些实现都在WebFlux Configer中声明:

Result Handler Type Return Values Default Order

ResponseEntityResultHandler

ResponseEntity,通常来自@控制器实例。

0

ServerResponseResultHandler

ServerResponse,通常来自功能终结点。

0

ResponseBodyResultHandler

处理来自@ResponseBody方法或@RestController类的返回值。

100个

ViewResoltionResultHandler

CharSequence查看ModelMap渲染或任何其他对象被视为模型属性。

请参阅查看分辨率

Integer.MAX_Value

1.3.5. Exceptions

HandlerAdapter实现可以在内部处理调用请求处理程序的异常,如控制器方法。但是,如果请求处理程序返回异步值,则可能会延迟异常。

HandlerAdapter可以将其异常处理机制公开为在其返回的HandlerResult上设置的DispatchExceptionHandler。设置好后,DispatcherHandler也会将其应用于结果处理。

HandlerAdapter也可以选择实现DispatchExceptionHandler。在这种情况下,DispatcherHandler会将其应用于在映射处理程序之前(例如,在处理程序映射期间)或更早(例如,在WebFilter中)出现的异常。

另请参阅“带注释的控制器”部分中的异常或WebHandler API部分中的异常

1.3.6. View Resolution

使用视图分辨率可以渲染到具有HTML模板和模型的浏览器,而不会将您绑定到特定的视图技术。在Spring WebFlux中,通过专用的HandlerResultHandler支持视图解析,该HandlerResultHandler使用ViewResolver实例将字符串(表示逻辑视图名称)映射到View实例。然后使用View呈现响应。

Handling

传入ViewResultHandlerHandlerResult包含来自处理程序的返回值和包含在请求处理期间添加的属性的模型。返回值将作为以下内容之一进行处理:

  • 字符串CharSequence:逻辑视图名称,通过配置的ViewResolver实现列表解析为View

  • void:根据请求路径选择一个默认的view名称,去掉前后的斜杠,解析成一个view。当未提供视图名称(例如,返回了模型属性)或异步返回值(例如,MonoComplete Empty)时,也会发生同样的情况。

  • 渲染:查看分辨率场景接口。使用代码完成功能研究您的IDE中的选项。

  • ModelMap:要为请求添加到模型的额外模型属性。

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

该模型可以包含异步、反应性类型(例如,来自反应器或RxJava)。在呈现之前,AbstractView将这些模型属性解析为具体的值并更新模型。单值反应类型被解析为单值或没有值(如果为空),而多值反应类型(例如,Flux<;T>;)被收集并解析为list<;T>;

要配置视图分辨率,只需向您的Spring配置添加一个ViewResoltionResultHandlerBean即可。WebFlux配置提供了查看解析的专用配置接口。

有关与Spring WebFlux集成的视图技术的更多信息,请参见View Technologies

Redirecting

视图名称中的特殊reDirect:前缀允许您执行重定向。UrlBasedViewResolver(和子类)将此识别为需要重定向的指令。视图名称的其余部分是重定向URL。

实际效果与控制器返回<Rendering.redirectTo(“abc”).build(),>重定向视图 或代码相同,但现在控制器本身可以根据逻辑视图名称进行操作。诸如<代码>REDIRECT:/Some/RESOURCE之类的视图名称是相对于当前应用程序的,而诸如redirect:https://example.com/arbitrary/path之类的视图名称则重定向到绝对URL。

Content Negotiation

ViewResoltionResultHandler支持内容协商。它将请求的媒体类型与每个选定的视图支持的媒体类型进行比较。使用支持请求的媒体类型的第一个视图

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

1.4. Annotated Controllers

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

下面的清单显示了一个基本示例:

Java
Kotlin
@RestController
public class HelloController {

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

            

在前面的示例中,该方法返回要写入响应正文的字符串

1.4.1. @Controller

您可以使用标准的Spring Bean定义来定义控制器Bean。@Controller构造型允许自动检测,并且与Spring对检测类路径中的@Component类以及为它们自动注册Bean定义的支持保持一致。它还充当带注释的类的构造型,指示其作为Web组件的角色。

要启用此类@ControllerBean的自动检测,您可以将组件扫描添加到Java配置中,如下例所示:

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

    // ...
}

             
1 Scan the org.example.web package.

@RestController是一个组成的批注,它本身使用@Controller@ResponseBody进行了元批注,表示控制器的每个方法都继承了类型级@ResponseBody批注,因此直接写入响应正文,而不是使用HTML模板进行视图解析和呈现。

AOP Proxies

在某些情况下,您可能需要在运行时使用AOP代理来装饰控制器。一个例子是,如果您选择在控制器上直接使用@Transaction注释。在这种情况下,特别是对于控制器,我们建议使用基于类的代理。直接在控制器上使用此类注释时会自动出现这种情况。

如果控制器实现了一个接口,并且需要AOP代理,您可能需要显式地配置基于类的代理。例如,使用<代码>@EnableTransactionManagement 可以更改为@EnableTransactionManagement(proxyTargetClass=TRUE) ,使用<代码><;tx:批注驱动/>; 可以更改为<代码><;tx:批注驱动的代理-目标类=“True”/>;

Keep in mind that as of 6.0, with interface proxying, Spring WebFlux no longer detects controllers based solely on a type-level @RequestMapping annotation on the interface. Please, enable class based proxying, or otherwise the interface must also have an @Controller annotation.

1.4.2. Request Mapping

@Requestmap注释用于将请求映射到控制器方法。它具有按URL、HTTP方法、请求参数、标头和媒体类型匹配的各种属性。您可以在类级别使用它来表示共享映射,也可以在方法级别使用它来缩小到特定的端点映射。

还有特定于HTTP方法的快捷方式变体@Requestmap

  • @Getmap

  • @Postmap

  • @Putmap

  • @Deletemap

  • @Patchmap

前面的批注是自定义批注,因为可以说,大多数控制器方法都应该映射到特定的HTTP方法,而不是使用@Requestmap,后者默认情况下与所有HTTP方法匹配。同时,在类级别上仍然需要@RequestMap来表示共享映射。

下面的示例使用类型和方法级别映射:

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

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

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}

             
URI Patterns

您可以使用全局模式和通配符映射请求:

Pattern Description Example

匹配一个字符

“/ages/t?st.html”匹配“/ages/test.html”“/ages/t3st.html”

*

匹配路径段中的零个或多个字符

“/Resources/*.png”匹配“/resource/file.png”

“/Projects/*/Versions”匹配“/Projects/Spring/Versions”但不匹配“/Projects/Spring/Boot/Versions”

**

匹配零个或多个路径段,直到路径末尾

“/Resources/**”匹配“/resource/file.png”“/Resources/Image/file.png”

“/Resources/**/file.png”无效,因为**只允许出现在路径的末尾。

{名称}

匹配路径段并将其捕获为名为“name”的变量

“/Projects/{project}/Versions”匹配“/Projects/Spring/Versions”并捕获project=Spring

{名称:[a-z]+}

将regexp“[a-z]+”匹配为名为“name”的路径变量

“/projects/{project:[a-z]+}/versions”与<代码>“/项目/Spring1/版本” 匹配,但不与<代码>“/项目/Sprring1/版本” 匹配

{*路径}

匹配零个或多个路径段,直到路径的末尾,并将其捕获为名为“Path”的变量

“/Resources/{*file}”匹配“/Resources/Images/file.png”并捕获file=/Images/file.png

捕获的URI变量可以通过@PathVariable访问,如下例所示:

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

              
1 Class-level URI mapping.
2 Method-level URI mapping.

您可以在类和方法级别声明URI变量,如下面的示例所示:

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

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

              
1 Class-level URI mapping.
2 Method-level URI mapping.

URI变量将自动转换为适当的类型,或者引发TypeMismatchException。默认支持简单类型(intlongDate等),您可以注册对任何其他数据类型的支持。请参阅类型转换数据活页夹

URI变量可以显式命名(例如,@PathVariable(“CustomID”)),但如果名称相同,并且在Java 8上使用调试信息或-参数编译器标志编译代码,则可以省略该细节。

语法{*varName}声明了一个与零个或多个剩余路径段匹配的URI变量。例如,/resource/{*path}匹配/resource/下的所有文件,而“Path”变量捕获/resource下的完整路径。

语法{varName:regex}使用语法为{varName:regex}的正则表达式声明URI变量。例如,给定URL/Spring-web-3.0.5.jar,以下方法提取名称、版本和文件扩展名:

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

              

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

Spring WebFlux uses PathPattern and the PathPatternParser for URI path matching support. Both classes are located in spring-web and are expressly designed for use with HTTP URL paths in web applications where a large number of URI path patterns are matched at runtime.

Spring WebFlux不支持后缀模式匹配 - ,这与Spring MVC不同,在Spring MVC中,像/Person这样的映射也与/Person.*匹配。对于基于URL的内容协商,如果需要,我们建议使用查询参数,该参数更简单、更明确,更不易受到基于URL路径的利用。

Pattern Comparison

当多个模式与一个URL匹配时,必须对它们进行比较以找到最佳匹配。这是通过PathPattern.SPECIFICITY_COMPARATOR,完成的,它寻找更具体的模式。

对于每个模式,都会根据URI变量和通配符的数量计算分数,其中URI变量的分数低于通配符。总分较低的模式获胜。如果两个图案有相同的分数,则选择较长的。

所有捕获模式(例如,**{*varName})将从评分中排除,并始终排在最后。如果两种模式都是包罗万象的,则选择时间较长的模式。

Consumable Media Types

您可以根据请求的Content-Type缩小请求映射范围,如下例所示:

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

              

消费属性还支持否定表达式 - 例如,<代码>!文本/普通 表示除<代码>文本/普通 以外的任何内容类型。

您可以在类级别声明共享的消耗属性。然而,与大多数其他请求映射属性不同,当在类级别使用时,方法级别的使用属性覆盖,而不是扩展类级别声明。

MediaType provides constants for commonly used media types — for example, APPLICATION_JSON_VALUE and APPLICATION_XML_VALUE.
Producible Media Types

您可以根据Accept请求头和控制器方法生成的内容类型列表来缩小请求映射范围,如下例所示:

Java
Kotlin
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

              

媒体类型可以指定一个字符集。支持否定表达式。例如,< >!Text/  表示除<代码>文本/纯文本 以外的任何内容类型。

您可以在类级别声明共享的Products属性。然而,与大多数其他请求映射属性不同的是,当在类级别使用时,方法级别的会生成属性覆盖,而不是扩展类级别声明。

MediaType provides constants for commonly used media types — e.g. APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE.
Parameters and Headers

您可以根据查询参数条件缩小请求映射范围。您可以测试查询参数是否存在(myParam)、是否不存在(!myParam)或特定值(myParam=myValue)。下面的示例测试具有值的参数:

Java
Kotlin
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}

              
1 Check that myParam equals myValue.

您还可以对请求标头条件使用相同的方法,如下例所示:

Java
Kotlin
@GetMapping(path = "/pets", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
    // ...
}

              
1 Check that myHeader equals myValue.
HTTP HEAD, OPTIONS

@Getmap@RequestMapping(method=HttpMethod.GET)为请求映射透明地支持HTTPHead。控制器方法不需要更改。在HttpHandler服务器适配器中应用的响应包装确保将Content-Length头设置为在没有实际写入响应的情况下写入的字节数。

默认情况下,通过将Allow响应头设置为所有@Requestmap方法中列出的具有匹配URL模式的HTTP方法列表来处理HTTP选项。

对于没有HTTP方法声明的@RequestmapAllow标头设置为GET、Head、POST、PUT、Patch、Delete、Options。控制器方法应该始终声明受支持的http方法(例如,通过使用特定于http方法的变量 - <代码>@Getmap、<代码>@postmap等)。

您可以显式地将@Requestmap方法映射到HTTPHead和HTTP选项,但在常见情况下这不是必需的。

Custom Annotations

Spring WebFlux支持使用组合注释进行请求映射。这些批注本身使用@RequestMapping进行了元批注,并被组合以使用更窄、更具体的目的重新声明@Requestmap属性的一个子集(或全部)。

@Getmap@Postmap@Putmap@Deletemap@Patchmap是合成批注的示例。之所以提供它们,是因为可以说,大多数控制器方法都应该映射到特定的HTTP方法,而不是使用@Requestmap,后者默认情况下与所有HTTP方法匹配。如果您需要组合注释的示例,请看一下这些注释是如何声明的。

Spring WebFlux还支持使用定制请求匹配逻辑的定制请求映射属性。这是一个更高级的选项,它需要将RequestMappingHandlerMapping子类化并覆盖getCustomMethodCondition方法,在该方法中您可以检查自定义属性并返回您自己的RequestCondition

Explicit Registrations

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

Java
Kotlin
@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 Inject target handlers and the handler mapping for controllers.
2 Prepare the request mapping metadata.
3 Get the handler method.
4 Add the registration.

1.4.3. Handler Methods

@Requestmap处理程序方法具有灵活的签名,可以从一系列受支持的控制器方法参数和返回值中进行选择。

Method Arguments

下表显示了支持的控制器方法参数。

在需要解析阻塞I/O(例如,读取请求正文)的参数上支持反应式类型(Reactive、RxJava、或其他)。这将在Description列中标记。对于不需要阻塞的参数,不应使用反应类型。

支持将JDK 1.8的java.util.Optional作为方法参数,并将其与具有RequestParam属性(例如,@RequestParam@RequestHeader等)的批注结合使用,并等同于RequestHeader=False

Controller method argument Description

ServerWebExchange

访问完整的ServerWebExchange - 容器,用于http请求和响应、请求和会话属性、check NotModified方法等。

ServerHttpRequestServerHttpResponse

访问HTTP请求或响应。

WebSession

对会话的访问权限。除非添加了属性,否则这不会强制启动新会话。支持反应式类型。

java.security.main

当前经过身份验证的用户 - 可能是特定的主体实现类(如果已知)。支持反应式类型。

org.springframework.http.HttpMethod

请求的HTTP方法。

java.util.Locale

当前请求区域设置,由有效的最具体的<LocaleResolver/LocaleContextResolver.>LocaleResolver 确定,即配置的LocaleResolver

java.util.TimeZone+java.time.ZoneID

与当前请求关联的时区,由LocaleConextResolver确定。

@路径变量

用于访问URI模板变量。请参阅URI模式

@MatrixVariable

用于访问URI路径段中的名称-值对。请参阅矩阵变量

@RequestParam

用于访问查询参数。参数值将转换为声明的方法参数类型。参见@RequestParam

注意,使用<代码>@RequestParam 是可选的 - ,例如,用于设置其属性。请参阅本表后面的“任何其他参数”。

@RequestHeader

用于访问请求标头。标头值将转换为声明的方法参数类型。参见@RequestHeader

@CookieValue

用于访问Cookie。Cookie值被转换为声明的方法参数类型。参见@CookieValue

@RequestBody

用于访问HTTP请求正文。主体内容通过使用HttpMessageReader实例转换为声明的方法参数类型。支持反应式类型。参见@RequestBody

HttpEntity<;B&>

用于访问请求头和正文。正文使用HttpMessageReader实例进行转换。支持反应式类型。请参阅HttpEntity

@RequestPart

用于访问多部分/表单数据请求中的部分。支持反应式类型。参见多部分内容多部分数据

java.util.Maporg.springFrawork.ui.Modelorg.springFrawork.ui.ModelMap

用于访问在HTML控制器中使用的模型,该模型作为视图呈现的一部分公开给模板。

@ModelAttribute

用于访问模型中的现有属性(如果不存在,则实例化),并应用数据绑定和验证。请参见@ModelAttribute以及模型DataBinder

请注意,使用< >@ModelAttribute是可选的 ,例如,用于设置其属性。请参阅本表后面的“任何其他参数”。

错误绑定结果

用于访问来自命令对象的验证和数据绑定的错误,即@ModelAttribute参数。ErrorsBindingResult参数必须紧跟在已验证的方法参数之后声明。

SessionStatus+类级别@SessionAttributes

用于将表单处理标记为完成,这将触发通过类级@SessionAttributes注释声明的会话属性的清理。有关详细信息,请参阅@SessionAttributes

UriComponentsBuilder

用于准备相对于当前请求的主机、端口、方案和上下文路径的URL。请参阅URI链接

@SessionAttribute

用于访问任何会话属性 - ,而不是作为类级<代码>@SessionAttributes 声明存储在会话中的模型属性。有关详细信息,请参阅@SessionAttribute

@RequestAttribute

用于访问请求属性。有关详细信息,请参阅@RequestAttribute

任何其他论点

如果方法参数与上面的任何一个都不匹配,则默认情况下,如果它是简单类型(由BeanUtils#isSimpleProperty确定),则它被解析为@RequestParam,否则被解析为@ModelAttribute

Return Values

下表显示了支持的控制器方法返回值。请注意,所有返回值通常都支持来自Reactive、RxJava、或其他等库的反应类型。

Controller method return value Description

@ResponseBody

返回值通过HttpMessageWriter实例编码并写入响应。参见@ResponseBody

HttpEntity<;B&>;ResponseEntity<;B&>

返回值指定完整的响应,包括HTTP头,主体通过HttpMessageWriter实例编码并写入响应。请参阅ResponseEntity

HttpHeaders

用于返回带有标头但没有正文的响应。

错误响应

要在正文中呈现具有详细信息的RFC 7807错误响应,请参阅错误响应

ProblemDetail

要在正文中呈现具有详细信息的RFC 7807错误响应,请参阅错误响应

字符串

要用ViewResolver实例解析的视图名称,并与通过命令对象和@ModelAttribute方法确定的隐式模型 - 一起使用。处理程序方法还可以通过声明Model参数(前面)来以编程方式丰富模型。

查看

用于呈现的视图实例以及通过命令对象和@ModelAttribute方法确定的隐式模型 - 。处理程序方法还可以通过声明Model参数(前面)来以编程方式丰富模型。

java.util.Maporg.springFrawork.ui.Model

要添加到隐式模型的属性,视图名根据请求路径隐式确定。

@ModelAttribute

要添加到模型的属性,视图名根据请求路径隐式确定。

请注意,@ModelAttribute是可选的。请参阅本表后面的“任何其他返回值”。

呈现

用于模型和视图渲染场景的API。

作废

如果方法还具有ServerHttpResponseServerWebExchange参数或@ResponseStatus批注,则具有void、可能是异步(例如,mono<;void>;)、返回类型(或NULL返回值)的方法被视为已完全处理了响应。如果控制器进行了肯定的ETag或lastModified时间戳检查,情况也是如此。//TODO:请参阅控制器了解详细信息。

如果上述情况都不成立,则void返回类型还可以为REST控制器指示“无响应正文”,或者为HTML控制器指示默认的视图名称选择。

流量;ServerSentEvent;Observable<;ServerSentEvent>;,或其他反应类型

发出服务器发送的事件。当只需要写入数据时,可以省略ServerSentEvent包装器(但是,必须通过Products属性在映射中请求或声明文本/事件流)。

其他返回值

如果返回值仍未以任何其他方式解析,则它将被视为模型属性,除非它是由BeanUtils#isSimpleProperty确定的简单类型,在这种情况下它仍未解析。

Type Conversion

一些表示基于字符串的请求输入的带注释的控制器方法参数(例如,@RequestParam@RequestHeader@PathVariable@MatrixVariable@CookieValue)如果被声明为字符串以外的内容,则可能需要类型转换。

对于此类情况,将根据配置的转换器自动应用类型转换。默认支持简单类型(如intlongdate等)。可以通过WebDataBinder(请参阅)或通过向FormattingConversionService注册FormattingConversionService(请参阅Spring字段格式设置)来自定义类型转换。

类型转换中的一个实际问题是空字符串源值的处理。如果此类值由于类型转换而变为NULL,则将其视为缺少。longuuid和其他目标类型可能是这种情况。如果要允许注入Null,请在参数批注上使用Required标志,或者将参数声明为@Nullable

Matrix Variables

RFC 3986讨论路径段中的名称-值对。在Spring WebFlux中,我们将基于Tim Berners-Lee的“old post”的“矩阵变量”称为“矩阵变量”,但它们也可以称为URI路径参数。

矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号 - 分隔,例如,“/CARS;COLOR=RED,GREEN;Year=2012”。还可以通过重复的变量名(例如,“color=red;color=green;color=blue”.)指定多个值 - 

与Spring MVC不同,在WebFlux中,URL中矩阵变量的存在与否不会影响请求映射。换句话说,您不需要使用URI变量来屏蔽变量内容。也就是说,如果您希望从控制器方法访问矩阵变量,则需要将URI变量添加到需要矩阵变量的路径段。以下示例显示了如何执行此操作:

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

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

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

              

考虑到所有路径段都可以包含矩阵变量,有时可能需要明确矩阵变量应该在哪个路径变量中,如下例所示:

Java
Kotlin
// 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
}

              

您可以将矩阵变量定义为可选,并指定一个默认值,如下例所示:

Java
Kotlin
// GET /pets/42

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

    // q == 1
}

              

若要获取所有矩阵变量,请使用MultiValueMap,如下例所示:

Java
Kotlin
// 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]
}

              
@RequestParam

您可以使用@RequestParam注释将查询参数绑定到控制器中的方法参数。下面的代码片段显示了用法:

Java
Kotlin
@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 Using @RequestParam.
The Servlet API “request parameter” concept conflates query parameters, form data, and multiparts into one. However, in WebFlux, each is accessed individually through ServerWebExchange. While @RequestParam binds to query parameters only, you can use data binding to apply query parameters, form data, and multiparts to a command object.

默认情况下,使用@RequestParam注释的方法参数是必需的,但您可以通过将@RequestParam的Required标志设置为FALSE或通过使用java.util.Optional包装器声明参数来指定方法参数是可选的。

如果目标方法参数类型不是字符串,则自动应用类型转换。请参阅类型转换

当在Map<;字符串MultiValueMap<;字符串,字符串&>参数上声明@RequestParam批注时,映射将使用所有查询参数填充。

注意,使用<代码>@RequestParam 是可选的 - ,例如,用于设置其属性。默认情况下,任何简单值类型的参数(由BeanUtils#isSimpleProperty确定)并且不被任何其他参数解析器解析,将被视为使用@RequestParam进行了注释。

@RequestHeader

您可以使用@RequestHeader注释将请求头绑定到控制器中的方法参数。

以下示例显示了一个带有标头的请求:

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-EndingKeep-Alive标头的值:

Java
Kotlin
@GetMapping("/demo")
public void handle( @RequestHeader("Accept-Encoding") String encoding, (1) @RequestHeader("Keep-Alive") long keepAlive) { (2)
    //...
}

              
1 Get the value of the Accept-Encoding header.
2 Get the value of the Keep-Alive header.

如果目标方法参数类型不是字符串,则自动应用类型转换。请参阅类型转换

@RequestHeader批注用于Map<;字符串、字符串>;MultiValueMap<;字符串、字符串>;HttpHeaders参数时,映射将填充所有标头值。

Built-in support is available for converting a comma-separated string into an array or collection of strings or other types known to the type conversion system. For example, a method parameter annotated with @RequestHeader("Accept") may be of type String but also of String[] or List<String>.
@CookieValue

您可以使用@CookieValue注释将HTTP cookie的值绑定到控制器中的方法参数。

以下示例显示带有Cookie的请求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代码示例演示如何获取Cookie值:

Java
Kotlin
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { (1)
    //...
}

              
1 Get the cookie value.

如果目标方法参数类型不是字符串,则自动应用类型转换。请参阅类型转换

@ModelAttribute

您可以在方法参数上使用@ModelAttribute注释来访问模型中的属性,或者实例化该属性(如果不存在)。模型属性还被名称与字段名称匹配的查询参数和表单字段的值所覆盖。这称为数据绑定,它使您不必解析和转换单个查询参数和表单域。下面的示例绑定Pet的一个实例:

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

              
1 Bind an instance of Pet.

上例中的Pet实例解析如下:

  • 如果已经通过Model添加。

  • 从HTTP会话通过@SessionAttributes

  • 来自默认构造函数的调用。

  • 调用带有与查询参数或表单域匹配的参数的“主构造函数”。参数名称通过JavaBeans@ConstructorProperties或通过字节码中的运行时保留的参数名称来确定。

获取模型属性实例后,应用数据绑定。WebExchangeDataBinder类将查询参数和表单字段的名称与目标对象上的字段名称匹配。在必要时应用类型转换后,将填充匹配的字段。有关数据绑定(和验证)的更多信息,请参阅验证。有关定制数据绑定的更多信息,请参阅DataBinder

数据绑定可能会导致错误。默认情况下,会引发WebExchangeBindException,但要检查控制器方法中的此类错误,您可以在@ModelAttribute旁边添加一个BindingResult参数,如下面的示例所示:

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

              
1 Adding a BindingResult.

通过添加jakarta.validation.Valid批注或Spring的@valated批注(另请参阅Bean验证Spring验证),可以在数据绑定后自动应用验证。下面的示例使用@Valid批注:

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

              
1 Using @Valid on a model attribute argument.

io.reactivex.Single<;Account>;.与Spring MVC不同,它支持 - 模型中的反应式类型,例如Mono<;Account;或Spring您可以使用或不使用反应类型包装器来声明@ModelAttribute参数,如果需要,它将相应地解析为实际值。但是,请注意,要使用BindingResult参数,必须在它之前声明@ModelAttribute参数,而不使用反应类型包装器,如前面所示。或者,您可以通过反应性类型处理任何错误,如下面的示例所示:

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

              

请注意,使用< >@ModelAttribute是可选的 ,例如,用于设置其属性。默认情况下,任何不是简单值类型(由BeanUtils#isSimpleProperty确定)且不能由任何其他参数解析器解析的参数都被视为使用@ModelAttribute进行了注释。

@SessionAttributes

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

请考虑以下示例:

Java
Kotlin
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {
    // ...
}

              
1 Using the @SessionAttributes annotation.

在第一次请求时,当名为pet的模型属性添加到模型中时,它会自动提升到WebSession中并保存在其中。它一直保留在那里,直到另一个控制器方法使用SessionStatus方法参数清除存储空间,如下面的示例所示:

Java
Kotlin
@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 Using the @SessionAttributes annotation.
2 Using a SessionStatus variable.
@SessionAttribute

如果您需要访问预先存在的会话属性,这些属性是全局管理的(即,在控制器 - 之外,例如由筛选器管理),并且可能存在也可能不存在,则可以在方法参数上使用@SessionAttribute批注,如下面的示例所示:

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

              
1 Using @SessionAttribute.

对于需要添加或删除会话属性的用例,可以考虑将WebSession注入控制器方法。

要将模型属性作为控制器工作流的一部分临时存储在会话中,请考虑使用SessionAttributes,如@SessionAttributes中所述。

@RequestAttribute

@SessionAttribute类似,您可以使用@RequestAttribute注释来访问先前创建的预先存在的请求属性(例如,通过WebFilter创建),如下例所示:

Java
Kotlin
@GetMapping("/")
public String handle(@RequestAttribute Client client) { (1)
    // ...
}

              
1 Using @RequestAttribute.
Multipart Content

正如多部分数据中所解释的,ServerWebExchange提供对多部分内容的访问。在控制器中处理文件上载表单(例如,从浏览器)的最佳方式是通过数据绑定到命令对象,如下例所示:

Java
Kotlin
class MyForm {

    private String name;

    private MultipartFile file;

    // ...

}

@Controller
public class FileUploadController {

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

}

              
1 Using @RequestPart to get the metadata.
2 Using @RequestPart to get the file.

您还可以在REST式服务场景中提交来自非浏览器客户端的多部分请求。下面的示例将一个文件与JSON一起使用:

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访问各个部件,如下例所示:

Java
Kotlin
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata, (1) @RequestPart("file-data") FilePart file) { (2)
    // ...
}

              
1 Using @RequestPart to get the metadata.
2 Using @RequestPart to get the file.

要反序列化原始部件内容(例如,反序列化JSON - ,类似于<代码>@RequestBody ),您可以声明一个具体的目标<代码>对象 ,而不是<代码>部件 ,如下例所示:

Java
Kotlin
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) { (1)
    // ...
}

              
1 Using @RequestPart to get the metadata.

您可以将@RequestPartjakarta.validation.Valid或Spring的@valated注释结合使用,这会导致应用标准Bean验证。验证错误会导致WebExchangeBindException导致400(BAD_REQUEST)响应。该异常包含带有错误详细信息的BindingResult,也可以在控制器方法中通过使用异步包装声明参数,然后使用与错误相关的运算符来处理:

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

              
1 Using @RequestBody.

要以MultiValueMap的形式访问所有分块数据,可以使用@RequestBody,如下例所示:

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

              
1 Using @RequestBody.
PartEvent

要以流的方式顺序访问多部分数据,您可以使用@RequestBodyFlux<;PartEvent>;(或flow<;PartEvent>;在Kotlin中)。多部分HTTP消息中的每个部分将至少生成一个PartEvent,其中既包含头部,又包含部分内容的缓冲区。

  • 表单域将生成一个单个FormPartEvent,其中包含该字段的值。

  • 文件上传将生成一个或多个FilePartEvent对象,其中包含上传时使用的文件名。如果文件足够大,可以跨多个缓冲区拆分,则第一个FilePartEvent将跟随后续事件。

例如:

Java
Kotlin
@PostMapping("/")
public void handle(@RequestBody Flux<PartEvent> allPartsEvents) { (1)
    allPartsEvents.windowUntil(PartEvent::isLast) (2)
            .concatMap(p -> p.switchOnFirst((signal, partEvents) -> { (3)
                if (signal.hasValue()) {
                    PartEvent event = signal.get();
                    if (event instanceof FormPartEvent formEvent) { (4)
                        String value = formEvent.value();
                        // handle form field
                    }
                    else if (event instanceof FilePartEvent fileEvent) { (5)
                        String filename = fileEvent.filename();
                        Flux<DataBuffer> contents = partEvents.map(PartEvent::content); (6)
                        // handle file upload
                    }
                    else {
                        return Mono.error(new RuntimeException("Unexpected event: " + event));
                    }
                }
                else {
                    return partEvents; // either complete or error signal
                }
            }));
}

               
1 Using @RequestBody.
2 The final PartEvent for a particular part will have isLast() set to true, and can be followed by additional events belonging to subsequent parts. This makes the isLast property suitable as a predicate for the Flux::windowUntil operator, to split events from all parts into windows that each belong to a single part.
3 The Flux::switchOnFirst operator allows you to see whether you are handling a form field or file upload.
4 Handling the form field.
5 Handling the file upload.
6 The body contents must be completely consumed, relayed, or released to avoid memory leaks.

还可以使用WebClient将接收到的部件事件转发到另一个服务。请参阅多部分数据

@RequestBody

您可以使用@RequestBody注释通过HttpMessageReader将请求正文读取并反序列化为对象。下面的示例使用@RequestBody参数:

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

              

与Spring MVC不同,在WebFlux中,@RequestBody方法参数支持反应类型和完全非阻塞读取和(客户端到服务器)流。

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

              

您可以使用WebFlux配置HTTP消息编解码器选项来配置或定制消息阅读器。

您可以将@RequestBodyjakarta.validation.Valid或Spring的@valated注释结合使用,这会导致应用标准Bean验证。验证错误会导致WebExchangeBindException,从而导致400(BAD_REQUEST)响应。该异常包含带有错误详细信息的BindingResult,可以在控制器方法中通过使用异步包装声明参数,然后使用与错误相关的运算符来处理:

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

              
HttpEntity

HttpEntity与使用@RequestBody基本相同,但基于公开请求头和正文的容器对象。下面的示例使用HttpEntity

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

              
@ResponseBody

您可以在方法上使用@ResponseBody注释,通过HttpMessageWriter将返回序列化到响应体。以下示例显示了如何执行此操作:

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

              

@ResponseBody在类级别也受支持,在这种情况下,所有控制器方法都会继承它。这就是@RestController的效果,它只不过是一个用@Controller@ResponseBody标记的元注释。

@ResponseBody支持反应类型,这意味着您可以返回反应类型或RxJava类型,并将它们产生的异步值呈现给响应。有关其他详细信息,请参阅JSON渲染

您可以将@ResponseBody方法与JSON序列化视图结合使用。详情请参阅Jackson JSON

您可以使用WebFlux配置HTTP消息编解码器选项来配置或定制消息编写。

ResponseEntity

ResponseEntity类似于@ResponseBody,但有状态和头。例如:

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

              

WebFlux支持使用单值反应类型为Body异步生成ResponseEntity和/或单值和多值反应类型。这允许使用ResponseEntity进行各种异步响应,如下所示:

  • ResponseEntity<;Mono<;T>;>;ResponseEntity<;Flux<;T>;>;使响应状态和标头立即可用,而正文则在以后以异步方式提供。如果Body由0..1值组成,则使用Mono,如果可以生成多个值,则使用Flux

  • Mono<;ResponseEntity<;T>;>;稍后以异步方式提供所有三个 - 响应状态、头和正文。这允许响应状态和标头根据异步请求处理的结果而变化。

  • Mono<;ResponseEntity<;Mono<;T>;>;>;Mono<;ResponseEntity<;Flux<;T>;>;>;是另一种可能的选择,尽管不太常见。它们首先以异步方式提供响应状态和标头,然后也以异步方式提供响应正文,然后是第二个。

Jackson JSON

Spring提供对Jackson JSON库的支持。

JSON Views

Spring WebFlux提供了对Jackson的序列化视图的内置支持,它只允许呈现对象中所有字段的子集。要将其与@ResponseBodyResponseEntity控制器方法一起使用,可以使用Jackson的@JsonView注释来激活序列化视图类,如下例所示:

Java
Kotlin
@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;
    }
}

               
@JsonView allows an array of view classes but you can only specify only one per controller method. Use a composite interface if you need to activate multiple views.

1.4.4. Model

您可以使用@ModelAttribute注释:

  • @Requestmap方法中的方法参数上,从模型创建或访问对象,并通过WebDataBinder将其绑定到请求。

  • 作为@Controller@ControllerAdance类中的方法级批注,帮助在任何@RequestMap方法调用之前初始化模型。

  • @Requestmap方法上将其返回值标记为模型属性。

本节讨论@ModelAttribute方法,或前面列表中的第二项。控制器可以有任意数量的@ModelAttribute方法。所有此类方法都在同一控制器中的@Requestmap方法之前调用。@ModelAttribute方法也可以通过@ControllerAdance在控制器之间共享。有关详细信息,请参阅控制器建议一节。

@ModelAttribute方法具有灵活的方法签名。它们支持许多与@RequestMap方法相同的参数(除了@ModelAttribute本身和与请求正文相关的任何内容)。

下面的示例使用@ModelAttribute方法:

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

             

下面的示例仅添加一个属性:

Java
Kotlin
@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}

             
When a name is not explicitly specified, a default name is chosen based on the type, as explained in the javadoc for Conventions. You can always assign an explicit name by using the overloaded addAttribute method or through the name attribute on @ModelAttribute (for a return value).

Spring WebFlux与Spring MVC不同,它显式支持模型中的反应类型(例如,Mono<;Account;io.reactivex.Single<;Account>;).这样的异步模型属性可以在@Requestmap调用时透明地解析(并更新模型)为它们的实际值,前提是@ModelAttribute参数声明时没有包装,如下面的示例所示:

Java
Kotlin
@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) {
    // ...
}

             

此外,任何具有反应类型包装器的模型属性都会在视图呈现之前解析为它们的实际值(并更新模型)。

您还可以使用@ModelAttribute作为@RequestMap方法的方法级批注,在这种情况下,@RequestMapping方法的返回值被解释为模型属性。这通常不是必需的,因为这是HTML控制器中的默认行为,除非返回值是字符串,否则会被解释为视图名。@ModelAttribute还可以帮助定制模型属性名称,如下例所示:

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

             

1.4.5. DataBinder

@控制器@ControllerAdance类可以具有@InitBinder方法,以初始化WebDataBinder的实例。这些反过来又被用来:

  • 将请求参数(即表单数据或查询)绑定到模型对象。

  • 将基于字符串的请求值(如请求参数、路径变量、标头、cookie等)转换为控制器方法参数的目标类型。

  • 呈现HTML表单时,将模型对象值格式化为字符串值。

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

@InitBinder方法支持与@RequestMap方法相同的许多参数,但@ModelAttribute(命令对象)参数除外。通常,它们是用WebDataBinder参数(用于注册)和void返回值声明的。下面的示例使用@InitBinder批注:

Java
Kotlin
@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 Using the @InitBinder annotation.

或者,当通过共享的FormattingConversionService使用基于格式化程序的设置时,您可以重复使用相同的方法并注册特定于控制器的格式化程序实例,如下面的示例所示:

Java
Kotlin
@Controller
public class FormController {

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

    // ...
}

             
1 Adding a custom formatter (a DateFormatter, in this case).
Model Design

在Web应用程序的上下文中,数据绑定涉及将HTTP请求参数(即表单数据或查询参数)绑定到模型对象及其嵌套对象中的属性。

只有遵循JavaBeans命名约定公共属性才公开用于数据绑定-例如,FirstName()属性的公共字符串getFirstName()方法和FirstName属性的公共空setFirstName(字符串)方法。

The model object, and its nested object graph, is also sometimes referred to as a command object, form-backing object, or POJO (Plain Old Java Object).

默认情况下,Spring允许绑定到模型对象图中的所有公共属性。这意味着您需要仔细考虑该模型具有哪些公共属性,因为客户端可能以任何公共属性路径为目标,甚至是一些不会针对给定用例的路径。

例如,在给定一个HTTP表单数据终结点的情况下,恶意客户端可能会为模型对象图中存在但不属于浏览器中显示的HTML表单的属性提供值。这可能会导致在模型对象及其任何嵌套对象上设置数据,而这些数据预计不会更新。

推荐的方法是使用专用模型对象,该对象仅公开与表单提交相关的属性。例如,在用于更改用户电子邮件地址的表单上,模型对象应该声明至少一组属性,如下面的ChangeEmailForm所示。

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

}

              

如果不能或不想为每个数据绑定用例使用专用模型对象,则必须限制数据绑定允许的属性。理想情况下,您可以通过在WebDataBinder上注册允许的字段模式来实现这一点。

例如,要在应用程序中注册允许的字段模式,您可以在@控制器@ControllerAdvice组件中实现@InitBinder方法,如下所示:

@Controller
public class ChangeEmailController {

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

    // @RequestMapping methods, etc.

}

              

除了注册允许的模式外,还可以通过DataBinder及其子类中的setDisalloweFields()方法注册不允许的字段模式。但是,请注意,“允许列表”比“拒绝列表”更安全。因此,应该优先使用setAllowweFields(),而不是setDisalloweFields()

请注意,与允许的字段模式匹配区分大小写;而与不允许的字段模式匹配不区分大小写。此外,与不允许的模式匹配的字段将不被接受,即使它碰巧也与允许的列表中的模式匹配。

在出于数据绑定的目的直接公开您的域模型时,正确配置允许和不允许的字段模式非常重要。否则,这是一个很大的安全风险。

此外,强烈建议您不要将域模型中的类型(如JPA或Hibernate实体)用作数据绑定场景中的模型对象。

1.4.6. Exceptions

@控制器@ControllerAdacy类可以有@ExceptionHandler方法来处理来自控制器方法的异常。下面的示例包括这样的处理程序方法:

Java
Kotlin
@Controller
public class SimpleController {

    // ...

    @ExceptionHandler (1)
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}

             
1 Declaring an @ExceptionHandler.

异常可以与传播的顶级异常匹配(即抛出直接的IOException),也可以匹配顶级包装器异常中的直接原因(例如,包装在IlLegalStateException中的IOException)。

对于匹配的异常类型,最好将目标异常声明为方法参数,如上例所示。或者,注释声明可以缩小异常类型以匹配。我们通常建议在参数签名中尽可能具体,并在具有相应顺序的@ControllerAdance上声明您的主根异常映射。有关详细信息,请参阅MVC部分

An @ExceptionHandler method in WebFlux supports the same method arguments and return values as a @RequestMapping method, with the exception of request body- and @ModelAttribute-related method arguments.

Spring WebFlux中对@ExceptionHandler方法的支持由HandlerAdapter@Requestmap方法提供。有关详细信息,请参阅DispatcherHandler

Method Arguments

@ExceptionHandler方法支持与@Requestmap方法相同的方法参数,只是请求体可能已被使用。

Return Values

@ExceptionHandler方法支持与@Requestmap相同的返回值方法。

1.4.7. Controller Advice

通常,@ExceptionHandler@InitBinder@ModelAttribute方法在声明它们的@Controller类(或类层次结构)中应用。如果希望这样的方法在全局范围内(跨控制器)应用,可以在用@ControllerAdvice@RestControllerAdvice注释的类中声明它们。

@ControllerAdance@Component注释,这意味着此类类可以通过组件扫描注册为Spring Bean。@RestControllerAdance是一个合成批注,同时使用@ControllerAdvice@ResponseBody进行标注,这实质上意味着@ExceptionHandler方法通过消息转换(而不是视图解析或模板呈现)呈现给响应体。

在启动时,@Requestmap@ExceptionHandler方法的基础结构类检测用@ControllerAdance注释的Spring Bean,然后在运行时应用它们的方法。全局@ExceptionHandler方法(来自@ControllerAdvice)在本地方法(来自@控制器)之后应用。相比之下,全局@ModelAttribute@InitBinder方法在本地方法之前应用。

默认情况下,@ControllerAdance方法适用于每个请求(即所有控制器),但您可以通过使用注释上的属性将其缩小到控制器的子集,如下面的示例所示:

Java
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。

1.5. Functional Endpoints

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

1.5.1. Overview

在WebFlos.fn中,使用HandlerFunction处理HTTP请求:该函数接受ServerRequest,并返回延迟的ServerResponse(即Mono<;ServerResponse>;)。请求和响应对象都有不变的约定,这些约定提供对HTTP请求和响应的JDK 8友好访问。HandlerFunction相当于基于注释的编程模型中的@RequestMap方法体。

传入的请求被路由到具有RouterFunction的处理程序函数:该函数接受ServerRequest并返回延迟的HandlerFunction(即Mono<;HandlerFunction&>)。当路由器函数匹配时,返回处理程序函数;否则返回空的单声道。RouterFunction等同于@Requestmap注释,但主要区别在于路由器功能不仅提供数据,还提供行为。

RouterFunctions.route()提供路由器构建器,便于创建路由器,如下例所示:

Java
Kotlin
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() (1) .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) { // ... } } 
             
1 Create router using route().

运行RouterFunction的一种方法是将其转换为HttpHandler,并通过内置服务器适配器之一进行安装:

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction,处理程序策略)

大多数应用程序都可以通过WebFlux Java配置运行,请参阅运行服务器

1.5.2. HandlerFunction

ServerRequestServerResponse是不变的接口,它们提供对HTTP请求和响应的JDK 8友好访问。请求和响应都提供了对主体流的反应流背压。请求主体用反应器FluxMono表示。响应体由任何反应流发布者表示,包括FluxMono。有关详细信息,请参阅反应库

ServerRequest

ServerRequest提供对HTTP方法、URI、Header和查询参数的访问,而通过Body方法提供对Body的访问。

下面的示例将请求正文提取为单字符串>;

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

              

下面的示例将正文提取到Flux<;Person>;(或Kotlin中的flow<;Person>;),其中Person对象是从某种序列化格式(如JSON或XML)解码的:

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

              

前面的示例是使用更通用的ServerRequest.body(BodyExtractor),的快捷方式,它接受BodyExtractor功能策略接口。实用程序类BodyExtractors提供对许多实例的访问。例如,前面的示例也可以写成如下:

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

              

下面的示例显示如何访问表单数据:

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

              

以下示例显示如何以地图形式访问多部分数据:

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

              

以下示例显示如何以流方式一次访问一个部分的多部分数据:

Java
Kotlin
Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
allPartsEvents.windowUntil(PartEvent::isLast)
      .concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
          if (signal.hasValue()) {
              PartEvent event = signal.get();
              if (event instanceof FormPartEvent formEvent) {
                  String value = formEvent.value();
                  // handle form field
              }
              else if (event instanceof FilePartEvent fileEvent) {
                  String filename = fileEvent.filename();
                  Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
                  // handle file upload
              }
              else {
                  return Mono.error(new RuntimeException("Unexpected event: " + event));
              }
          }
          else {
              return partEvents; // either complete or error signal
          }
      }));

              

注意,必须完全使用、传递或释放PartEvent对象的正文内容,以避免内存泄漏。

ServerResponse

ServerResponse提供对HTTP响应的访问,并且由于它是不可变的,您可以使用Build方法来创建它。您可以使用构建器来设置响应状态、添加响应头或提供正文。下面的示例使用JSON内容创建一个200(OK)响应:

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

              

以下示例显示如何构建带有Location头而没有正文的201(已创建)响应:

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

              

根据使用的编解码器,可以传递提示参数来自定义正文的序列化或反序列化方式。例如,要指定Jackson JSON视图:

Java
Kotlin
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);

                
Handler Classes

我们可以将处理程序函数编写为lambda,如下面的示例所示:

Java
Kotlin
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().bodyValue("Hello World");

              
1 listPeople is a handler function that returns all Person objects found in the repository as JSON.
2 createPerson is a handler function that stores a new Person contained in the request body. Note that PersonRepository.savePerson(Person) returns Mono<Void>: an empty Mono that emits a completion signal when the person has been read from the request and stored. So we use the build(Publisher<Void>) method to send a response when that completion signal is received (that is, when the Person has been saved).
3 getPerson is a handler function that returns a single person, identified by the id path variable. We retrieve that Person from the repository and create a JSON response, if it is found. If it is not found, we use switchIfEmpty(Mono<T>) to return a 404 Not Found response.

这很方便,但在应用程序中,我们需要多个函数,而多个内联lambda可能会变得混乱。因此,将相关的处理程序函数组合到一个处理程序类中非常有用,该处理程序类与基于注释的应用程序中的@Controller具有类似的角色。例如,下面的类公开了一个被动的Person存储库:

Java
Kotlin
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 is a handler function that returns all Person objects found in the repository as JSON.
2 createPerson is a handler function that stores a new Person contained in the request body. Note that PersonRepository.savePerson(Person) is a suspending function with no return type.
3 getPerson is a handler function that returns a single person, identified by the id path variable. We retrieve that Person from the repository and create a JSON response, if it is found. If it is not found, we return a 404 Not Found response.
Validation

功能端点可以使用Spring的验证工具将验证应用于请求正文。例如,给定Person的自定义SpringValidator实现:

Java
Kotlin
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 Create Validator instance.
2 Apply validation.
3 Raise exception for a 400 response.

处理程序还可以使用标准的Bean验证API(JSR-303),方法是基于LocalValidatorFactoryBean创建并注入一个全局Validator实例。请参阅Spring验证

1.5.3. RouterFunction

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

一般来说,建议使用route()构建器,因为它为典型的映射场景提供了方便的快捷方式,而不需要难以发现的静态导入。例如,路由器函数构建器提供方法GET(字符串,HandlerFunction)为GET请求创建映射;为POST提供POST(字符串,HandlerFunction)方法。

除了基于HTTP方法的映射之外,路由构建器还提供了一种在映射到请求时引入附加谓词的方法。对于每个HTTP方法,都有一个以RequestPredicate为参数的重载变量,通过该参数可以表达额外的约束。

Predicates

您可以编写自己的RequestPredicate,但是RequestPredicates实用程序类提供了基于请求路径、HTTP方法、内容类型等的常用实现。下面的示例使用请求谓词基于Accept头创建约束:

Java
Kotlin
RouterFunction<ServerResponse> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> ServerResponse.ok().bodyValue("Hello World")).build();

              

您可以使用以下命令将多个请求谓词组合在一起:

  • RequestPredicate.and(RequestPredicate) — both必须匹配。

  • RequestPredicate.or(RequestPredicate) — either可以匹配。

RequestPredicates中的许多谓词都是组合的。例如,<代码>RequestPredicates.GET(字符串) RequestPredicates.method(HttpMethod)和<代码>RequestPredicates.Path(字符串) 组成。上面显示的示例还使用了两个请求谓词,因为构建器在内部使用RequestPredicates.GET,并将其与Accept谓词组合在一起。

Routes

按顺序评估路由器功能:如果第一条路由不匹配,则评估第二条,依此类推。因此,在宣布一般路线之前宣布更具体的路线是有意义的。这在将路由器功能注册为SpringBean时也很重要,稍后将对此进行描述。请注意,此行为不同于基于注释的编程模型,在该模型中,“最具体的”控制器方法是自动选取的。

使用路由器函数构建器时,所有定义的路由被组合成一个RouterFunction,该函数从Build()返回。还可以通过其他方式将多个路由器功能组合在一起:

  • RouterFunctions.route()生成器上添加(RouterFunction)

  • RouterFunction.and(RouterFunction)

  • RouterFunction.andRoute(RequestPredicate,处理程序函数) -使用嵌套的<代码>RouterFunctions.route() RouterFunction.and()的快捷方式。

以下示例显示了四条路由的组成:

Java
Kotlin
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} with an Accept header that matches JSON is routed to PersonHandler.getPerson
2 GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople
3 POST /person with no additional predicates is mapped to PersonHandler.createPerson, and
4 otherRoute is a router function that is created elsewhere, and added to the route built.
Nested Routes

一组路由器功能通常具有共享谓词,例如共享路径。在上面的示例中,共享谓词将是匹配/Person的路径谓词,由三个路由使用。在使用批注时,您可以通过使用映射到/Person的类型级@Requestmap批注来消除这种重复。在WebFlos.fn中,可以通过路由器函数构建器上的Path方法共享路径谓词。例如,上面示例的最后几行可以通过使用嵌套路由以以下方式进行改进:

Java
Kotlin
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 Note that second parameter of path is a consumer that takes the router builder.

尽管基于路径的嵌套是最常见的,但是通过使用构建器上的Nest方法,您可以在任何类型的谓词上进行嵌套。以上代码仍然以共享接受-标头谓词的形式包含一些重复项。我们可以通过使用Nest方法和Accept来进一步改进:

Java
Kotlin
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();

              

1.5.4. Running a Server

如何在HTTP服务器中运行路由器功能?一个简单的选项是使用以下方法之一将路由器函数转换为HttpHandler

  • RouterFunctions.toHttpHandler(RouterFunction)

  • RouterFunctions.toHttpHandler(RouterFunction,处理程序策略)

然后,您可以按照HttpHandler获取特定于服务器的说明,从而将返回的HttpHandler用于多个服务器适配器。

一种更典型的选择,也被Spring Boot使用,是通过WebFlux ConfigDispatcherHandler-based设置一起运行,它使用Spring配置来声明处理请求所需的组件。WebFlux Java配置声明了以下基础架构组件以支持功能端点:

前面的组件使功能端点适合DispatcherHandler请求处理生命周期,并且(可能)与带注释的控制器并行运行(如果声明了任何控制器的话)。这也是Spring Boot WebFlux starter启用功能端点的方式。

以下示例显示了一个WebFlux Java配置(有关如何运行它,请参阅DispatcherHandler):

Java
Kotlin
@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...
    }
}

             

1.5.5. Filtering Handler Functions

您可以使用路由函数构建器上的BEFOREAfterFilter方法来筛选处理程序函数。通过使用注释,您可以使用@ControllerAdviceServletFilter或同时使用两者来实现类似的功能。过滤器将应用于构建方构建的所有路由。这意味着嵌套路由中定义的过滤器不适用于“顶级”路由。例如,考虑以下示例:

Java
Kotlin
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 The before filter that adds a custom request header is only applied to the two GET routes.
2 The after filter that logs the response is applied to all routes, including the nested ones.

路由器生成器上的Filter方法接受HandlerFilterFunction:该函数接受ServerRequestHandlerFunction,并返回ServerResponse。处理程序函数参数表示链中的下一个元素。这通常是路由到的处理程序,但如果应用了多个,它也可以是另一个筛选器。

现在,我们可以向我们的路由添加一个简单的安全筛选器,假设我们有一个SecurityManager可以确定是否允许特定路径。以下示例显示了如何执行此操作:

Java
Kotlin
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();

             

前面的示例演示了调用next.Handle(ServerRequest)是可选的。我们只在允许访问时才让处理程序函数运行。

除了在路由器函数构建器上使用Filter方法外,还可以通过RouterFunction.filter(HandlerFilterFunction).将过滤器应用于现有的路由器函数

CORS support for functional endpoints is provided through a dedicated CorsWebFilter.

1.6. URI Links

本节描述了Spring框架中可用于准备URI的各种选项。

1.6.1. UriComponents

Spring MVC和Spring WebFlux

UriComponentsBuilder帮助从带有变量的URI模板构建URI,如下例所示:

Java
Kotlin
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 Static factory method with a URI template.
2 Add or replace URI components.
3 Request to have the URI template and URI variables encoded.
4 Build a UriComponents.
5 Expand variables and obtain the URI.

上面的示例可以合并到一个链中,并使用BuildAndExpand缩写,如下例所示:

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

             

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

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

             

您可以使用完整的URI模板进一步缩短它,如下例所示:

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

             

1.6.2. UriBuilder

Spring MVC和Spring WebFlux

UriComponentsBuilder实现<代码>UriBuilder 。您可以依次使用UriBuilderFactory创建UriBuilderUriBuilderFactoryUriBuilder一起提供了一种可插拔的机制,以便基于共享配置(如基本URL、编码首选项和其他细节)从URI模板构建URI。

您可以使用UriBuilderFactory配置RestTemplateWebClient,以自定义URI的准备。DefaultUriBuilderFactoryUriBuilderFactory的默认实现,它在内部使用UriComponentsBuilder并公开共享配置选项。

以下示例显示如何配置RestTemplate

Java
Kotlin
// 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);

             

以下示例配置WebClient

Java
Kotlin
// 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();

             

另外,您也可以直接使用DefaultUriBuilderFactory。它类似于使用UriComponentsBuilder,但它不是静态工厂方法,而是保存配置和首选项的实际实例,如下例所示:

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

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

             

1.6.3. URI Encoding

Spring MVC和Spring WebFlux

UriComponentsBuilder在两个级别公开编码选项:

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

Consider ";", which is legal in a path but has reserved meaning. The first option replaces ";" with "%3B" in URI variables but not in the URI template. By contrast, the second option never replaces ";", since it is a legal character in a path.

对于大多数情况,第一个选项可能会给出预期的结果,因为它将URI变量视为要完全编码的不透明数据,而第二个选项在URI变量确实有意包含保留字符时非常有用。第二个选项在根本不展开URI变量时也很有用,因为它还会编码任何偶然看起来像URI变量的内容。

下面的示例使用第一个选项:

Java
Kotlin
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"

             

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

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

             

您可以使用完整的URI模板进一步缩短它,如下例所示:

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

             

WebClientRestTemplate通过UriBuilderFactory策略在内部展开和编码URI模板。两者都可以使用自定义策略进行配置,如下例所示:

Java
Kotlin
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();

             

DefaultUriBuilderFactory实现在内部使用UriComponentsBuilder来展开和编码URI模板。作为工厂,它根据以下编码模式之一提供单一位置来配置编码方法:

  • TEMPLATE_AND_VALUES:使用UriComponentsBuilder#encode(),对应于前面列表中的第一个选项,预编码URI模板,并在展开时严格编码URI变量。

  • VALUES_ONLY:不对URI模板进行编码,而是在将URI变量展开到模板中之前,通过UriUtils#encodeUriVariables对它们进行严格编码。

  • URI_Components:在URI变量展开后,使用UriComponents#encode(),对应于前面列表中的第二个选项,对URI组件值进行编码。

  • :未应用编码。

出于历史原因和向后兼容性的原因,将RestTemplate设置为EncodingMode.URI_ComponentWebClient依赖DefaultUriBuilderFactory中的默认值,从5.0.x中的EncodingMode.URI_Component更改为5.1中的EncodingMode.TEMPLATE_AND_VALUES

1.7. CORS

Spring WebFlux允许您处理CORS(跨域资源共享)。本节介绍如何执行此操作。

1.7.1. Introduction

出于安全原因,浏览器禁止对当前来源之外的资源进行AJAX调用。例如,您可以将银行账户放在一个选项卡中,而将evil.com放在另一个选项卡中。来自evil.com的脚本应该不能使用您的凭据 - 向您的银行API发出AJAX请求,例如,从您的帐户中提取资金!

跨域资源共享(CORS)是由大多数浏览器实现的W3C规范,它允许您指定授权哪种类型的跨域请求,而不是使用基于IFRAME或JSONP的安全性较低、功能较弱的解决方案。

1.7.2. Processing

CORS规范区分了印前检查、简单请求和实际请求。要了解CORS的工作原理,您可以阅读本文,或查看规范以了解更多详细信息。

Spring WebFluxHandlermap实现提供了对CORS的内置支持。在成功地将请求映射到处理程序之后,Handlermap检查给定请求和处理程序的CORS配置,并采取进一步的操作。印前检查请求被直接处理,而简单和实际的CORS请求被拦截、验证,并设置了所需的CORS响应头。

为了启用跨域请求(即存在Origin头部,且与请求的host不同),您需要有一些显式声明的CORS配置。如果未找到匹配的CORS配置,则会拒绝印前检查请求。没有CORS头添加到简单和实际的CORS请求的响应中,因此,浏览器会拒绝它们。

可以使用基于URL模式的CorsConfiguration映射单独配置每个HandlerMap。在大多数情况下,应用程序使用WebFlux Java配置来声明这样的映射,这会导致将单个全局映射传递给所有的HandlerMapping实现。

您可以将Handlermap级别的全局CORS配置与更细粒度、处理程序级别的CORS配置相结合。例如,带注释的控制器可以使用类或方法级别的@CrossOrigin注释(其他处理程序可以实现CorsConfigurationSource)。

用于组合全局和本地配置的规则通常是累加的 - ,例如,所有全局和所有本地来源。对于那些只能接受单个值的属性,例如AllowCredentialsMaxAge,本地值覆盖全局值。有关更多详细信息,请参阅CorsConfiguration#combine(CorsConfiguration)

要从源代码中了解更多信息或进行高级定制,请参阅:

  • 相关配置

  • CorsProcessorDefaultCorsProcessor

  • AbstractHandlerMapping

1.7.3. @CrossOrigin

@CrossOrigin注释允许跨域请求带注释的控制器方法,如下例所示:

Java
Kotlin
@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) {
        // ...
    }
}

             
1 Using @CrossOrigin at the class level.
2 Using @CrossOrigin at the method level.

默认情况下,@CrossOrigin允许:

  • 都是起源。

  • 所有标题。

  • 控制器方法映射到的所有HTTP方法。

AllowCredentials默认情况下未启用,因为这会建立一个信任级别,该级别会公开敏感的用户特定信息(如Cookie和CSRF令牌),并且应该仅在适当的情况下使用。启用时,必须将AllowOrigins设置为一个或多个特定域(但不是特殊值“*”),或者可以使用AllowOriginPatterns属性来匹配一组动态来源。

MaxAge设置为30分钟。

@CrossOrigin在类级别也受支持,并由所有方法继承。下面的示例指定某个域,并将MaxAge设置为小时:

Java
Kotlin
@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) {
        // ...
    }
}

             
1 Using @CrossOrigin at the class level.
2 Using @CrossOrigin at the method level.

您可以在类和方法级别使用@CrossOrigin,如下面的示例所示:

Java
Kotlin
@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.7.4. Global Configuration

除了细粒度的控制器方法级配置之外,您可能还希望定义一些全局CORS配置。您可以在任何HandlerMap上分别设置基于URL的CorsConfiguration映射。然而,大多数应用程序使用WebFlux Java配置来实现这一点。

默认情况下,全局配置启用以下功能:

  • 都是起源。

  • 所有标题。

  • GETHeadPOST方法。

AllowedCredentials默认情况下未启用,因为这会建立一个信任级别,该级别会公开敏感的用户特定信息(如Cookie和CSRF令牌),并且应该仅在适当的情况下使用。启用时,必须将AllowOrigins设置为一个或多个特定域(但不是特殊值“*”),或者可以使用AllowOriginPatterns属性来匹配一组动态来源。

MaxAge设置为30分钟。

要在WebFlux Java配置中启用CORS,可以使用CorsRegistry回调,如下例所示:

Java
Kotlin
@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...
    }
}

             

1.7.5. CORS WebFilter

您可以通过内置的CorsWebFilter应用CORS支持,这非常适合功能端点

If you try to use the CorsFilter with Spring Security, keep in mind that Spring Security has built-in support for CORS.

要配置筛选器,可以声明一个CorsWebFilterBean并将CorsConfigurationSource传递给它的构造函数,如下面的示例所示:

Java
Kotlin
@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);
}

             

1.8. Error Responses

REST服务的一个常见要求是在错误响应正文中包含详细信息。Spring框架支持RFC7807的“HTTAPIs问题详细信息”规范。

以下是此支持的主要抽象:

  • ProblemDetailRFC7807问题详细信息的 - 表示形式;规范中定义的标准字段和非标准字段的简单容器。

  • ErrorResponse - 约定公开HTTp错误响应详细信息,包括HTTp状态、响应头和RFC7807格式的正文;这允许异常封装和公开它们如何映射到HTTp响应的详细信息。所有的Spring WebFlux异常都实现了这一点。

  • <代码>错误响应异常 - Basic<代码>错误响应实现,其他人可以将其用作方便的基类。

  • @ControllerAdviceResponseEntityExceptionHandler — convenient基类,它处理所有Spring WebFlux异常和任何ErrorResponseException,并呈现一个带有正文的错误响应。

1.8.1. Render

您可以从任何@ExceptionHandler或任何@Requestmap方法返回ProblemDetailErrorResponse,以呈现RFC 7807响应。其处理过程如下:

  • ProblemDetailStatus属性确定HTTP状态。

  • ProblemDetail实例属性是从当前URL路径设置的,如果尚未设置的话。

  • 对于内容协商,在呈现ProblemDetail时,JacksonHttpMessageConverter优先选择应用程序/问题+json而不是应用程序/json,如果找不到兼容的媒体类型,也会求助于它。

要为Spring WebFlux异常和任何ErrorResponseException启用RFC 7807响应,请扩展ResponseEntityExceptionHandler,并在Spring配置中将其声明为@ControllerAdvice。该处理程序有一个@ExceptionHandler方法,用于处理任何ErrorResponse异常,其中包括所有内置的Web异常。您可以添加更多异常处理方法,并使用受保护的方法将任何异常映射到ProblemDetail

1.8.2. Non-Standard Fields

您可以通过以下两种方式之一使用非标准字段扩展RFC 7807响应。

一、插入到ProblemDetail的“属性”映射中。当使用Jackson库时,Spring框架注册ProblemDetailJacksonMixin,以确保这个“属性”Map被解包并呈现为响应中的顶级JSON属性,同样,在反序列化过程中的任何未知属性都被插入到这个Map中。

您还可以扩展ProblemDetail以添加专用的非标准属性。ProblemDetail中的复制构造函数允许从现有的ProblemDetail创建一个子类。这可以例如从诸如ResponseEntityExceptionHandler之类的@ControllerAdvice集中完成,该将异常的ProblemDetail重新创建到具有附加非标准字段的子类中。

1.8.3. Internationalization

国际化错误响应详细信息是一个常见的需求,而定制Spring WebFlux异常的问题详细信息是一个很好的实践。其支持方式如下:

  • 每个ErrorResponse都公开一个消息代码和参数,以通过MessageSource解析“Detail”字段。实际的消息代码值使用占位符进行参数化,例如“不支持的HTTP方法{0}”从参数展开。

  • 每个ErrorResponse还公开一个消息代码来解析“标题”字段。

  • ResponseEntityExceptionHandler使用消息代码和参数来解析“详细信息”和“标题”字段。

默认情况下,“Detail”字段的消息代码是“problemDetail.”+完全限定的异常类名。某些异常可能会公开其他消息代码,在这种情况下,会在默认消息代码中添加后缀。下表列出了Spring WebFlux异常的消息参数和代码:

Exception Message Code Message Code Arguments

UnsupportedMediaTypeStatusException

(违约)

{0}不支持的媒体类型,{1}支持的媒体类型列表

UnsupportedMediaTypeStatusException

(默认)+“.parseError”

MissingRequestValueException

(违约)

{0}值的标签(例如“RequestHeader”、“Cookie值”、…​),<代码>{1} 值名称

UnsatisfiedRequestParameterException

(违约)

{0}参数条件列表

WebExchangeBindException

(违约)

{0}全局错误列表,{1}字段错误列表。BindingResult内每个错误的消息代码和参数也通过MessageSource进行解析。

NotAccepableStatusException

(违约)

{0}支持的媒体类型列表

NotAccepableStatusException

(默认)+“.parseError”

服务器错误异常

(违约)

{0}提供给类构造函数的失败原因

方法NotMillweException

(违约)

{0}当前的HTTP方法,{1}受支持的HTTP方法列表

默认情况下,“TITLE”字段的消息代码是“problemDetail.title.”+完全限定的异常类名。

1.8.4. Client Handling

客户端应用程序可以在使用WebClient时捕获WebClientResponseException,或在使用RestTemplate时捕获RestClientResponseException,并使用它们的getResponseBodyAs方法将错误响应体解码为任何目标类型,如ProblemDetailProblemDetail的子类。

1.9. Web Security

Spring Security项目为保护Web应用程序免受恶意攻击提供支持。请参阅Spring安全参考文档,其中包括:

1.10. HTTP Caching

HTTP缓存可以显著提高Web应用程序的性能。Http缓存围绕缓存-控制响应头和后续的条件请求头,如Last-ModifyEtag缓存控制建议私有(例如浏览器)和公共(例如代理)缓存如何缓存和重用响应。Etag头用于进行条件请求,如果内容没有更改,则可能导致304(NOT_MODIFIED)没有正文。Etag可以被视为Last-Modify头的更复杂的继承者。

本节介绍了Spring WebFlux中可用的与HTTP缓存相关的选项。

1.10.1. CacheControl

CacheControl支持配置与Cache-Control头相关的设置,并在许多地方被接受为参数:

RFC 7234描述了Cache-Control响应头的所有可能指令,而CacheControl类型采用面向用例的方法,侧重于常见场景,如下例所示:

Java
Kotlin
// 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();

             

1.10.2. Controllers

控制器可以添加对HTTP缓存的显式支持。我们建议这样做,因为需要计算资源的lastModifiedEtag值,然后才能将其与条件请求头部进行比较。控制器可以将Etag缓存-控制设置添加到ResponseEntity,如下面的示例所示:

Java
Kotlin
@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);
}

             
1 Application-specific calculation.
2 Response has been set to 304 (NOT_MODIFIED). No further processing.
3 Continue with request processing.

如果与条件请求头的比较表明内容没有更改,则前面的示例发送带有空正文的304(NOT_MODIFIED)响应。否则,EtagCache-Control头被添加到响应中。

您还可以在控制器中对条件请求头进行检查,如下面的示例所示:

Java
Kotlin
@RequestMapping
public String myHandleMethod(ServerWebExchange exchange, Model model) {

    long eTag = ... (1)

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

    model.addAttribute(...); (3)
    return "myViewName";
}

             
1 Application-specific calculation.
2 Response has been set to 304 (NOT_MODIFIED). No further processing.
3 Continue with request processing.

有三种变量用于根据eTag值和/或lastModified值检查条件请求。对于有条件的GETHead请求,可以将响应设置为304(NOT_MODIFIED)。对于有条件的POSTPUTDELETE,您可以将响应设置为412(Predition_FAILED)以防止并发修改。

1.10.3. Static Resources

您应该为静态资源提供Cache-Control和条件响应头,以获得最佳性能。请参阅配置静态资源部分。

1.11. View Technologies

在Spring WebFlux中使用视图技术是可插拔的。您是否决定使用Thymeleaf、FreeMarker或其他一些视图技术主要是一个配置更改的问题。本章介绍了与Spring WebFlux集成的视图技术。我们假定您已经熟悉查看分辨率

1.11.1. Thymeleaf

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

Thymeleaf与Spring WebFlux的集成由Thymeleaf项目管理。配置涉及几个Bean声明,如SpringResourceTemplateResolverSpringWebFlosTemplateEngineThymeleafReactive veViewResolver。有关更多详细信息,请参阅Thymeleaf+Spring和WebFlux集成公告

1.11.2. FreeMarker

Apache FreeMarker是一个模板引擎,用于生成从HTML到电子邮件等的任何类型的文本输出。Spring框架具有内置的集成,可以将Spring WebFlux与FreeMarker模板结合使用。

View Configuration

以下示例显示如何将FreeMarker配置为视图技术:

Java
Kotlin
@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;
    }
}

              

您的模板需要存储在由FreeMarkerConfigurer指定的目录中,如上例所示。根据前面的配置,如果您的控制器返回视图名<代码>欢迎 ,则解析器将查找classpath:/templates/freemarker/welcome.ftl模板。

FreeMarker Configuration

通过在FreeMarkerConfigurerBean上设置适当的Bean属性,可以将FreeMarker的“设置”和“SharedVariables”直接传递给FreeMarker配置对象(由Spring管理)。freemarkerSetting属性需要java.util.Properties对象,而freemarkerVariables属性需要java.util.Map。以下示例显示如何使用FreeMarkerConfigurer

Java
Kotlin
@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;
    }
}

              

有关应用于配置对象的设置和变量的详细信息,请参阅FreeMarker文档。

Form Handling

Spring提供了一个在JSP中使用的标记库,其中包含一个<;Spring:Bind/>;元素。该元素主要允许表单显示来自表单支持对象的值,并显示来自Web或业务层中的验证器的失败验证的结果。Spring还支持在FreeMarker中使用相同的功能,并增加了用于生成表单输入元素本身的便利宏。

The Bind Macros

在FreeMarker的Spring-webflux.jar文件中维护了一组标准的宏,因此它们始终可用于适当配置的应用程序。

在Spring模板库中定义的一些宏被认为是内部的(私有的),但宏定义中不存在这样的作用域,从而使所有的宏对调用代码和用户模板都可见。以下部分仅集中介绍需要从模板内直接调用的宏。如果您希望直接查看宏代码,则该文件名为spring.ftl,位于org.springframework.web.reactive.result.view.freemarker包中。

有关绑定支持的更多详细信息,请参阅简单绑定for Spring MVC。

Form Macros

有关Spring对FreeMarker模板的表单宏支持的详细信息,请参考Spring MVC文档的以下部分。

1.11.3. Script Views

Spring框架有一个内置的集成,可以将Spring WebFlux与任何可以在JSR-223Java脚本引擎上运行的模板库一起使用。下表显示了我们在不同脚本引擎上测试的模板库:

Scripting Library Scripting Engine

车把

Nashorn

胡子

Nashorn

反应

Nashorn

ejs

Nashorn

再培训局

JRuby

字符串模板

Jython

Kotlin脚本模板

柯特林

The basic rule for integrating any other script engine is that it must implement the ScriptEngine and Invocable interfaces.
Requirements

您的类路径上需要有脚本引擎,其详细信息因脚本引擎而异:

  • NashornJavaScript引擎随Java 8+一起提供。强烈建议使用可用的最新更新版本。

  • 应该将JRuby添加为Ruby支持的依赖项。

  • 应将Jython添加为对Python支持的依赖项。

  • 应该添加org.jetbrains.kotlin:kotlin-script-util依赖项和包含META-INF/services/javax.script.ScriptEngineFactory行的org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory文件以支持kotlin脚本。有关详细信息,请参阅此示例

您需要有脚本模板库。实现这一点的一种方法是通过WebJars

Script Templates

您可以声明ScriptTemplateConfigurerBean,以指定要使用的脚本引擎、要加载的脚本文件、要调用什么函数来呈现模板等。下面的示例使用Mustache模板和Nashorn JavaScript引擎:

Java
Kotlin
@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;
    }
}

              

使用以下参数调用Render函数:

  • 字符串模板:模板内容

  • 映射模型:视图模型

  • RenderingContext renderingContextRenderingContext,用于访问应用程序上下文、区域设置、模板加载器和URL(从5.0开始)

Mustahe.render()与该签名原生兼容,可以直接调用。

如果您的模板技术需要一些定制,您可以提供一个实现定制呈现功能的脚本。例如,Handlerbar需要在使用模板之前对其进行编译,并且需要一个PolyFill来模拟服务器端脚本引擎中没有的一些浏览器工具。下面的示例显示如何设置自定义呈现函数:

Java
Kotlin
@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;
    }
}

              
Setting the sharedEngine property to false is required when using non-thread-safe script engines with templating libraries not designed for concurrency, such as Handlebars or React running on Nashorn. In that case, Java SE 8 update 60 is required, due to this bug, but it is generally recommended to use a recent Java SE patch release in any case.

Polyfit.js仅定义Handlebar正常运行所需的Window对象,如以下代码片断所示:

var window = {};
              

这个基本的render.js实现在使用模板之前对其进行编译。生产就绪的实现还应该存储和重用缓存的模板或预编译的模板。这可以在脚本端完成,也可以进行所需的任何定制(例如管理模板引擎配置)。以下示例显示如何编译模板:

function render(template, model) {
    var compiledTemplate = Handlebars.compile(template);
    return compiledTemplate(model);
}
              

有关更多配置示例,请查看Spring框架单元测试、Java资源

1.11.4. JSON and XML

出于内容协商的目的,根据客户端请求的内容类型,能够在使用HTML模板或以其他格式(如JSON或XML)呈现模型之间进行交替是很有用的。为了支持这一点,Spring WebFlux提供了HttpMessageWriterView,您可以使用它来插入Spring-web中可用的任何编解码器,例如Jackson2JsonEncodeJackson2SmileEncodeJaxb2XmlEncode

与其他视图技术不同,HttpMessageWriterView不需要ViewResolver,而是将配置为默认视图。您可以配置一个或多个这样的默认视图,包装不同的HttpMessageWriter实例或编码器实例。与请求的内容类型匹配的内容类型在运行时使用。

在大多数情况下,一个模型包含多个属性。要确定序列化哪一个,可以使用用于呈现的模型属性的名称配置HttpMessageWriterView。如果模型只包含一个属性,则使用该属性。

1.12. WebFlux Config

WebFlux Java配置声明了使用带注释的控制器或功能端点处理请求所需的组件,并提供了一个用于定制配置的API。这意味着您不需要了解Java配置创建的底层Bean。但是,如果您想了解它们,您可以在WebFlosConfigurationSupport中看到它们,或者阅读Special Bean Types中有关它们的更多信息。

对于配置API中不提供的更高级定制,您可以通过高级配置模式完全控制配置。

1.12.1. Enabling WebFlux Config

您可以在Java配置中使用@EnableWebFlux注释,如下面的示例所示:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig {
}

             

前面的示例注册了许多Spring WebFlux基础设施Bean,并适应类路径 - 上的依赖项。

1.12.2. WebFlux config API

在您的Java配置中,您可以实现WebFlosConfigurer接口,如下例所示:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    // Implement configuration methods...
}

             

1.12.3. Conversion, formatting

默认情况下,安装了各种数字和日期类型的格式化程序,并支持通过@NumberFormat@DateTimeFormat对字段进行自定义。

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

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

}

             

默认情况下,在解析和格式化日期值时,Spring WebFlux会考虑请求区域设置。这适用于将日期表示为带有“输入”表单域的字符串的表单。然而,对于“日期”和“时间”表单域,浏览器使用在HTML规范中定义的固定格式。对于这种情况,可以按如下方式自定义日期和时间格式:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}

             
See FormatterRegistrar SPI and the FormattingConversionServiceFactoryBean for more information on when to use FormatterRegistrar implementations.

1.12.4. Validation

默认情况下,如果类路径上存在Bean验证(例如,Hibernate Validator),则LocalValidatorFactoryBean注册为全局验证器,用于@Valid@Controller方法参数。

在Java配置中,您可以自定义全局Validator实例,如下例所示:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

}

             

请注意,您还可以在本地注册Validator实现,如下面的示例所示:

Java
Kotlin
@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }

}

             
If you need to have a LocalValidatorFactoryBean injected somewhere, create a bean and mark it with @Primary in order to avoid conflict with the one declared in the MVC config.

1.12.5. Content Type Resolvers

您可以配置Spring WebFlux如何从请求中为@控制器实例确定所请求的媒体类型。默认情况下,只选中Accept头,但您也可以启用基于参数的查询策略。

以下示例显示如何自定义请求的内容类型解析:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
        // ...
    }
}

             

1.12.6. HTTP message codecs

以下示例显示如何自定义请求和响应正文的读取和写入方式:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().maxInMemorySize(512 * 1024);
    }
}

             

ServerCodecConfigurer提供一组默认读取器和写入器。您可以使用它来添加更多的读取器和写入器,定制默认的读取器和写入器,或者完全替换默认的读取器和写入器。

对于Jackson JSON和XML,考虑使用Jackson2ObjectMapperBuilder,,它使用以下属性定制Jackson的默认属性:

如果在类路径上检测到以下众所周知的模块,它还会自动注册这些模块:

  • jackson-datatype-joda:支持Joda-Time类型。

  • jackson-datatype-jsr310:支持。

  • jackson-datatype-jdk8:>可选。

  • jackson-module-kotlin:支持。

1.12.7. View Resolvers

以下示例显示如何配置视图分辨率:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

             

ViewResolverRegistry提供了与Spring框架集成的视图技术的快捷方式。下面的示例使用了FreeMarker(它还需要配置底层的FreeMarker视图技术):

Java
Kotlin
@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;
    }
}

             

您还可以插入任何ViewResolver实现,如下例所示:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        ViewResolver resolver = ... ;
        registry.viewResolver(resolver);
    }
}

             

为了支持内容协商和通过视图解析呈现其他格式(除了HTML),您可以基于HttpMessageWriterView实现配置一个或多个默认视图,它接受来自Spring-web的任何可用的编解码器。以下示例显示了如何执行此操作:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {


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

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

    // ...
}

             

有关与Spring WebFlux集成的视图技术的更多信息,请参见View Technologies

1.12.8. Static Resources

此选项提供了一种从基于资源的位置列表中提供静态资源的便捷方法。

在下一个示例中,给定一个以/resource开头的请求,相对路径用于查找和提供类路径上相对于/静态的静态资源。资源的未来有效期为一年,以确保最大限度地使用浏览器缓存并减少浏览器发出的HTTP请求。还会计算最后修改的标头,如果存在,则返回304状态代码。下面的列表显示了该示例:

Java
Kotlin
@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));
    }

}

             

资源处理程序还支持一系列ResourceResolver实现和资源转换器实现,这些实现可用于创建使用优化资源的工具链。

您可以根据根据内容、固定应用程序版本或其他信息计算出的MD5哈希,对版本化资源URL使用VersionResourceResolverContentVersionStrategy(MD5散列)是一个很好的选择,但有一些值得注意的例外(例如与模块加载器一起使用的JavaScript资源)。

以下示例显示如何在Java配置中使用VersionResourceResolver

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

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

}

             

您可以使用ResourceUrlProvider重写URL并应用整个解析器和转换器链(例如,插入版本)。WebFlux配置提供了一个ResourceUrlProvider,以便可以注入到其他对象中。

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

请注意,当同时使用EncodedResourceResolver(例如,Gzip,Brotli ended)和VersionedResourceResolver时,必须按该顺序注册它们,以确保始终根据未编码的文件可靠地计算基于内容的版本。

对于WebJars,像/webjars/jquery/1.2.0/jquery.min.js这样的版本化URL是推荐和最有效的使用方式。相关的资源位置由Spring Boot现成配置(也可以通过ResourceHandlerRegistry手动配置),不需要添加org.webjars:webjars-Locator-core依赖项。

通过WebJarsResourceResolver支持像/webjars/jQuery/jquery.min.js这样的无版本URL,当类路径上存在org.webjars:webjars-Locator-core库时,会自动注册该URL,代价是进行类路径扫描,这可能会降低应用程序的启动速度。解析器可以重写URL以包括JAR的版本,还可以与从/Webjars/jquery/jquery.min.js到/webjars/jquery/1.2.0/jquery.min.js.的没有 - 版本的传入URL进行匹配

The Java configuration based on ResourceHandlerRegistry provides further options for fine-grained control, e.g. last-modified behavior and optimized resource resolution.

1.12.9. Path Matching

您可以自定义与路径匹配相关的选项。有关各个选项的详细信息,请参阅PathMatchConfigurerjavadoc。以下示例说明如何使用PathMatchConfigurer

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setUseCaseSensitiveMatch(true)
            .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
    }
}

             

Spring WebFlux依赖名为RequestPath的请求路径的解析表示来访问已解码的路径段值,删除了分号内容(即PATH或矩阵变量)。这意味着,与在Spring MVC中不同,您不需要指明是否解码请求路径,也不需要出于路径匹配的目的而删除分号内容。

Spring WebFlux也不支持后缀模式匹配,这与在Spring MVC中不同,在Spring MVC中,我们也建议摆脱对它的依赖。

1.12.10. WebSocketService

WebFlux Java配置声明了一个WebSocketHandlerAdapterBean,它为WebSocket处理程序的调用提供支持。这意味着要处理WebSocket握手请求,剩下的工作就是通过SimpleUrlHandlerMappingWebSocketHandler映射到URL。

在某些情况下,可能需要使用提供的WebSocketService服务创建WebSocketHandlerAdapterBean,该服务允许配置WebSocket服务器属性。例如:

Java
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

    @Override
    public WebSocketService getWebSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}

             

1.12.11. Advanced Configuration Mode

@EnableWebFlux导入DelegatingWebFlosConfiguration

  • 为WebFlux应用程序提供默认的Spring配置

  • 检测并委托给WebFlosConfigurer实现以自定义该配置。

对于高级模式,您可以移除@EnableWebFlux,直接从DelegatingWebFlosConfigurer扩展,而不是实现WebFlosConfigurer,如下例所示:

Java
Kotlin
@Configuration
public class WebConfig extends DelegatingWebFluxConfiguration {

    // ...
}

             

您可以将现有方法保留在WebConfig中,但现在也可以覆盖基类中的Bean声明,并在类路径上仍具有任意数量的其他WebMvcConfigurer实现。

1.13. HTTP/2

反应器Netty、Tomcat、Jetty和Undertow支持HTTP/2。但是,有一些与服务器配置相关的注意事项。有关更多详细信息,请参阅HTTP/2维基页面

2. WebClient

Spring WebFlux包括一个用来执行HTTP请求的客户端。WebClient有一个基于反应器的功能流畅的API,请参阅反应库,它支持声明式合成异步逻辑,而不需要处理线程或并发。它是完全非阻塞的,它支持流,并依赖于同样的编解码器,这些编解码器也用于在服务器端编码和解码请求和响应内容。

WebClient需要一个HTTP客户端库来执行请求。它内置了对以下功能的支持:

2.1. Configuration

创建WebClient的最简单方法是通过静态工厂方法之一:

  • WebClient.create()

  • WebClient.create(字符串基URL)

您还可以将WebClient.Builder()与其他选项一起使用:

  • uriBuilderFactory:自定义的UriBuilderFactory用作基本URL。

  • defaultUriVariables:展开URI模板时使用的默认值。

  • defaultHeader:每个请求的Header。

  • defaultCookie:每个请求的Cookie。

  • defaultRequestConsumer定制每个请求。

  • Filter:客户端对每个请求进行过滤。

  • exchangeStrategy:http消息读取器/写入器定制。

  • clientConnector:HTTP客户端库设置。

例如:

Java
Kotlin
WebClient client = WebClient.builder()
        .codecs(configurer -> ... )
        .build();

            

一旦构建,WebClient就是不可变的。但是,您可以克隆它并构建修改后的副本,如下所示:

Java
Kotlin
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

            

2.1.1. MaxInMemorySize

编解码器对在内存中缓冲数据有限制,以避免应用程序内存问题。默认情况下,这些大小设置为256KB。如果这还不够,您将得到以下错误:

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

要更改默认编解码器的限制,请使用以下命令:

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

             

2.1.2. Reactor Netty

要自定义反应器Netty设置,请提供预配置的HttpClient

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

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

             
Resources

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

如果服务器与进程一起计时,则通常不需要显式关闭。但是,如果服务器可以启动或停止进程内(例如,部署为WAR的Spring MVC应用程序),则可以使用global alResources=true(缺省值)声明一个ReactorResourceFactory类型的Spring托管Bean,以确保在关闭SpringApplicationContext时关闭Reector Netty全局资源,如下面的示例所示:

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

              
1 Create resources independent of global ones.
2 Use the ReactorClientHttpConnector constructor with resource factory.
3 Plug the connector into the WebClient.Builder.

您也可以选择不参与全球反应堆Netty资源。但是,在此模式下,您需要确保所有反应器Netty客户端和服务器实例都使用共享资源,如下例所示:

Java
Kotlin
@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 Create resources independent of global ones.
2 Use the ReactorClientHttpConnector constructor with resource factory.
3 Plug the connector into the WebClient.Builder.
Timeouts

要配置连接超时,请执行以下操作:

Java
Kotlin
import io.netty.channel.ChannelOption; HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000); WebClient webClient = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); 
              

配置读取或写入超时的步骤:

Java
Kotlin
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... 
              

要为所有请求配置响应超时:

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

// Create WebClient...

              

要配置特定请求的响应超时,请执行以下操作:

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

              

2.1.3. JDK HttpClient

以下示例展示如何自定义JDKHttpClient

Java
Kotlin
HttpClient httpClient = HttpClient.newBuilder()
    .followRedirects(Redirect.NORMAL)
    .connectTimeout(Duration.ofSeconds(20))
    .build();

ClientHttpConnector connector =
        new JdkClientHttpConnector(httpClient, new DefaultDataBufferFactory());

WebClient webClient = WebClient.builder().clientConnector(connector).build();

             

2.1.4. Jetty

以下示例显示如何自定义JettyHttpClient设置:

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

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

             
1 Use the JettyClientHttpConnector constructor with resource factory.
2 Plug the connector into the WebClient.Builder.

默认情况下,HttpClient创建自己的资源(ExecutorByteBufferPoolScheduler),这些资源一直保持活动状态,直到进程退出或Stop()被调用。

您可以在Jetty客户端(和服务器)的多个实例之间共享资源,并通过声明类型为JettyResourceFactory的Spring托管Bean,确保在关闭SpringApplicationContext时关闭资源,如下面的示例所示:

Java
Kotlin
@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 Use the JettyClientHttpConnector constructor with resource factory.
2 Plug the connector into the WebClient.Builder.

2.1.5. HttpComponents

以下示例显示如何自定义Apache HttpComponentsHttpClient设置:

Java
Kotlin
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(...);
CloseableHttpAsyncClient client = clientBuilder.build();

ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);

WebClient webClient = WebClient.builder().clientConnector(connector).build();

             

2.2. retrieve()

方法的作用是:声明如何提取响应。例如:

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

            

或者只得到肉体:

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

Mono<Person> result = client.get()
        .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .bodyToMono(Person.class);

            

要获取已解码对象的流,请执行以下操作:

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

            

默认情况下,4xx或5xx响应会导致WebClientResponseException,其中包含特定HTTP状态码的子类。要自定义错误响应的处理,请使用onStatus处理程序,如下所示:

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

            

2.3. Exchange

exchangeToMono()exchangeToFlux()方法(或Kotlin中的waitExchange{}exchangeToFlow{})对于需要更多控制的更高级情况很有用,例如根据响应状态对响应进行不同的解码:

Java
Kotlin
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.createError();
            }
        });

            

当使用上面的方法时,在返回的MonoFlux完成后,检查响应体,如果没有使用,则释放它,以防止内存和连接泄漏。因此,无法进一步向下游解码该响应。如果需要,由所提供的函数声明如何对响应进行解码。

2.4. Request Body

请求正文可以通过Reactive AdapterRegistry处理的任何异步类型进行编码,如Mono或Kotlin CoroutinesDefered,如下例所示:

Java
Kotlin
Mono<Person> personMono = ... ;

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

            

还可以对对象流进行编码,如下面的示例所示:

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

            

或者,如果您有实际值,则可以使用bodyValue快捷方法,如下面的示例所示:

Java
Kotlin
Person person = ... ;

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

            

2.4.1. Form Data

要发送表单数据,可以提供MultiValueMap<;字符串,字符串&>作为正文。注意,<application/x-www-form-urlencoded>FormHttpMessageWriter自动将内容设置为代码。以下示例说明如何使用MultiValueMap<;字符串,字符串>;

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

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

             

还可以使用BodyInserters内联提供表单数据,如下例所示:

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

2.4.2. Multipart Data

若要发送多部分数据,您需要提供MultiValueMap<;字符串?&>,其值可以是表示部件内容的Object实例,也可以是表示部件内容和标头的HttpEntity实例。MultipartBodyBuilder提供了一个方便的API来准备分块请求。以下示例显示如何创建MultiValueMap<;字符串?>;

Java
Kotlin
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();

             

大多数情况下,您不必为每个部分指定Content-Type。内容类型是根据选择序列化它的HttpMessageWriter自动确定的,如果是资源,则根据文件扩展名确定。如果需要,您可以通过重载的构建器部分方法之一显式地提供用于每个部分的mediaType

准备好MultiValueMap后,将其传递给WebClient的最简单方法是通过Body方法,如下面的示例所示:

Java
Kotlin
MultipartBodyBuilder builder = ...;

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

             

如果MultiValueMap包含至少一个非字符串值,该值也可以表示常规表单数据(即application/x-www-form-urlencoded),),则不需要将Content-Type设置为MultiPart/Form-Data。使用MultipartBodyBuilder时总是这样,这确保了HttpEntity包装。

作为MultipartBodyBuilder的替代方案,您还可以通过内置的BodyInserters提供内联样式的多部分内容,如下例所示:

Java
Kotlin
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); 
             
PartEvent

要按顺序传输分块数据,可以通过PartEvent对象提供分块内容。

  • 表单域可以通过FormPartEvent::Create创建。

  • 文件上传可以通过FilePartEvent::Create创建。

您可以通过Flux::Concat拼接方法返回的流,并为WebClient创建请求。

例如,此示例将发布一个包含表单域和文件的多部分表单。

Java
Kotlin
Resource resource = ...
Mono<String> result = webClient
    .post()
    .uri("https://example.com")
    .body(Flux.concat(
            FormPartEvent.create("field", "field value"),
            FilePartEvent.create("file", resource)
    ), PartEvent.class)
    .retrieve()
    .bodyToMono(String.class);

              

在服务器端,通过@RequestBodyServerRequest::bodyToFlux(PartEvent.class)接收到的PartEvent对象可以通过WebClient转发到另一个服务。

2.5. Filters

您可以通过WebClient.Builder注册一个客户端过滤器(ExchangeFilterFunction),以拦截和修改请求,如下例所示:

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

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

            return next.exchange(filtered);
        })
        .build();

            

这可以用于横切关注点,例如身份验证。以下示例通过静态工厂方法使用筛选器进行基本身份验证:

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

可以通过更改现有的WebClient实例来添加或删除筛选器,从而生成一个新的WebClient实例,该实例不会影响原始实例。例如:

Java
Kotlin
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; WebClient client = webClient.mutate() .filters(filterList -> { filterList.add(0, basicAuthentication("user", "password")); }) .build(); 
            

WebClient是一个围绕筛选器链的细小外观,后跟ExchangeFunction。它提供了一个发出请求、对更高级别对象进行编码的工作流,并有助于确保始终使用响应内容。当筛选器以某种方式处理响应时,必须格外注意始终使用其内容,或者以其他方式将其向下传播到将确保相同内容的WebClient。下面是一个筛选器,它处理未经授权状态代码,但确保释放任何响应内容,无论是否预期:

Java
Kotlin
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);
        }
    });
}

            

2.6. Attributes

您可以向请求添加属性。如果您想要通过筛选器链传递信息并影响给定请求的筛选器行为,这是很方便的。例如:

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

    }

            

注意,您可以在WebClient.Builder级别全局配置defaultRequest回调,它允许您将属性插入到所有请求中,例如,可以在Spring MVC应用程序中使用这些属性来基于ThreadLocal数据填充请求属性。

2.7. Context

属性提供了一种将信息传递给筛选器链的便捷方法,但它们只影响当前请求。如果您希望传递传播到其他请求的信息,这些请求是嵌套的,例如通过flatMap,或者在之后执行的,例如通过linatMap,那么您将需要使用反应器上下文

需要在反应链的末尾填充反应器上下文,以便应用于所有操作。例如:

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. Synchronous Use

WebClient可以通过在结果的末尾阻塞的方式同步使用:

Java
Kotlin
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();

            

但是,如果需要进行多个调用,更有效的方法是避免单独阻塞每个响应,而是等待合并结果:

Java
Kotlin
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();

            

以上只是其中一个例子。还有许多其他模式和操作符可以组合成一个反应式管道,使许多远程调用(可能是一些嵌套的、相互依赖的调用)不会阻塞到最后。

使用FluxMono,您永远不需要在Spring MVC或Spring WebFlux控制器中阻塞。只需从控制器方法返回结果反应类型即可。同样的原理适用于Kotlin Coroutines和Spring WebFlux,只需在您的控制器方法中使用挂起函数或返回flow即可。

2.9. Testing

要测试使用WebClient的代码,您可以使用模拟Web服务器,例如OkHttp MockWebServer。要查看其用法示例,请查看Spring框架测试套件中的WebClientIntegrationTests或OkHttp存储库中的静态服务器 示例。

3. HTTP Interface Client

Spring框架允许您将HTTP服务定义为具有HTTP交换方法的Java接口。然后,您可以生成实现此接口并执行交换的代理。这有助于简化HTTP远程访问,并为选择同步或反应式等API风格提供了额外的灵活性。

有关详细信息,请参阅REST Endpoint

4. WebSockets

参考文档的这一部分介绍对反应式堆栈WebSocket消息传递的支持。

=WebSocket简介

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

WebSocket交互以使用HTTPUpgrade头的HTTP请求开始,在本例中,该头用于升级或切换到WebSocket协议。以下示例显示了这样的交互:

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 The Upgrade header.
2 Using the Upgrade connection.

支持WebSocket的服务器返回类似以下内容的输出,而不是通常的200状态代码:

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

成功握手后,作为HTTP升级请求基础的TCP套接字保持打开状态,以便客户端和服务器继续发送和接收消息。

对WebSockets工作原理的完整介绍超出了本文的范围。请参阅RFC 6455,HTML5的WebSocket章节,或Web上许多介绍和教程中的任何一个。

请注意,如果WebSocket服务器在Web服务器(例如nginx)之后运行,您可能需要将其配置为将WebSocket升级请求传递到WebSocket服务器。同样,如果应用程序在云环境中运行,请查看云提供商关于WebSocket支持的说明。

==HTTP与WebSocket

尽管WebSocket被设计为与HTTP兼容并以HTTP请求开始,但了解这两个协议导致非常不同的体系结构和应用程序编程模型是很重要的。

在HTTP和REST中,一个应用程序被建模为多个URL。为了与应用程序交互,客户端以请求-响应的方式访问这些URL。服务器根据HTTP URL、方法和标头将请求路由到适当的处理程序。

相比之下,在WebSockets中,初始连接通常只有一个URL。随后,所有应用程序消息都在同一个TCP连接上流动。这指向了一个完全不同的异步、事件驱动的消息传递体系结构。

WebSocket也是一种低级传输协议,与HTTP不同,它没有为消息内容规定任何语义。这意味着,除非客户端和服务器在消息语义上达成一致,否则无法路由或处理消息。

WebSocket客户端和服务器可以通过HTTP握手请求上的SEC-WebSocket-Protocol头协商使用更高级别的消息传递协议(例如STOMP)。在缺乏这一点的情况下,他们需要拿出自己的惯例。

==何时使用WebSocket

WebSockets可以使网页变得动态和交互。然而,在许多情况下,结合使用AJAX和HTTP流或长轮询可以提供简单而有效的解决方案。

例如,新闻、邮件和社交提要需要动态更新,但每隔几分钟更新一次可能完全没有问题。另一方面,协作、游戏和金融应用程序需要更接近实时。

延迟本身并不是一个决定性因素。如果消息量相对较小(例如,监控网络故障),HTTP流或轮询可以提供有效的解决方案。正是低延迟、高频率和大容量的组合为WebSocket的使用提供了最佳选择。

还要记住,在Internet上,您无法控制的限制性代理可能会阻止WebSocket交互,因为它们没有配置为传递Upgrade头,或者因为它们关闭了看起来空闲的长期连接。这意味着,将WebSocket用于防火墙内的内部应用程序比用于面向公众的应用程序是一个更直接的决定。

4.1. WebSocket API

Spring框架提供了一个WebSocket API,您可以使用它来编写处理WebSocket消息的客户端和服务器端应用程序。

4.1.1. Server

要创建WebSocket服务器,您可以首先创建一个WebSocketHandler。以下示例显示了如何执行此操作:

Java
Kotlin
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) { // ... } } 
             

然后,您可以将其映射到URL:

Java
Kotlin
@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);
    }
}

             

如果使用WebFlux配置,则无需进一步操作,否则如果不使用WebFlux配置,则需要声明一个WebSocketHandlerAdapter,如下所示:

Java
Kotlin
@Configuration
class WebConfig {

    // ...

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

             

4.1.2. WebSocketHandler

WebSocketHandlerHandle方法接受WebSocketSession,并返回Mono;lt;void&>以指示会话的应用程序处理何时完成。会话通过两个流处理,一个用于入站消息,另一个用于出站消息。下表描述了处理流的两种方法:

WebSocketSession method Description

Flux<;WebSocketMessage&>Receive()

提供对入站消息流的访问,并在连接关闭时完成。

单色<;空&>send(Publisher<;WebSocketMessage>;)

获取传出消息的源,编写消息,并返回一个Mono;lt;void>;,在源完成和写入完成时完成。

WebSocketHandler必须将入站和出站流组合成一个统一的流,并返回反映该流完成的Mono<;void&>。根据应用程序要求,统一流程在以下情况下完成:

  • 入站或出站消息流完成。

  • 入站流完成(即连接关闭),而出站流无限。

  • 在选定的点上,通过WebSocketSessionClose方法。

当入站和出站消息流组合在一起时,不需要检查连接是否打开,因为反应流发出结束活动的信号。入站流接收完成或错误信号,出站流接收取消信号。

处理程序的最基本实现是处理入站流的实现。以下示例显示了这样的实施:

Java
Kotlin
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.receive()            (1)
                .doOnNext(message -> {
                    // ... (2)
                })
                .concatMap(message -> {
                    // ... (3)
                })
                .then();                    (4)
    }
}

             
1 Access the stream of inbound messages.
2 Do something with each message.
3 Perform nested asynchronous operations that use the message content.
4 Return a Mono<Void> that completes when receiving completes.
For nested, asynchronous operations, you may need to call message.retain() on underlying servers that use pooled data buffers (for example, Netty). Otherwise, the data buffer may be released before you have had a chance to read the data. For more background, see Data Buffers and Codecs.

以下实施组合了入站和出站流:

Java
Kotlin
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 Handle the inbound message stream.
2 Create the outbound message, producing a combined flow.
3 Return a Mono<Void> that does not complete while we continue to receive.

入站流和出站流可以是独立的,并且只能在完成时加入,如下例所示:

Java
Kotlin
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 Handle inbound message stream.
2 Send outgoing messages.
3 Join the streams and return a Mono<Void> that completes when either stream ends.

4.1.3. DataBuffer

DataBuffer是WebFlux中字节缓冲区的表示形式。参考的Spring Core部分在数据缓冲区和编解码器一节中有更多的内容。要了解的关键点是,在某些服务器(如Netty)上,字节缓冲区是池化和引用计数的,必须在使用时释放,以避免内存泄漏。

在Netty上运行时,如果应用程序希望保留输入数据缓冲区以确保它们不被释放,则必须使用DataBufferUtils.retain(dataBuffer),随后在缓冲区被消耗时使用DataBufferUtils.release(dataBuffer)

4.1.4. Handshake

WebSocketHandlerAdapter委托WebSocketService。默认情况下,它是HandshakeWebSocketService的一个实例,它对WebSocket请求执行基本检查,然后对正在使用的服务器使用RequestUpgradeStrategy。目前,有对反应堆Netty、Tomcat、Jetty和Undertow的内置支持。

HandshakeWebSocketService公开一个会话属性,该属性允许设置谓词字符串以从WebSocketSession中提取属性并将其插入到WebSocketSession的属性中。

4.1.5. Server Configuration

每个服务器的RequestUpgradeStrategy公开特定于底层WebSocket服务器引擎的配置。使用WebFlux Java配置时,您可以自定义WebFlux配置的相应部分中所示的属性,否则,如果不使用WebFlux配置,请使用以下设置:

Java
Kotlin
@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);
    }
}

             

检查您的服务器的升级策略,以了解可用的选项。目前,只有Tomcat和Jetty公开此类选项。

4.1.6. CORS

要配置CORS并限制对WebSocket终结点的访问,最简单的方法是让WebSocketHandler实现CorsConfigurationSource并返回具有允许的来源、标头和其他详细信息的CorsConfiguration。如果无法做到这一点,还可以在SimpleUrlHandler上设置corsConfigurations属性,以按URL模式指定CORS设置。如果两者都指定,则通过使用CorsConfiguration上的Combine方法组合它们。

4.1.7. Client

Spring WebFlux提供了WebSocketClient抽象,并实现了反应器Netty、Tomcat、Jetty、Undertow和标准Java(即JSR-356)。

The Tomcat client is effectively an extension of the standard Java one with some extra functionality in the WebSocketSession handling to take advantage of the Tomcat-specific API to suspend receiving messages for back pressure.

要启动WebSocket会话,可以创建客户端实例并使用其Execute方法:

Java
Kotlin
WebSocketClient client = new ReactorNettyWebSocketClient();

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

             

Jetty等一些客户端实现了生命周期,需要先停止再启动,然后才能使用它们。所有客户端都有与底层WebSocket客户端配置相关的构造函数选项。

5. Testing

Spring-test模块提供了ServerHttpRequestServerHttpResponseServerWebExchange的模拟实现。有关模拟对象的讨论,请参阅Spring Web active

WebTestClient构建在这些模拟请求和响应对象之上,以支持在没有HTTP服务器的情况下测试WebFlux应用程序。您也可以使用WebTestClient进行端到端集成测试。

6. RSocket

本节描述了Spring框架对RSocket协议的支持。

6.1. Overview

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

  • 请求-响应 - 发送一条消息并接收一条消息。

  • RequestStream - 发送一条消息并接收返回的消息流。

  • 频道 - 双向发送消息流。

  • 即发即忘 - 发送单向消息。

一旦建立了初始连接,“客户端”和“服务器”的区别就消失了,因为两端都变得对称,并且每一端都可以发起上述交互之一。这就是为什么在协议中,参与方称为“请求方”和“响应方”,而上述交互称为“请求流”或简称为“请求”。

以下是RSocket协议的主要功能和优点:

  • 跨网络边界的反应流语义 - 对于诸如请求流和通道的流请求,背压信号在请求者和响应者之间传播,允许请求者在源处减慢响应者的速度,从而减少对网络层拥塞控制的依赖,以及对在网络级别或在任何级别上的缓冲的需要。

  • 请求限制 - 此功能以每一端可以发送的租借帧命名为租赁,以限制给定时间内另一端允许的请求总数。租约定期续订。

  • 会话恢复 - 这是为丢失连接而设计的,需要维护一些状态。状态管理对应用程序是透明的,与背压很好地结合在一起工作,在可能的情况下可以停止生产商,并减少所需的状态量。

  • 大型报文的分段和重组。

  • 保持活力(心跳)。

RSocket有多种语言的实现Java库建立在项目反应堆上,反应堆网络用于运输。这意味着来自应用程序中的Reactive Streams Publisher的信号通过RSocket透明地在网络上传播。

6.1.1. The Protocol

RSocket的好处之一是,它在网络上有定义良好的行为,以及易于阅读的规范和一些协议扩展。因此,最好阅读规范,独立于语言实现和更高级别的框架API。本部分提供了一个简洁的概述,以建立一些背景。

正在连接

最初,客户端通过一些低级流传输(如TCP或WebSocket)连接到服务器,并向服务器发送Setup帧以设置连接参数。

服务器可以拒绝设置帧,但通常在发送(对于客户端)和接收(对于服务器)之后,双方都可以开始进行请求,除非设置指示使用租用语义来限制请求的数量,在这种情况下,双方都必须等待来自另一端的租用帧以允许进行请求。

提出请求

一旦建立连接,双方就可以通过RequestResponseRequestStreamRequestChannelRequestFnf帧之一发起请求。这些帧中的每一个都将一条消息从请求方传送到响应方。

然后,响应方可以返回带有响应消息的净荷帧,并且在RequestChannel的情况下,请求方还可以发送具有更多请求消息的净荷帧。

当请求涉及RequestStreamChannel等消息流时,响应者必须尊重请求者的需求信号。需求被表示为多条消息。初始需求在RequestStreamRequestChannel帧中指定。随后的请求通过RequestN帧发送。

每一方还可以通过METADATA_PUSH帧发送元数据通知,其不属于任何单个请求,而是关于整个连接。

消息格式

RSocket消息包含数据和元数据。元数据可用于发送路由、安全令牌等。数据和元数据的格式可以不同。每个的MIME类型都在Setup框架中声明,并应用于给定连接上的所有请求。

虽然所有消息都可以具有元数据,但典型地,诸如路由之类的元数据是按请求的,因此仅包括在请求的第一条消息中,即,具有帧之一的请求_响应、请求_流、请求_频道或请求_FNF。

协议扩展定义了在应用程序中使用的常见元数据格式:

6.1.2. Java Implementation

RSocket的Java实现构建在项目反应堆上。TCP和WebSocket的传输建立在反应器网络上。作为一个反应流库,反应器简化了执行协议的工作。对于应用程序来说,将FluxMono与声明性运算符和透明的背压支持结合使用是非常合适的。

RSocket Java中的API有意采用最小和基本的方式。它将重点放在协议功能上,而将应用程序编程模型(例如,RPC编解码器与其他)作为更高级别的独立关注点。

主契约io.rsocket.RSocketMono表示对单个消息的承诺,Flux表示消息流,以及io.rsocket.Payload对实际消息进行建模,并将对数据和元数据的访问作为字节缓冲区。RSocket约定对称使用。对于请求,应用程序被赋予一个RSocket用于执行请求。对于响应,应用程序实现RSocket来处理请求。

这并不是一个彻底的介绍。在大多数情况下,Spring应用程序将不必直接使用其API。但是,了解或尝试独立于Spring的RSocket可能很重要。RSocket Java存储库包含许多演示其API和协议特性的样例应用程序

6.1.3. Spring Support

Spring-Messaging模块包含以下内容:

Spring-web模块包含RSocket应用程序可能需要的编码器解码器实现,如Jackson CBOR/JSON和Protobuf。它还包含PathPatternParser,可以插入以实现高效的路由匹配。

Spring Boot 2.2支持在TCP或WebSocket上建立RSocket服务器,包括在WebFlux服务器中通过WebSocket公开RSocket的选项。还有对RSocketRequester.BuilderRSocketStrategy的客户端支持和自动配置。有关详细信息,请参阅《Spring Boot参考》中的RSocket部分。

Spring Security5.2提供RSocket支持。

Spring集成5.2提供了入站和出站网关来与RSocket客户端和服务器进行交互。有关详细信息,请参阅《Spring集成参考手册》。

Spring Cloud Gateway支持RSocket连接。

6.2. RSocketRequester

RSocketRequester提供了一个流畅的API来执行RSocket请求,接受和返回数据和元数据的对象,而不是底层的数据缓冲区。它可以对称地用于从客户端发出请求,也可以从服务器发出请求。

6.2.1. Client Requester

若要在客户端获取RSocketRequester,则需要连接到服务器,这涉及到发送带有连接设置的RSocketSetup帧。RSocketRequester提供帮助准备io.rsocket.core.RSocketConnector的生成器,该生成器包括安装框架的连接设置。

这是使用默认设置进行连接的最基本方式:

Java
Kotlin
RSocketRequester requester = RSocketRequester.builder().tcp("localhost", 7000);

URI url = URI.create("https://example.org:8080/rsocket");
RSocketRequester requester = RSocketRequester.builder().webSocket(url);

             

以上内容不会立即连接。当发出请求时,将透明地建立并使用共享连接。

Connection Setup

RSocketRequester.Builder提供以下内容以自定义初始安装框架:

  • dataMimeType(MimeType) - 为连接上的数据设置MIME类型。

  • metadataMimeType(MimeType) - 设置连接上元数据的MIME类型。

  • setupData(Object)要包括在安装程序中的 - 数据

  • setuproute(字符串,对象…​)元数据中要包括在设置中的 - 路径

  • setupMetadata(Object,MimeType) - 要包括在安装程序中的其他元数据

对于数据,默认的MIME类型派生自第一个配置的Decoder。对于元数据,默认的MIME类型是复合元数据,这允许每个请求有多个元数据值和MIME类型对。通常情况下,两者都不需要更改。

设置框架中的数据和元数据是可选的。在服务器端,@Connectmap方法可用于处理连接的开始和Setup框架的内容。元数据可用于连接级安全。

Strategies

RSocketRequester.Builder接受RSocketStrategy配置请求方。您需要使用它来为数据和元数据值的(反)序列化提供编码器和解码器。默认情况下,只为字符串byte[]ByteBuffer注册来自Spring-core的基本编解码器。添加Spring-web提供了对更多内容的访问,可以按如下方式注册:

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

              

RSocketStrategy设计为可重复使用。在某些场景中,例如同一应用程序中的客户端和服务器,最好在Spring配置中声明它。

Client Responders

RSocketRequester.Builder可用于配置来自服务器的请求的响应者。

您可以使用带注释的处理程序进行客户端响应,其基础结构与服务器上使用的基础结构相同,但以编程方式注册,如下所示:

Java
Kotlin
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 Use PathPatternRouteMatcher, if spring-web is present, for efficient route matching.
2 Create a responder from a class with @MessageMapping and/or @ConnectMapping methods.
3 Register the responder.

注意:以上仅是为客户端响应者的程序化注册而设计的快捷方式。对于客户端响应器采用Spring配置的替代场景,您仍然可以将RSocketMessageHandler声明为Spring Bean,然后按如下方式应用:

Java
Kotlin
ApplicationContext context = ... ;
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);

RSocketRequester requester = RSocketRequester.builder()
    .rsocketConnector(connector -> connector.acceptor(handler.responder()))
    .tcp("localhost", 7000);

              

对于上述情况,您可能还需要使用RSocketMessageHandler中的setHandlerPredicate来切换到不同的检测客户端响应器的策略,例如基于@RSocketClientResponder之类的自定义注释与默认的@Controller。在客户端和服务器或同一应用程序中有多个客户端的情况下,这是必需的。

有关编程模型的更多信息,请参阅带注释的响应器

Advanced

RSocketRequester Builder提供回调以公开底层io.rsocket.core.RSocketConnector,以获得有关保持连接间隔、会话恢复、拦截器等的进一步配置选项。您可以在该级别配置选项,如下所示:

Java
Kotlin
RSocketRequester requester = RSocketRequester.builder()
    .rsocketConnector(connector -> {
        // ...
    })
    .tcp("localhost", 7000);

              

6.2.2. Server Requester

从服务器向连接的客户端发出请求是从服务器获取连接的客户端的请求方的问题。

Annotated Responders中,@Connectmap@Messagemap方法支持RSocketRequester参数。使用它来访问连接的请求方。请记住,@Connectmap方法本质上是Setup框架的处理程序,必须在请求开始之前对其进行处理。因此,必须从一开始就将请求与处理分离。例如:

Java
Kotlin
@ConnectMapping
Mono<Void> handle(RSocketRequester requester) {
    requester.route("status").data("5")
        .retrieveFlux(StatusReport.class)
        .subscribe(bar -> { (1)
            // ...
        });
    return ... (2)
}

             
1 Start the request asynchronously, independent from handling.
2 Perform handling and return completion Mono<Void>.

6.2.3. Requests

一旦您有了客户端服务器请求者,您可以按如下方式进行请求:

Java
Kotlin
ViewBox viewBox = ... ;

Flux<AirportLocation> locations = requester.route("locate.radars.within") (1)
        .data(viewBox) (2)
        .retrieveFlux(AirportLocation.class); (3)

             
1 Specify a route to include in the metadata of the request message.
2 Provide data for the request message.
3 Declare the expected response.

交互类型由输入和输出的基数隐式确定。上面的例子是一个请求流,因为发送了一个值并接收了一个值流。在大多数情况下,只要输入和输出的选择与RSocket交互类型和响应者期望的输入和输出类型匹配,您就不需要考虑这一点。无效组合的唯一例子是多对一。

数据(对象)方法还接受任何反应性流发布者,包括FluxMono,以及在Reactive AdapterRegistry中注册的任何其他值生产者。对于生成相同类型值的多值发布者(如Flux),请考虑使用重载的data方法之一,以避免对每个元素进行类型检查和编码器查找:

data(Object producer, Class<?> elementClass);
data(Object producer, ParameterizedTypeReference<?> elementTypeRef);

             

数据(对象)步骤是可选的。跳过不发送数据的请求:

Java
Kotlin
Mono<AirportLocation> location = requester.route("find.radar.EWR"))
    .retrieveMono(AirportLocation.class);

             

如果使用复合元数据(默认设置),并且注册的编码器支持这些值,则可以添加额外的元数据值。例如:

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

             

对于即发即忘,请使用返回Mono<;void>;Send()方法。请注意,Mono仅指示消息已成功发送,而不是已被处理。

对于元数据推送,使用sendMetadata()方法和Mono<;void&>返回值。

6.3. Annotated Responders

RSocket响应器可以实现为@Messagemap@Connectmap方法。@Messagemap方法处理单个请求,而@Connectmap方法处理连接级事件(设置和元数据推送)。对称地支持带注释的响应器,以便从服务器端响应和从客户端响应。

6.3.1. Server Responders

要在服务器端使用带注释的响应器,请将RSocketMessageHandler添加到您的Spring配置中,以使用@Messagemap@Connectmap方法检测@ControllerBean:

Java
Kotlin
@Configuration
static class ServerConfig {

    @Bean
    public RSocketMessageHandler rsocketMessageHandler() {
        RSocketMessageHandler handler = new RSocketMessageHandler();
        handler.routeMatcher(new PathPatternRouteMatcher());
        return handler;
    }
}

             

然后通过Java RSocket API启动RSocket服务器,并为响应方插入RSocketMessageHandler,如下所示:

Java
Kotlin
ApplicationContext context = ... ;
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);

CloseableChannel server =
    RSocketServer.create(handler.responder())
        .bind(TcpServerTransport.create("localhost", 7000))
        .block();

             

RSocketMessageHandler默认支持复合Routing元数据。如果需要切换到不同的MIME类型或注册其他元数据MIME类型,可以设置其MetadataExtractor

您需要设置支持元数据和数据格式所需的编码器解码器实例。您可能需要Spring-web模块来实现编解码器。

默认情况下,SimpleRouteMatcher通过AntPath Matcher匹配路由。我们建议插入Spring-web中的PathPatternRouteMatcher,以实现高效的路由匹配。RSocket路由可以是分层的,但不是URL路径。两个路由匹配器都配置为使用“。默认情况下作为分隔符,并且不像使用HTTPURL那样进行URL解码。

RSocketMessageHandler可以通过RSocketStrategy配置,如果您需要在同一进程中共享客户端和服务器之间的配置,这可能会很有用:

Java
Kotlin
@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();
    }
}

             

6.3.2. Client Responders

客户端的带注释的响应器需要在RSocketRequester.Builder中配置。有关详细信息,请参阅客户端响应程序

6.3.3. @MessageMapping

一旦服务器客户端配置就绪,@Messagemap方法可以如下使用:

Java
Kotlin
@Controller
public class RadarsController {

    @MessageMapping("locate.radars.within")
    public Flux<AirportLocation> radars(MapRequest request) {
        // ...
    }
}

             

上面的@Messagemap方法响应具有路径“locate.radars.in”的请求-流交互。它支持灵活的方法签名,可以选择使用以下方法参数:

Method Argument Description

@Payload

请求的有效负载。这可以是MonoFlux等异步类型的具体值。

注意:使用批注是可选的。不是简单类型且不是任何其他受支持参数的方法参数被假定为预期的有效负载。

RSocketRequester

向远程终端发出请求的请求方。

@DestinationVariable

根据映射模式中的变量从路径中提取的值,例如@MessageMapping(“find.radar.{id}”).

@Header

注册用于提取的元数据值,如MetadataExtractor中所述。

@Headers Map<;字符串、对象&>

注册用于提取的所有元数据值,如MetadataExtractor中所述。

返回值应该是一个或多个要作为响应有效负载序列化的对象。它可以是像MonoFlux这样的异步类型、具体的值,也可以是void或非值异步类型,如mono<;void>;

@Messagemap方法支持的RSocket交互类型由输入(即@PayLoad参数)和输出的基数确定,其中基数的含义如下:

Cardinality Description

1

显式值或单值异步类型,如Mono<;T>;

许多

多值异步类型,如Flux<;T>;

0

对于输入,这意味着该方法没有@PayLoad参数。

对于输出,它是void或非值异步类型,如mono<;void>;

下表显示了所有输入和输出基数组合以及相应的交互类型:

Input Cardinality Output Cardinality Interaction Types

0,1

0

即发即忘、请求响应

0,1

1

请求-响应

0,1

许多

请求流

许多

0、1、多

请求-渠道

6.3.4. @ConnectMapping

@Connectmap在RSocket连接开始时处理Setup帧,并通过METADATA_PUSH帧处理任何后续元数据推送通知,即io.rsocket.RSocket中的metadataPush(Payload)

@Connectmap方法支持与@MessageMapping相同的参数,但基于SetupMETADATA_PUSH帧中的元数据和数据。@Connectmap可以有一个模式来将处理范围缩小到元数据中具有路由的特定连接,或者如果没有声明模式,则所有连接都匹配。

@Connectmap方法不能返回数据,必须使用voidmono<;void>;作为返回值来声明。如果处理为新连接返回错误,则该连接将被拒绝。不能阻止处理向RSocketRequester发出连接请求。有关详细信息,请参阅服务器请求者

6.4. MetadataExtractor

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

MetadataExtractor是一种协定,用于获取序列化的元数据并返回解码的名称-值对,然后可以像按名称的标头一样访问这些名称-值对,例如通过带注释的处理程序方法中的@Header

DefaultMetadataExtractor可以赋予Decoder实例对元数据进行解码。它内置了对“Message/x.rsocket.routing.v0”的支持,并将其解码为字符串并保存在“ROUTE”键下。对于任何其他MIME类型,您需要提供解码器并注册MIME类型,如下所示:

Java
Kotlin
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
extractor.metadataToExtract(fooMimeType, Foo.class, "foo");

            

复合元数据可以很好地组合独立的元数据值。但是,请求者可能不支持复合元数据,也可能选择不使用它。为此,DefaultMetadataExtractor可能需要自定义逻辑来将解码的值映射到输出映射。以下是将JSON用于元数据的示例:

Java
Kotlin
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders);
extractor.metadataToExtract(
    MimeType.valueOf("application/vnd.myapp.metadata+json"),
    new ParameterizedTypeReference<Map<String,String>>() {},
    (jsonMap, outputMap) -> {
        outputMap.putAll(jsonMap);
    });

            

通过RSocketStrategy配置MetadataExtractor时,您可以让RSocketStrategies.Builder使用配置的解码器创建提取程序,只需使用回调来定制注册,如下所示:

Java
Kotlin
RSocketStrategies strategies = RSocketStrategies.builder()
    .metadataExtractorRegistry(registry -> {
        registry.metadataToExtract(fooMimeType, Foo.class, "foo");
        // ...
    })
    .build();

            

6.5. RSocket Interface

Spring框架允许您将RSocket服务定义为带有用于RSocket交换的带注释方法的Java接口。然后,您可以生成实现此接口并执行交换的代理。这有助于通过包装底层RSocketRequester的使用来简化RSocket远程访问。

第一,使用@RSocketExchange方法声明一个接口:

interface RadarService {

    @RSocketExchange("radars")
    Flux<AirportLocation> getRadars(@Payload MapRequest request);

    // more RSocket exchange methods...

}

            

第二,创建将执行声明的RSocket交换的代理:

RSocketRequester requester = ... ;
RSocketServiceProxyFactory factory = RSocketServiceProxyFactory.builder(requester).build();

RepositoryService service = factory.createClient(RadarService.class);

            

6.5.1. Method Parameters

带注释的RSocket交换方法支持使用以下方法参数的灵活方法签名:

Method argument Description

@DestinationVariable

添加一个要传递给RSocketRequester以及@RSocketExchange注释中的路由的路由变量,以便在路由中展开模板占位符。该变量可以是字符串或任何对象,然后通过toString()进行格式化。

@Payload

设置请求的输入有效负载。这可以是具体的值,也可以是可以通过Reactive AdapterRegistry适应反应流发布者的任何值的生产者

对象,如果后跟MimeType

输入有效负载中的元数据条目的值。它可以是任何对象,只要下一个参数是元数据条目MimeType。该值可以是具体的值,也可以是可以通过Reactive AdapterRegistry适应反应式流发布者的单个值的任何生产者。

MimeType

元数据条目的MimeType。前面的方法参数应为元数据值。

6.5.2. Return Values

带注释的RSocket交换方法支持返回值,这些返回值是具体的值,或者是可以通过Reactive AdapterRegistry修改为反应式流发布者的任何值生产者。

7. Reactive Libraries

Spring-webflow依赖于反应堆核心,并在内部使用它来组成异步逻辑并提供反应流支持。通常,WebFlux API返回FluxMono(因为它们是在内部使用的),并宽松地接受任何反应性流发布者实现作为输入。Fluxmono的使用很重要,因为它有助于表达基数 - ,例如,是否需要单个或多个异步值,这对于决策(例如,在编码或解码HTTPS消息时)可能是必不可少的。

对于带注释的控制器,WebFlux透明地适应应用程序选择的反应库。这是在ReactiveAdapterRegistry,的帮助下完成的,它为反应库和其他异步类型提供了可插拔的支持。注册表内置了对RxJava 3、Kotlin协程和SmallRye Mutiny的支持,但您也可以注册其他注册表。

对于函数式API(如FunctionalEndpointWebClient等),WebFlux API的一般规则是使用 - FluxMono作为返回值,使用反应式流发布者作为输入。当提供发布者时,无论是自定义的还是来自另一个反应库的,它都只能被视为具有未知语义(0..N)的流。但是,如果语义已知,您可以用FluxMono.from(Publisher)来包装它,而不是传递原始的发布者

例如,给定一个不是Mono发布者,Jackson JSON消息编写器需要多个值。如果媒体类型暗示无限流(例如,应用程序/json+stream),则分别写入和刷新各个值。否则,值被缓冲到列表中并呈现为JSON数组。