Spring 5 响应式编程

要点

  • Reactor 是一个运行在 Java8 之上的响应式流框架,它提供了一组响应式风格的 API
  • 除了个别 API 上的区别,它的原理跟 RxJava 很相似
  • 它是第四代响应式框架,支持操作融合,类似 RxJava 2
  • Spring 5 的响应式编程模型主要依赖 Reactor

RxJava 回顾

Reactor 是第四代响应式框架,跟RxJava 2 有些相似。Reactor 项目由Pivotal 启动,以响应式流规范、Java8 和ReactiveX 术语表为基础。它的设计是Reactor 2(上一个主要版本)和RxJava 核心贡献者共同努力的结果。

在之前的同系列文章 RxJava 实例解析 和测试RxJava 里,我们已经了解了响应式编程的基础:数据流的概念、Observable 类和它的各种操作以及通过工厂方法创建静态和动态的Observable 对象。

Observable 是事件的源头,Observer 提供了一组简单的接口,并通过订阅事件源来消费 Observable 的事件。Observable 通过 onNext 向 Observer 通知事件的到达,后面可能会跟上 onError 或 onComplete 来表示事件的结束。

RxJava 提供了 TestSubscriber 来测试 Observable,TestSubscriber 是一个特别的 Observer,可以用它断言流事件。

在这篇文章里,我们将会对 ReactorRxJava 进行比较,包括它们的相同点和不同点。

Reactor 的类型

Reactor 有两种类型, Flux Mono

  • Flux 类似 RaxJava 的 Observable,它可以触发零到多个事件,并根据实际情况结束处理或触发错误。
  • Mono 最多只触发一个事件,它跟 RxJava 的 Single 和 Maybe 类似,所以可以把 Mono 用于在异步任务完成时发出通知。

因为这两种类型之间的简单区别,我们可以很容易地区分响应式 API 的类型:从返回的类型我们就可以知道一个方法会发射并忘记请求并等待(Mono),还是在处理一个包含多个数据项的流(Flux)。

FluxMono 的一些操作利用了这个特点在这两种类型间互相转换。例如,调用 Flux 的 single() 方法将返回一个 Mono ,而使用 concatWith() 方法把两个 Mono 串在一起就可以得到一个 Flux。类似地,有些操作对 Mono 来说毫无意义(例如 take(n) 会得到 n>1 的结果),而有些操作只有作用在 Mono 上才有意义(例如 or(otherMono))。

Reactor 设计的原则之一是要保持 API 的精简,而对这两种响应式类型的分离,是表现力与 API 易用性之间的折中。

使用响应式流,基于 Rx 构建

正如RxJava 实例解析里所说的,从设计概念方面来看,RxJava 有点类似 Java 8 Streams API。而 Reactor 看起来有点像 RxJava,不过这决不只是个巧合。这样的设计是为了能够给复杂的异步逻辑提供一套原生的具有 Rx 操作风格的响应式流 API。所以说 Reactor 扎根于响应式流,同时在 API 方面尽可能地与 RxJava 靠拢。

响应式类库和响应式流的使用

Reactive Streams (以下简称为 RS)是一种规范,它为基于非阻塞回压的异步流处理提供了标准。它是一组包含了 TCK 工具套件和四个简单接口(Publisher、Subscriber、Subscription 和 Processor)的规范,这些接口将被集成到 Java 9.

RS 主要跟响应式回压(稍后会详细介绍)以及多个响应式事件源之间的交互操作有关。它并不提供任何操作方法,它只关注流的生命周期。

Reactor 不同于其它框架的最关键一点就是 RS。Flux 和 Mono 这两者都是 RS 的 Publisher 实现,它们都具备了响应式回压的特点。

RxJava 1 里,只有少部分操作支持回压,RxJava 1 的 Observable 并没有实现 RS 里的任何类型,不过它有一些 RS 类型的适配器。可以说,RxJava 1 实际上比 RS 规范出现得更早,而且在 RS 规范设计期间,RxJava 1 充当了函数式工作者的角色。

所以,你在使用那些 Publisher 适配器时,它们并不会为你提供任何操作。为了能做一些有用的操作,你可能需要用回 Observable,而这个时候你需要另一个适配器。这种视觉上的混乱会破坏代码的可读性,特别是像 Spring 5 这样的框架,如果整个框架建立在这样的 Publisher 之上,那么就更是杂乱不堪。

RS 规范不支持 null 值,所以在从 RxJava 1 迁移到 Reactor 或 RxJava 2 时要注意这点。如果你在代码里把 null 用作特殊用途,那么就更是要注意了。

RxJava 2 是在 RS 规范之后出现的,所以它直接在 Flowable 类型里实现了 Publisher。不过除了 RS 类型,RxJava 2 还保留了 RxJava 1 的遗留类型(Observable、Completable 和 Single)并且引入了其它一些可选类型——Maybe。这些类型提供了不同的语义,不过它们并没有实现 RS 接口,这是它们的不足之处。跟 RxJava 1 不一样,RxJava 2 的 Observable 不支持 RxJava 2 的回压协议(只有 Flowable 具备这个特性)。之所以这样设计是为了能够为一些场景提供一组丰富且流畅的 API,比如用户界面发出的事件,在这样的场景里是不需要用到回压的,而且也不可能用到。Completable、Single 和 Maybe 不需要支持回压,不过它们也提供了一组丰富的 API,而且在被订阅之前不会做任何事情。

在响应式领域,Reactor 变得愈加精益,它的 Mono 和 Flux 两种类型都实现了 Publisher,并且都支持回压。虽然把 Mono 作为一个 Publisher 需要付出一些额外的开销,不过 Mono 在其它方面的优势弥补了它的缺点。在后续部分我们将看到对 Mono 来说回压意味着什么。

相比 RxJava,API 相似但不相同

ReactiveX 和 RxJava 的操作术语表有时候真的难以掌握,因为历史原因,有些操作的名字让人感到困惑。Reactor 尽量把 API 设计得紧凑,在给 API 取名时尽量选择好一点的名字,不过总的来说,这两套 API 看起来还是很相像。在最新的 RxJava 2 迭代版本中,RxJava 2 借鉴了 Reactor 的一些术语,这预示着这两个项目之间可能会有越来越紧密的合作。一些操作和概念总是先出现在其中的一个项目里,然后互相借鉴,最后会同时渗透到两个项目里。

例如,Flux 也有常见的 just 工厂方法(虽然只有两种变形:接受一个参数或变长参数)。不过 from 方法有很多个变种,最值得一提的是 fromIterable。当然,Flux 也包含了那些常规的操作:map、merge、concat、flatMap、take,等等。

Reactor 把 RxJava 里令人困惑的 amb 操作改成了看起来更加中肯的 firstEmitting。另外,为了保持 API 的一致,toList 被重新命名为 collectList。实际上,所有以 collect 开头的操作都会把值聚合到一个特定类型的集合里,不过只会为每个集合生成一个 Mono。而所有以 to 开头的操作被保留用于类型转换,转换之后的类型可以用于非响应式编程,例如 toFuture()。

在类初始化和资源使用方面,Reactor 之所以也能表现得如此精益,要得益于它的融合特性:Reactor 可以把多个串行的操作(例如调用 concatWith 两次)合并成单个操作,这样就可以只对这个操作的内部类做一次初始化(也就是 macro-fusion)。这个特性包含了基于数据源的优化,抵消了 Mono 在实现 Publisher 时的一些额外开销。它还能在多个相关的操作之间共享资源(也就是 micro-fusion),比如内部队列。这些特性让 Reactor 成为不折不扣的的第四代响应式框架,不过这个超出了这篇文章的讨论范围。

下面让我们来看看几个 Reactor 的操作。

一些操作示例

(这一小节包含了一些代码片段,我们建议你动手去运行它们,深入体验一下 Reactor。所以你需要打开 IDE,并创建一个测试项目,把 Reactor

你可能感兴趣的:(技术文章,Spring,Java,git,git,rebase)