Spring Boot 2.0发布已经过去了2个多月,随着微服务的流行,Spring Boot也越来越受到青睐,更好的隔离编程范式得到了越来越多项目的应用,这是一件值得高兴的事。
Spring Boot 2.0除了一些更新升级特性外,其实最大的一个升级是对反应式编程模型的支持,接下来,我们就来介绍一下反应式编程(Reactive Programming)。在 Java 社区中比较流行的是 RxJava 和 RxJava 2。本文要介绍的是另外一个新的反应式编程库 Reactor。
反应式编程简介
反应式编程最早由 .NET 平台上的 Reactive Extensions (Rx) 库来实现。后来迁移到 Java 平台之后就产生了著名的 RxJava 库,并产生了很多其他编程语言上的对应实现。在这些实现的基础上产生了后来的反应式流(Reactive Streams)规范。该规范定义了反应式流的相关接口,并将集成到 Java 9 中。
而 Reactive Streams 指的是一套规范,对于 Java 开发者来讲,Reactive Streams 具体来说就是一套 API。Reactive Streams 给我们提供了一套通用的 API,让我们可以使用 Java 进行 Reactive Programming。
在响应式编程范式中,实际上,使用的是即常见的发布者-订阅者模式。过去,我们通过拉的方式,也就是传统的迭代器遍历的方式来处理数据流,而在越来越注重用户体验的场景里,拉的方式通常会产生过剩的数据(多余数据),而我们希望让按需订阅,当发布者有新的数据产生时,这些数据会被推送到订阅者来进行处理。
这就引出第一个重要概念——负压(backpressure)。因为常见的发布者-订阅者模式有一个经典问题,就是当发布者数据产生速度过快,订阅者生产书速度跟不上时,会导致系统整体的级联崩溃效应(可能影响其它应用)。而负压的作用在于提供一种反向的,从订阅者到生产者的反馈渠道。订阅者可以通过 request()方法来声明其一次所能处理的消息数量,而生产者就只会产生相应数量的消息,直到下一次 request()方法调用。
Reactor 简介
Reactor是反应式编程范式的一个实现,可以概括为——
反应式编程是一种与数据流和变化传播有关的异步编程范例。 这意味着可以通过所采用的编程语言轻松地表达静态(例如阵列)或动态(例如事件发射器)数据流。
Reactor是JVM的完全非阻塞反应式编程基础类库。 它直接与Java 8功能的API集成,特别是CompletableFuture,Stream和Duration。 它提供了可组合的异步序列API Flux(用于[N]个元素)和Mono(用于[0或1]个元素),广泛实现了Reactive Extensions规范。
Reactor还支持与reactor-ipc组件之间的非阻塞进程间通信(IPC)。 Reactor IPC适用于微服务体系结构,为HTTP(包括Websockets),TCP和UDP提供了支持负压的网络引擎。 几乎能做到无缝的编码和解码。
在 Java 程序中使用 Reactor 库非常的简单,只需要通过 Maven 或 Gradle 来添加对 io.projectreactor:reactor-core 的依赖即可,目前的版本是 3.1.6.RELEASE。
下面要介绍两个重要的基本概念——Flux
和Mono
。
Flux
Flux 表示的是包含 0 到 N 个元素的异步序列。在该序列中可以包含三种不同类型的消息通知:正常的包含元素的消息、序列结束的消息和序列出错的消息。当消息通知产生时,订阅者中对应的方法 onNext(), onComplete()和 onError()会被调用。如下图所示:
Mono
Mono 表示的是包含 0 或者 1 个元素的异步序列。该序列中同样可以包含与 Flux 相同的三种类型的消息通知。Flux 和 Mono 之间可以进行转换。对一个 Flux 序列进行计数操作,得到的结果是一个 Mono对象。把两个 Mono 序列合并在一起,得到的是一个 Flux 对象。
注意,很多人有疑惑,既然有了Flux为什么要多此一举要一个Mono,两者的这种区别,就在于在类型中传递了一些语义信息,表明了异步处理的初步处理。例如,一个HTTP请求只产生一个响应,所以在进行计数操作时没有多少意义。因此,将这种HTTP调用的结果表示为Mono 比将其表示为Flux 更有意义,因为它仅提供与0个或1个项目的上下文相关的运算符。如下图所示:
实战
可能你一时半会儿还没有回过神来,为什么要搞这么麻烦?实际上,异步编程的确是有一点门槛的,不搞清楚来龙去脉,很容易被绕进去,所以,花点时间了解理论模型尤其重要。
创建 Flux
有三种方法可以创建Flux序列,分别是静态方法、generate()方法和create()方法。如下代码:
静态方法:
//可以指定序列中包含的全部元素。创建出来的 Flux 序列在发布这些元素之后会自动结束。
Flux.just("Hello", "World").subscribe(System.out::println);
//fromArray(),fromIterable()和 fromStream():可以从一个数组、Iterable 对象或 Stream 对象中创建 Flux 对象。
Flux.fromArray(new Integer[] {1, 2, 3}).subscribe(System.out::println);
//创建一个不包含任何元素,只发布结束消息的序列。
Flux.empty().subscribe(System.out::println);
//创建包含从 start 起始的 count 个数量的 Integer 对象的序列。
Flux.range(1, 10).subscribe(System.out::println);
Flux.interval(Duration.of(10, ChronoUnit.SECONDS)).subscribe(System.out::println);
Flux.intervalMillis(1000).subscribe(System.out::println);
generate()方法:
Flux.generate(sink -> {
sink.next("Hello");
sink.complete();
}).subscribe(System.out::println);
//generate()方法
final Random random = new Random();
Flux.generate(ArrayList::new, (list, sink) -> {
int value = random.nextInt(100);
list.add(value);
sink.next(value);
if (list.size() == 10) {
sink.complete();
}
return list;
}).subscribe(System.out::println);
create()方法:
//Create()方法
Flux.create(sink -> {
for (int i = 0; i < 10; i++) {
sink.next(i);
}
sink.complete();
}).subscribe(System.out::println);
创建 Mono
Mono 的创建方式与之前介绍的 Flux 比较相似。Mono 类中也包含了一些与 Flux 类中相同的静态方法。这些方法包括 just(),empty(),error()和 never()等。除了这些方法之外,Mono 还有一些独有的静态方法。如下代码:
//创建MONO
Mono.just("test hjf").subscribe(System.out::println);
Mono.empty().subscribe(System.out::println);
Mono.error(new Throwable()).subscribe(System.out::println);
Mono.never().subscribe(System.out::println);
//fromCallable()、fromCompletionStage()、fromFuture()、fromRunnable()和 fromSupplier():分别从 Callable、CompletionStage、CompletableFuture、Runnable 和 Supplier 中创建 Mono。
Mono.fromSupplier(() -> "Hello").subscribe(System.out::println);
//justOrEmpty(Optional<? extends T> data)和 justOrEmpty(T data):从一个 Optional 对象或可能为 null 的对象中创建 Mono。只有 Optional 对象中包含值或对象不为 null 时,Mono 序列才产生对应的元素。
Mono.justOrEmpty(Optional.of("Hello")).subscribe(System.out::println);
//delay(Duration duration)和 delayMillis(long duration):创建一个 Mono 序列,在指定的延迟时间之后,产生数字 0 作为唯一值。
Mono.delay(Duration.ofSeconds(10)).subscribe(System.out::println);
//ignoreElements(Publisher<T> source):创建一个 Mono 序列,忽略作为源的 Publisher 中的所有元素,只产生结束消息。
Mono.ignoreElements(null).subscribe(System.out::println);
//还可以通过 create()方法来使用 MonoSink 来创建 Mono。
Mono.create(sink -> sink.success("Hello")).subscribe(System.out::println);
小结
反应式编程范式对于习惯了传统编程范式的开发人员来说,难点也是重点就在于思维模式的转变,而这恰恰是现在很多熟手程序猿甚至一些高级程序猿很反感的地方。Reactor 作为一个基于反应式流规范的新的 Java 库,可以作为反应式应用的基础。本文对 Reactor 库做了简单的介绍,只演示了 Flux 和 Mono 序列的基本创建。目的是为了先建立一个可以切入的思考点,因为对于一个有追求的程序猿来说,终身学习是基础中的基础。
参考资源
1、Reactor 的官方网站:
2、Reactor 的用户指南:https://projectreactor.io/docs/core/release/reference/
3、InfoQ 上的Reactor例子:https://www.infoq.com/articles/reactor-by-example
4、反应式流规范:http://www.reactive-streams.org/