初始Reactor

反应式编程要求我们采取和命令式编程不同的思维方式。反应式编程意味着我们不再描述每一步要进行的步骤,而要构建数据将要流经的管道。当数据流经管道时,可以使用它们,也可以对它们进行某种形式的修改。

例如,假设我们想要接受一个英文人名,然后将所有的字母都转换为大写,用得到的结果创建一个问候消息,最终打印它。使用命令式编程模型,代码看起来如下所示:

String name = "Craig";
String capitalName = name.toUpperCase();
String greeting = "Hello, " + capitalName + "!";
System.out.println(greeting);

使用命令式编程模型,每行代码执行一个步骤,按部就班。各个步骤在同一个线程中执行,并且每一步在自身执行完成之前都会阻止执行线程执行下一步。

与之不同,如下的函数式、反应式代码完成了相同的事情:

Mono.just("Craig")
    .map(n -> n.toUpperCase())
    .map(cn -> "Hello, " + cn + "!")
    .subscribe(System.out::println);

不用过度关心这个例子中的细节,因为我们很快将会详细讨论 just()、map()、和subscribe() 方法。现在,重要的是要理解,虽然这个反应式的例子看起来依然保持着按步骤执行的模型,但实际上数据会流经处理管线。在处理管线的每一步,数据都进行了某种形式的加工,但是我们不能判断数据会在哪个线程上执行这些操作。它们可能在同一个线程,也可能在不同的线程。

这个例子中的Mono是Reactor的两种核心类型之一,另一个类型是Flux。两者都实现了反应式流的Publisher接口。Flux代表具有零个、一个或者多个(可能是无限个)数据项的管道,而Mono是一种特殊的反应式类型,针对数据项不超过一个的场景进行了优化。

Reactor与RxJava(ReactiveX)的对比

你如果熟悉RxJava或者ReactiveX,可能认为Mono和Flux类似于Observable 和Single。事实上它们不仅在语义上大致相同,还共享了很多相同的操作符。

虽然我们在本书中主要介绍Reactor,但是Reactor和RxJava的类型可以互相转换,我相信你会对这一点感到开心。接下来的章节中我们还会看到,Spring 甚至可以使用RxJava的类型。

实际上,在前面的例子中有3个Mono,其中just()操作创建了第一个。当该Mono发送一个值的时候,这个值传递给用于将字母转换为大写的map()操作,据此又创建了另一个Mono。当第二个Mono发布它的数据时,数据传递给第二个map()操作,并且会在此接受一些字符串连接操作,而结果将用于创建第三个Mono。最后,调用第三个Mono上的subscribe()方法时,方法会接收数据并将数据打印出来。

绘制反应式流图

反应式流程通常使用弹珠图(marble diagrams)表示。弹珠图的展现形式非常简单,如图11.1所示,顶部描述了数据流经Flux或者Mono的时间线,在中间描述了要执行的操作,在底部描述了结果形成的Flux或者Mono的时间线。我们将会看到,当数据流经原始的Flux时,一些操作会对其进行处理,并产生一个新的Flux,已经处理的数据将会在新Flux中流动。

image 2024 03 14 10 12 59 312
Figure 1. 图11.1 描绘Flux基本流程的弹珠图

图11.2展示了一个类似的弹珠图,但是针对的是Mono。我们可以看到,这里主要的不同是Mono将会有零个或者一个数据项,或者一个错误。

image 2024 03 14 10 13 47 407
Figure 2. 图11.2 描绘Mono基本流程的弹珠图

在11.3节,我们将会探索Flux和Mono支持的许多操作,还将使用弹珠图来可视化它们的工作原理。

添加Reactor依赖

要开始使用Reactor,我们首先要将下面的依赖项添加到项目的构建文件中:

<dependency>
  <groupId>io.projectreactor</groupId>
  <artifactId>reactor-core</artifactId>
</dependency>

Reactor还提供了非常棒的测试支持。我们将会围绕Reactor代码编写大量的测试,因此需要将下面的依赖添加到构建文件中:

<dependency>
  <groupId>io.projectreactor</groupId>
  <artifactId>reactor-test</artifactId>
  <scope>test</scope>
</dependency>

如果你计划将这些依赖添加到一个Spring Boot工程中,那么Spring Boot工程会替你管理依赖。但是,如果要在非Spring Boot项目中使用Reactor,则需要在构建文件中设置Reactor的物料清单(Bill Of Materials, BOM)。下面的依赖管理条目将会把Reactor的“2020.0.4”版本添加到构建文件中:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-bom</artifactId>
            <version>2020.0.4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

我们将在本章中使用一些独立的样例,与之前的Taco Cloud项目没有关系。因此,最好创建一个在构建文件中包含Reactor依赖的全新的Spring项目,并基于此开始接下来的工作。

现在Reactor已经位于项目的构建文件中,我们可以开始使用Mono和Flux来创建反应式的处理管线了。在本章的剩余部分,我们将介绍Mono和Flux提供的一些操作。