学习响应式编程 Reactor (3) - reactor 基础
Reactor
Reactor 项目的主要 artifact 是 reactor-core,这是一个基于 Java 8 的实现了响应式流规范的响应式库。
Reactor 提供了实现 Publisher 的响应式类 Flux 和 Mono,以及丰富的操作符。一个 Flux 代表 0...N 个元素的响应式流;一个 Mono 代表 0|1 个元素的响应式流。
Flux 和 Mono 之间可以转换,比如 Flux 的 count 操作(计算流中元素个数)返回 Mono,Mono 的 concatWith 操作(连接另一个响应式流)返回 Flux。
Flux
Flux<T> 是一个能够发出 0 到 N 个元素的标准 Publisher<T>,它会被一个完成(completion)或错误(error)信号终止。因此,一个 Flux 的可能结果是 value、completion
或 value,这三个分别会传递给订阅者中的 onNext、onComplete、onError 方法。
注意:所有的信号事件,包括代表终止的信号事件都是可选的。如果没有 onNext 事件,但是有 onComplete 事件,那么发出的就是空的有限流;如果去掉 onComplete
就得到一个 无限的空数据流。无限的数据流可以不是空的,比如 Flux.interval(Duration) 生成的是一个 Flux,这是一个无限周期性发出规律整数的时钟数据流。
下图展示的是 Flux 基于时间线的弹珠交互图,通过操作符转换 Flux 中元素:

- 上面那条线表示的是 Flux 数据流时间线,时间从左至右
- 上面那条线中的弹珠代表示的是 Flux 发出的 数据元素
- 上面那条线最后的垂直线表示的是 Flux 已经完成成功事件
- 中间的箭头虚线和框表示的是 Flux 中的元素正在被转换,框内的文字表示的是转换的方式(包含操作符)
- 下面那条线表示的是 FLux 经过转换后的新数据流
- 如果由于某种原因导致 Flux 的转换终止,将使用 X 来代替 垂直线
后续在学习操作符的过程中,我们将见到很多类似的弹珠图,请大家详细了解清楚该图各部分的含义。
Mono
Mono<T> 是一种特殊的Publisher<T>,它最多只能发出一个元素,然后(可选的)终止于 onComplete 或 onError 信号。
Mono 中的操作符是 Flux 中操作符的子集,即 Flux 中只有部分操作符适用于 Mono,有些操作符是将 Mono 和另一个 Publisher 连接转换为 Flux。例如,Mono#concatWith(Publisher
) 转换为 Flux,Mono#then(Mono) 返回另一个 Mono。
注意:可以使用 Mono 来创建一个只有完成概念的空值异步处理过程(类似于 Runnable)。
下图展示的是 Mono 基于时间线的弹珠交互图:

创建 Flux 和 Mono
如同创建 Java Stream 一样,Reactor 也为我们提供了 多个工厂方法用来创建 Flux 和 Mono,有了 Stream 的基础,创建的基本方法我们来快速过一下。
下面的创建方法,如果是 Flux 或 Mono 独有的,会在方法名前增加类名前缀。
下面的示例代码中都有用到 subscribe 方法,下面会讲到,大家先了解它是响应式流的订阅方法,用于触发流,类似于 Java Stream 中的终端操作。
just
使用提供的元素发出数据然后结束的流。

Mono.just("hello, world").subscribe(System.out::println);
Mono.justOrEmpty(str).subscribe(System.out::println);
Mono.justOrEmpty(optional).subscribe(System.out::println);
Flux.just("hello", "world").subscribe(System.out::println);
Flux.just("hello").subscribe(System.out::println);
Flux#fromXxx
Flux 提供了 fromArray(从数组)、fromIterable(从迭代器)、fromStream(从 Java Stream 流) 的方式来创建 Flux。
String[] array = new String[]{"hello", "reactor", "flux"};
List<String> iterable = Arrays.asList("foo", "bar", "foobar");
Flux.fromArray(array).subscribe(System.out::println);
Flux.fromIterable(iterable).subscribe(System.out::println);
Flux.fromStream(Arrays.stream(array)).subscribe(System.out::println);
Flux#range
从 start 开始构建一个 Flux,该 Flux 仅发出一系列递增计数的整数。 也就是说,在 start(包括)和 start + count(排除)之间发出整数,然后完成。见图识意:

Flux.range(3, 5).subscribe(System.out::println);
Flux#interval
在全局计时器上创建一个 Flux,该 Flux 在初始延迟后,发出从0开始并以指定的时间间隔递增的长整数。 如果未及时产生,则会通过溢出 IllegalStateException 发出 onError
信号,详细说明无法发出的原因。 在正常情况下,Flux 将永远不会完成。interval 提供了 3 个重载方法,三者的区别主要在于是否延迟发出、以及使用的调度器。
interval 生成的是一个无限数据流。
Flux<Long> interval(Duration period)
Flux<Long> interval(Duration delay, Duration period)
Flux<Long> interval(Duration delay, Duration period, Scheduler timer)
- 第 1 个方法,没有延迟,按照 period 的周期立即发出,默认使用 Schedulers.parallel() 调度器
- 第 2 个方法,以 delay 延迟,按照 period 的周期发出,默认使用 Schedulers.parallel() 调度器
- 第 3 个方法,以 delay 延迟,按照 period 的周期发出,使用指定的调度器
见图识意:

Flux.interval(Duration.ofMillis(30), Duration.ofMillis(500)).subscribe(System.out::println);
empty
生成一个空的有限流。见图识意:

Flux.empty().subscribe(System.out::println, System.out::println, () -> System.out.println("结束"));
never
生成一个空的无限流。见图识意:

Flux.never().subscribe(System.out::println, System.out::println, () -> System.out.println("结束"));
error
生成一个错误流。error 有 3 个重载方法,它们的主要区别是否立即生成错误及是否由 Supplier 提供,见图识意:

Flux.error(new IllegalStateException(), true)
.log()
.subscribe(System.out::println, System.err::println);
其它
Flux 和 Mono 还提供了编程式的创建数据流的方法,诸如 create、generate、push、handle 等的方式,这些内容暂时不是我们的重点,这里我们不细展开,感兴趣的可看 Api 进行研究下。

订阅 Flux 和 Mono
在上面创建 Flux 和 Mono 笔记的示例代码中,我们已经提到了 subscribe 订阅,在 subscribe 订阅中,Flux 和 Mono 支持 Java 8 Lambda 表达式。下面我们来看看 Reactor
为我们提供了哪些订阅方法。
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); // ⑤
subscribe(Subscriber<? super T> actual); // ⑥
- 序号① 订阅并触发响应式流。
- 序号② 对每个生成的元素进行消费。
- 序号③ 对正常元素进行消费,对错误进行响应处理。
- 序号④ 对正常元素和错误均有响应,还定义了响应流正常完成后的回调。
- 序号⑤ 对正常元素、错误信号和完成信号均有响应,同时也定义了 对该 subscribe 返回的 Subscription 的回调处理。
- 序号⑥ 通过自定义实现 Subscriber 接口来订阅。
注意:序号⑤ 变量传递一个 Subscription 的引用,如果不再需要更多元素时,可以通过它来取消订阅。取消订阅时,源头会停止生成数据,并清理相关的资源。取消和清理的操作是在 Disposable 接口中定义的。
来看下序号 ⑤ 的 subscribe 的弹珠图:

Flux.range(1, 4)
.subscribe(System.out::println,
error -> System.err.println("发生错误:" + error),
() -> System.out.println("完成"),
sub -> {
System.out.println("已订阅");
// 理解背压
// 尝试修改下 request 中的值,看看有啥变化
sub.request(10);
});
注意:序号⑥ 的方式支持背压等操作,不在我们本次笔记的范畴,我们还是先略过,后期在学习。
补充
在上节我们讲解 Reactor 调试部分时,遗漏了记录数据流的日志方法,再此做下补充:除了基于 stack trace 的方式调试分析,我们还可以使用 log
操作符,来跟踪响应式流并记录日志。将它添加到操作链上之后,它会读取每一个再其上游的 Flux 和 Mono 事件(包括 onNext、onError、onComplete、Subscribe、Cancel 和 Request)。
// 尝试交换下 take 和 log 的顺序,看看有啥变化
Flux.range(1, 10)
// .log()
.take(3)
.log()
.subscribe();
总结
本篇我们介绍了 Reactor 的基础知识:先是了解了 Reactor 为我们提供的响应式流类 Flux 和 Mono,之后学习了如何创建他们和订阅他们,因为有之前 Stream
的基础,想来大家对这些知识点都好理解和接受。
今天的内容就学到这里,我们下篇开始学习 Reactor 的操作符。
源码详见:https://github.com/crystalxmumu/spring-web-flux-study-note 下 02-reactor-core-learning
模块下 ReactorBasicLearningTest 测试类。
参考
学习响应式编程 Reactor (3) - reactor 基础的更多相关文章
- 学习响应式编程 Reactor (1) - 响应式编程
响应式编程 命令式编程(Imperative Programing),是一种描述计算机所需做出的行为的编程范式.详细的命令机器怎么(How)去处理以达到想要的结果(What). 声明式编程(Decla ...
- 学习响应式编程 Reactor (2) - 初识 reactor
Reactor Reactor 是用于 Java 的异步非阻塞响应式编程框架,同时具备背压控制的能力.它与 Java 8 函数式 Api 直接集成,比如 分为CompletableFuture.Str ...
- 学习响应式编程 Reactor (4) - reactor 转换类操作符(1)
Reactor 操作符 数据在响应式流中的处理,就像流过一条装配流水线.Reactor 既是传送带,又是一个个的装配工或机器人.原材料从源头(最初的 Publisher )流出,经过一个个的装配线中装 ...
- 学习响应式编程 Reactor (5) - reactor 转换类操作符(2)
Reactor 操作符 上篇文章我们将 Flux 和 Mono 的操作符分了 11 类,我们来继续学习转换类操作符的第 2 篇. 转换类操作符 转换类的操作符数量最多,平常过程中也是使用最频繁的. F ...
- 响应式编程简介之:Reactor
目录 简介 Reactor简介 reactive programming的发展史 Iterable-Iterator 和Publisher-Subscriber的区别 为什么要使用异步reactive ...
- 响应式编程系列(一):什么是响应式编程?reactor入门
响应式编程 系列文章目录 (一)什么是响应式编程?reactor入门 (二)Flux入门学习:流的概念,特性和基本操作 (三)Flux深入学习:流的高级特性和进阶用法 (四)reactor-core响 ...
- Project Reactor 响应式编程
目录 一. 什么是响应式编程? 二. Project Reactor介绍 三. Reactor核心概念 Flux 1. just() 2. fromArray(),fromIterable()和 fr ...
- SpringBoot 2.x (14):WebFlux响应式编程
响应式编程生活案例: 传统形式: 一群人去餐厅吃饭,顾客1找服务员点餐,服务员把订单交给后台厨师,然后服务员等待, 当后台厨师做好饭,交给服务员,经过服务员再交给顾客1,依此类推,该服务员再招待顾客2 ...
- 响应式编程(Reactive Programming)(Rx)介绍
很明显你是有兴趣学习这种被称作响应式编程的新技术才来看这篇文章的. 学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下.刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它 ...
随机推荐
- Java发送邮件报错:com.sun.mail.util.LineOutputStream.<init>(Ljava/io/OutputStream;Z)V
在练习使用Java程序发送邮件的代码 运行出现了com.sun.mail.util.LineOutputStream.<init>(Ljava/io/OutputStream;Z)V报错信 ...
- 关于window匿名通道的使用以及所遇到的问题
前言 学习windows通道时,用他去完成自己的cmd小工具时遇到了一些问题总结一下. ① 关于STARTUPINFO结构:因为为了在cmd程序中通过通道与我们的程序交互,我们需要把cmd的输入输出变 ...
- 拷贝构造函数第一个参数最好使用const
拷贝构造函数的第一个参数要求是自身类型的引用,但是没有一定要求具有底层const属性即对常量的引用,但是使用时最好加上const,原因是我们可能在某些"不知道"的情况下对常量对象调 ...
- 大数据开发-Flink-1.13新特性
介绍 大概4月,Flink1.13就发布了,参加 了Flink1.13 的Meetup,收获还是挺多,从大的方面讲就是FlingSql的改进和优化,资源调度管理方面的优化,以及流批一体Flink在运行 ...
- Envoy:离群点检测 outlier detection
outlier detection 在异常检测领域中,常常需要决定新观察的点是否属于与现有观察点相同的分布(则它称为inlier),或者被认为是不同的(称为outlier).离群是异常的数据,但是不一 ...
- 【转载】一次「Too many open files」故障
一次「Too many open files」故障 发表于2015-08-02 昨天,项目的 ElasticSearch 服务挂了,我说的挂可不是进程没了,因为有 Supervisor 保护,而是服务 ...
- 《SystemVerilog验证-测试平台编写指南》学习 - 第3章 过程语句和子程序
<SystemVerilog验证-测试平台编写指南>学习 - 第3章 过程语句和子程序 3.1 过程语句 3.2 任务.函数以及void函数 3.3 任务和函数概述 3.4 子程序参数 3 ...
- BUUCTF(十)[GXYCTF2019]Ping Ping Ping 1
BUUCTF系列 /?ip=baidu.com /?ip=baidu.com|ls 正常回显,当cat flag.php时,提示不让输入空格,而且后面还不让出现falg字符 IFS IFS (Inte ...
- 013.Ansible Playbook include
一 include 当项目越大,tasks越多的时候.如果将多有的task写入一个playbook中,可读性很差,就需要重新组织playbook 可以把一个playbook分成若干份晓得palyboo ...
- ssh判断免密登陆
ssh判断免密登陆 [root@jenkins ~]# vi /opt/release_code.sh #!/bin/bash . /etc/init.d/functions #echo $WORKS ...