本系列其他文章见:《响应式Spring的道法术器》。
前情提要:响应式流 | lambda与函数式
1.3.2 Reactor
Reactor与Spring是兄弟项目,侧重于Server端的响应式编程,主要 artifact 是 reactor-core,这是一个基于 Java 8 的实现了响应式流规范 (Reactive Streams specification)的响应式库。
本文对Reactor的介绍以基本的概念和简单的使用为主,深度以能够满足基本的Spring WebFlux使用为准。在下一章,我会结合Reactor的设计模式、并发调度模型等原理层面的内容系统介绍Reactor的使用。
本文源码。
光说不练假把式,我们先把练习用的项目搭起来。先创建一个maven项目,然后添加依赖:
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.1.4.RELEASE</version>
</dependency>
最新版本可到http://search.maven.org查询,复制过来即可。另外出于测试的需要,添加如下依赖:
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.1.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
好了,我们开始Coding吧。
1.3.2.1 Flux与Mono
Reactor中的发布者(Publisher)由Flux
和Mono
两个类定义,它们都提供了丰富的操作符(operator)。一个Flux对象代表一个包含0…N个元素的响应式序列,而一个Mono对象代表一个包含零/一个(0…1)元素的结果。
既然是“数据流”的发布者,Flux和Mono都可以发出三种“数据信号”:元素值、错误信号、完成信号,错误信号和完成信号都是终止信号,完成信号用于告知下游订阅者该数据流正常结束,错误信号终止数据流的同时将错误传递给下游订阅者。
下图所示就是一个Flux类型的数据流,黑色箭头是时间轴。它连续发出“1” - “6”共6个元素值,以及一个完成信号(图中⑥后边的加粗竖线来表示),完成信号告知订阅者数据流已经结束。
下图所示是一个Mono类型的数据流,它发出一个元素值后,又发出一个完成信号。
既然Flux具有发布一个数据元素的能力,为什么还要专门定义一个Mono类呢?举个例子,一个HTTP请求产生一个响应,所以对其进行“count”操作是没有多大意义的。表示这样一个结果的话,应该用
Mono<HttpResponse>
而不是Flux<HttpResponse>
,对于的操作通常只用于处理 0/1 个元素。它们从语义上就原生包含着元素个数的信息,从而避免了对Mono对象进行多元素场景下的处理。
有些操作可以改变基数,从而需要切换类型。比如,count操作用于Flux,但是操作返回的结果是
Mono<Long>
。
我们可以用如下代码声明上边两幅图所示的Flux和Mono:
Flux.just(1, 2, 3, 4, 5, 6);
Mono.just(1);
Flux和Mono提供了多种创建数据流的方法,just
就是一种比较直接的声明数据流的方式,其参数就是数据元素。
对于图中的Flux,还可以通过如下方式声明(分别基于数组、集合和Stream生成):
Integer[] array = new Integer[]{1,2,3,4,5,6};
Flux.fromArray(array);
List<Integer> list = Arrays.asList(array);
Flux.fromIterable(list);
Stream<Integer> stream = list.stream();
Flux.fromStream(stream);
不过,这三种信号都不是一定要具备的:
- 首先,错误信号和完成信号都是终止信号,二者不可能同时共存;
- 如果没有发出任何一个元素值,而是直接发出完成/错误信号,表示这是一个空数据流;
- 如果没有错误信号和完成信号,那么就是一个无限数据流。
比如,对于只有完成/错误信号的数据流:
// 只有完成信号的空数据流
Flux.just();
Flux.empty();
Mono.empty();
Mono.justOrEmpty(Optional.empty());
// 只有错误信号的数据流
Flux.error(new Exception("some error"));
Mono.error(new Exception("some error"));
你可能会纳闷,空的数据流有什么用?举个例子,当我们从响应式的DB中获取结果的时候(假设DAO层是ReactiveRepository<User>
),就有可能为空:
Mono<User> findById(long id);
Flux<User> findAll();
无论是空还是发生异常,都需要通过完成/错误信号告知订阅者,已经查询完毕,但是抱歉没有得到值,礼貌问题嘛~
1.3.2.2 订阅前什么都不会发生
数据流有了,假设我们想把每个数据元素原封不动地打印出来:
Flux.just(1, 2, 3, 4, 5, 6).subscribe(System.out::print);
System.out.println();
Mono.just(1).subscribe(System.out::println);
输出如下:
123456
1
可见,subscribe
方法中的lambda表达式作用在了每一个数据元素上。此外,Flux和Mono还提供了多个subscribe
方法的变体:
// 订阅并触发数据流
subscribe();
// 订阅并指定对正常数据元素如何处理
subscribe(Consumer<? super T> consumer);
// 订阅并定义对正常数据元素和错误信号的处理
subscribe(Consumer<? super T> consumer,
Consumer<? super Throwable> errorConsumer);
// 订阅并定义对正常数据元素、错误信号和完成信号的处理
subscribe(Consumer<? super T> consumer,
Consumer<? super Throwable> errorConsumer,
Runnable completeConsumer);
// 订阅并定义对正常数据元素、错误信号和完成信号的处理,以及订阅发生时的处理逻辑
subscribe(Consumer<? super T> consumer,
Consumer<? super Throwable> errorConsumer,
Runnable completeConsumer,
Consumer<? super Subscription> subscriptionConsumer);
1)如果是订阅上边声明的Flux:
Flux.just(1, 2, 3, 4, 5, 6).subscribe(
System.out::println,
System.err::println,
() -> System.out.println("Completed!"));
输出如下:
1
2
3
4
5
6
Completed!
2)再举一个有错误信号的例子:
Mono.error(new Exception("some error")).subscribe(
System.out::println,
System.err::println,
() -> System.out.println("Completed!")
);
输出如下ÿ