一、前言
Spring5是基于Reactor框架实现响应式流,其中Spring Boot WebFlux是完全依赖reactor-core来实现;直接上手Spring WebFlux,虽然能简单使用,但对其原理未做深入了解,难免会有不少迷惑,故建议从基础出发,按照如下步骤进行学习:
1、掌握JDK8的函数式编程及stream流;
2、学习JDK9的flux的响应式流设计原理及实现机制
3、上手spring Boot webflux(JDK8函数式编程及stream流 + JDK9响应式流 的结合)
二、JDK8中的函数式编程及stream流
jdk8中最大的特点之一就是支持函数式编程及stream流,下面就其中重点做下基本的简介和示例:
(一)lamda表达式
Lambda 表达式,也可称为闭包,本质上来说其允许把函数作为一个方法的参数。
public class LambdaDemo1 {
public static void main(String[] args) {
// 非lambda表达式代码示例
Runnable thread1 = new Runnable() {
@Override
public void run() {
System.out.println("普通线程");
}
};
new Thread(target).start();
// jdk8 lambda表达式代码示例
//返回一个接口,可以使用强转的方式(不过我觉得应该没人这么用)
Runnable thread2 = (Runnable)() -> System.out.println("ok");
new Thread(thread2).start();
}
}
通过上面示例,可看出lambda表单式的很大的一个特点就是把复杂的代码逻辑,变得更为简洁和紧凑。
另外,lambda表达式还有其他的如下写法:
interface InterfaceDemo1 {
int doubleNum(int i);
int addNum(int i1, int i2);
}
public class LambdaDemo1 {
public static void main(String[] args) {
// 函数逻辑多行时,需要加大括号
InterfaceDemo1 i1 = (int i) -> {
System.out.println("----------");
return i * 2;
};
// 函数逻辑仅为一行时,可省去大括号和return
InterfaceDemo1 i2 = (int i) -> i * 2;
// 参数可省去类型
InterfaceDemo1 i3 = (i) -> i * 2;
// 单个参数时,可以省去括号
InterfaceDemo1 i4 = i -> i * 2;
// 多个参数时,需要加括号
InterfaceDemo1 i5 = (i1, i2) -> i1 + i2;
}
}
(二)函数式接口
函数式接口:只有一个方法的interface接口,一般通过添加注解 @FunctionalInterface来标注(不添加注解也可以,但建议加上注解以供编译时检验,加上注解后如果有多个方法会报错)
默认接口: interface接口中使用default关键字标注的实现方法;该方法可以不被实现,也可以被重写,和类中的方法一样,可以在方法使用this调用接口中的方法。
@FunctionalInterface
interface InterfaceDemo2 {
// jdk8之后建议接口设计尽量的小,一个接口只做一件事,也就是更加便于函数式接口的使用
int doubleNum(int i);
// 默认方法
default int add(int x, int y) {
return x + y;
}
}
public class LambdaDemo2 {
public static void main(String[] args) {
InterfaceDemo2 i1 = i -> i * 2;
// 调用接口默认方法
System.out.println(i1.add(3, 7));
System.out.println(i1.doubleNum(20));
}
}
(三)内置函数接口
1、四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 方法 | 说明 |
---|---|---|---|---|
Consumer | T | void | void accept(T t) | 无返回值,对指定T类型参数进行处理 |
Supplier | 无 | T | T get() | 无入参,通过处理返回指定T类型对象 |
Function<T,R> | T | R | R apply(T) | 对指定T类型参数进行处理,通过处理返回指定R类型对象 |
Predicate | T | boolean | boolean test(T) | 对指定T类型参数进行处理,判断是否满足某种条件 |
public class FunctionDemo1 {
public static void main(String[] args) {
// Consumer<T>
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("1");
// Supplier<T>
Supplier<String> supplier = () -> "2";
System.out.println(supplier.get());
// Function<T,R>
Function<Integer, Integer> function = i -> i * 2;
System.out.println(function.apply(3));
// Predicate<T>
Predicate<Integer> predicate = i -> i > 0;
System.out.println(predicate.test(-4));
}
}
2、 其他演变函数式接口
函数式接口 | 参数类型 | 返回类型 | 方法 | 说明 |
---|---|---|---|---|
BiFunction<T,U,R> | T,U | R | R apply(T t, U u) | 对指定T类型和U类型参数进行处理,通过处理返回指定R类型对象 |
UnaryOperator | T | T | T apply(T t) | 对指定T类型参数进行处理,通过处理返回相同T类型对象, 其是Function<T,R>子接口 |
BinaryOperator | T,T | T | T apply(T t, T t) | 对两个指定的T类型参数进行处理,通过处理返回相同T类型对象, 其是BiFunction<T,U,R>子接口 |
BiConsumer<T,U> | T,U | void | void accept(T t,U u) | 无返回值,对指定T类型和U类型参数进行处理 |
(四)stream流
Stream流是一个来自数据源的元素队列并支持聚合操作,其有如下三个要素:
- 元素:特定类型的对象,基于该类型对象形成一个队列,且Stream流并不存储元素,而是仅按需进行操作;
- 数据源: Stream流的数据来源,可以是集合,数组,I/O channel, 产生器generator 等;
- 聚合操作:对元素做的简便操作,比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining:Stream流并不存储元素,而是仅按需进行操作,且中间操作都会返回流对象本身;基于此多个操作可串联起来,形成一个类似管道一样的操作序列;
- 内部迭代: Stream流通过访问者模式(Visitor)实现了内部迭代 。
1、 流的创建
来源 | 方法 |
---|---|
集合 | Collection.stream/parallelStream |
数组 | Arrays.stream |
数字Stream | IntStream/LongStream.range/rangeClosed Random.ins/longs/doubles |
直接创建 | Stream.generate/interate |
public class StreamDemo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 集合创建
list.stream();
list.parallelStream();
// 数组创建
Arrays.stream(new int[]{1, 2, 3});
// 数字Stream创建
IntStream.of(1, 2, 3);
IntStream.rangeClosed(1, 5);
// 创建一个无限流
new Random().ints().limit(5);
// 创建
Random rd= new Random();
Stream.generate(rd::nextInt).limit(20);
}
}
2、流的中间操作
操作分类 | 相关方法 |
---|---|
无状态 (有数据存储功能,线程不安全) | map/mapToXxx flatMap/flatMapToXxx filter peek unordered |
有状态 (一次操作,不能保存数据,线程安全) | distinct sorted limit/skip |
3、流的终止操作
操作分类 | 相关方法 |
---|---|
非短路操作 (有数据存储功能,线程不安全) | forEach/forEachOrdered collect/toArray reduce min/max/count |
短路操作 (不需要等待所有结果都计算完就可以结束流的操作) | findFirst/findAny allMatch/anyMatch/noneMatch |
public class StreamDemo2 {
public static void main(String[] args) {
String str = "Hello world";
// 迭代器forEach
// 使用并行流,乱序(效率更高)
str.chars().parallel().forEach(i -> System.out.print((char)i));
System.out.println("+++++++++++++++++++");
// 使用 forEachOrdered保证顺序
str.chars().parallel().forEachOrdered(i -> System.out.print((char)i));
System.out.println("+++++++++++++++++++");
// 收集器collect
// 收集到集合
List<String> list = Stream.of(str.split(" ")).collect(Collectors.toList());
System.out.println(list);
// 收集到Set
Set<String> set = Stream.of(str.split(" ")).collect(Collectors.toSet());
System.out.println(set);
// 归约reduce,将流中的元素结合起来得到一个值
// 拼接字符串
Optional<String> letters = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "," + s2);
// 使用optional进行空判断
System.out.println(letters.orElse(null));
// 带初始化值的reduce,默认值也会被算入元素中
String reduce = Stream.of(str.split(" ")).reduce("java语言:", (s1, s2) -> s1 + "," + s2);
System.out.println(reduce);
// 计算所有单词总长度
Integer length = Stream.of(str.split(" ")).map(String::length).reduce(0, (s1, s2) -> s1 + s2);
System.out.println(length);
// max的使用,找出最长的单词
Optional<String> maxLengthWorld = Stream.of(str.split(" ")).max(Comparator.comparingInt(String::length));
System.out.println(maxLengthWorld.orElse(null));
// min的使用,找出最短的单词
Optional<String> minLengthWorld = Stream.of(str.split(" ")).min(Comparator.comparingInt(String::length));
System.out.println(minLengthWorld.orElse(null));
// count的使用,统计有多少个单词
long count = Stream.of(str.split(" ")).count();
System.out.println(count);
// 使用 allMatch判断是否所有单词长度都大于1,返回boolean
// anyMatch和noneMatch用法差不多,都是返回boolean
boolean all = Stream.of(str.split(" ")).anyMatch(s -> s.length() > 1);
System.out.println(all);
// 使用 findFirst 短路操作
OptionalInt findFirst = new Random().ints().findFirst();
System.out.println(findFirst.orElse(0));
// 使用 findAny 短路操作
OptionalInt findAny = new Random().ints().findAny();
System.out.println(findAny.orElse(0));
}
}
三、JDK9中的响应式流
JDK9中的响应式流与JDK8中的函数式编程及stream流其实没有什么关系,其是依据Reactive Streams—响应式流规范而实现的,本质是一个发布-订阅模式的实现,其中核心类为java.util.concurrent.Flow,其中定义了Publisher、Subscriber、Subscription、Proccessor等核心响应式编程接口。
(一)简单demo
相关依赖:
JDK:1.9 以上
maven依赖:lombok
1、定义消费者Subscriber对象
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Flow;
// 实现java.util.concurrent.Flow.Subscriber
@Slf4j
public class BizSubscriber implements Flow.Subscriber<Integer> {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
// 绑定Subscription
this.subscription = subscription;
// 通过Subscription向Publisher请求数据
this.subscription.request(1);
}
@Override
public void onNext(Integer item) {
log.info("subscriber 接收数据:" + item);
// TODO
// do some biz things ……
// 处理完成后,重新通过Subscription向Publisher请求数据
this.subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
log.error("subscriber 处理错误", throwable);
// 处理发生异常时的错误处理
// TODO
// do some things when error
this.subscription.cancel();
}
@Override
public void onComplete() {
log.info("subscriber 完成数据处理!!");
// 处理完成后的业务逻辑处理
// TODO
// do some things when complete
}
}
2、demo代码
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
public class FlowTest {
public static void main(String[] args) throws Exception {
// 1、定义发布者实例,这里直接使用jdk自带的SubmissionPublisher(实现了Publisher接口),同时定义发布的数据类型式Integer
SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
// 2、定义消费者实例
Flow.Subscriber subscriber = new BizSubscriber();
// 3、建立发布-订阅消费绑定关系
publisher.subscribe(subscriber);
// 4、发布者开始发布数据
publisher.submit(1);
publisher.submit(2);
publisher.submit(3);
publisher.submit(4);
// 5、发布者发布完成后关闭
publisher.close();
// 这里为了演示需要,设置主线程延迟停止(否则发布者发布的数据还没有消费完,就可能会退出)
Thread.currentThread().join(1000);
}
}
最后执行接口如下:
16:10:15.589 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:1
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:2
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:3
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:4
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 完成数据处理!!
(二)源码解析
1、时序图示
下面我们结合上面的例子,深入到JDK的源码中查看整个链路的执行过程,我这里大致梳理了整体执行时序如下图所示:
通过上面图示我们可以总结出如下内容:
1、整个响应式流的逻辑核心处理逻辑是在Subscription中的;
2、Subscription的初始化工作是由Publisher来控制的,具体是在与subscriber创建绑定关系时做的;
3、Publisher真正提交数据时,也就是submit数据时,Subsciber才会真正的通过request方法请求到数据,并真实的消费处理数据。
2、关键步骤源码跟踪
(1)发布者定义
// 1、定义发布者实例,这里直接使用jdk自带的SubmissionPublisher(实现了Publisher接口),同时定义发布的数据类型式Integer
SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
// 这里做了两件事情:
// 1、设置Publisher异步执行线程池executor=ASYNC_POOL,最终默认线程池是ForkJoinPool.commonPool()
// 2、设置最大的缓存空间(也就是背压容量)为Flow.defaultBufferSize(),默认为256
// java.util.concurrent.SubmissionPublisher#SubmissionPublisher()
public SubmissionPublisher() {
this(ASYNC_POOL, Flow.defaultBufferSize(), null);
}
// 最终执行的SubmissionPublisher的构造方法
// java.util.concurrent.SubmissionPublisher#SubmissionPublisher(java.util.concurrent.Executor, int, java.util.function.BiConsumer<? super java.util.concurrent.Flow.Subscriber<? super T>,? super java.lang.Throwable>)
public SubmissionPublisher(Executor executor, int maxBufferCapacity,
BiConsumer<? super Subscriber<? super T>, ? super Throwable> handler) {
if (executor == null)
throw new NullPointerException();
if (maxBufferCapacity <= 0)
throw new IllegalArgumentException("capacity must be positive");
this.executor = executor;
this.onNextHandler = handler;
this.maxBufferCapacity = roundCapacity(maxBufferCapacity);
}
// ASYNC_POOL的初始逻辑
private static final Executor ASYNC_POOL =
(ForkJoinPool.getCommonPoolParallelism() > 1) ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
(2)发布者与消费者绑定订阅关系
// 3、建立发布-订阅消费绑定关系
publisher.subscribe(subscriber);
// java.util.concurrent.SubmissionPublisher#subscribe
public void subscribe(Subscriber<? super T> subscriber) {
if (subscriber == null) throw new NullPointerException();
// 1、初始化背压容器,设置背压容器的最大容量
int max = maxBufferCapacity; // allocate initial array
Object[] array = new Object[max < INITIAL_CAPACITY ?
max : INITIAL_CAPACITY];
// 2、初始化绑定关系Subscription实例,也就是BufferedSubscription实例
BufferedSubscription<T> subscription =
new BufferedSubscription<T>(subscriber, executor, onNextHandler,
array, max);
// 3、同步操作,这里就标明Publisher的subscribe方法是阻塞执行
synchronized (this) {
if (!subscribed) {
subscribed = true;
owner = Thread.currentThread();
}
// 4、这里是死循环会一直尝试执行,直到发生错误或执行完成
for (BufferedSubscription<T> b = clients, pred = null;;) {
if (b == null) {
// 5、首次进来,b = clients 为null,开始执行8、subscription.onSubscribe()方法
Throwable ex;
subscription.onSubscribe();
if ((ex = closedException) != null)
subscription.onError(ex);
else if (closed)
subscription.onComplete();
else if (pred == null)
clients = subscription;
else
pred.next = subscription;
break;
}
BufferedSubscription<T> next = b.next;
if (b.isClosed()) { // remove
b.next = null; // detach
if (pred == null)
clients = next;
else
pred.next = next;
}
else if (subscriber.equals(b.subscriber)) {
// 6、发生错误后,停止死循环
b.onError(new IllegalStateException("Duplicate subscribe"));
break;
}
else
pred = b;
// 7、执行下一个存在绑定关系(也就是下一个消费者实例)
b = next;
}
}
}
// 8、java.util.concurrent.SubmissionPublisher.BufferedSubscription#onSubscribe
final void onSubscribe() {
startOnSignal(RUN | ACTIVE);
}
// 9、java.util.concurrent.SubmissionPublisher.BufferedSubscription#startOnSignal
final void startOnSignal(int bits) {
if ((ctl & bits) != bits &&
(getAndBitwiseOrCtl(bits) & (RUN | CLOSED)) == 0)
tryStart();
}
// 10、java.util.concurrent.SubmissionPublisher.BufferedSubscription#tryStart
// 这里开始异步执行具体的任务
final void tryStart() {
try {
Executor e;
// ConsumerTask的初始化逻辑详见下面11
// 这里的this就是上面2、中创建的BufferedSubscription实例
ConsumerTask<T> task = new ConsumerTask<T>(this);
if ((e = executor) != null) // skip if disabled on error
e.execute(task);
} catch (RuntimeException | Error ex) {
getAndBitwiseOrCtl(ERROR | CLOSED);
throw ex;
}
}
// 11、java.util.concurrent.SubmissionPublisher.ConsumerTask
// 这里是ConsumerTask的初始化方法,其中consumer就是上面10中的this,也就是2、中创建的BufferedSubscription实例
// 通过该类的run方法,我们可以看到其最终执行的是BufferedSubscription实例的consume方法
static final class ConsumerTask<T> extends ForkJoinTask<Void>
implements Runnable, CompletableFuture.AsynchronousCompletionTask {
final BufferedSubscription<T> consumer;
ConsumerTask(BufferedSubscription<T> consumer) {
this.consumer = consumer;
}
public final Void getRawResult() { return null; }
public final void setRawResult(Void v) {}
public final boolean exec() { consumer.consume(); return false; }
public final void run() { consumer.consume(); }
}
// 12、java.util.concurrent.SubmissionPublisher.BufferedSubscription#consume
// 这里是整个响应式流背压执行的核心逻辑,其中head、tail等都是对array背压容量的相关位置记录
final void consume() {
// 13、注意这里的s,此处设值为subscriber,也就是2、中初始化时我们创建绑定关系的那个消费者BizSubscriber实例
Subscriber<? super T> s;
if ((s = subscriber) != null) { // hoist checks
// 14、这里开始传入消费者实例,确定与消费者之间的绑定关系,并会执行subscriber的onSubscribe方法
subscribeOnOpen(s);
long d = demand;
// 15、这里又是一个死循环,不断是对背压容器array的遍历操作执行
for (int h = head, t = tail;;) {
int c, taken; boolean empty;
if (((c = ctl) & ERROR) != 0) {
closeOnError(s, null);
break;
}
// 15-1、首次进来,会执行takeItems方法,其中把subscriber消费者实例传递了进去,具体详见15-2
// 在Publisher尚未submit提交数据时,taker永远是0,不会真实的执行subscriber中的逻辑
else if ((taken = takeItems(s, d, h)) > 0) {
head = h += taken;
d = subtractDemand(taken);
}
else if ((d = demand) == 0L && (c & REQS) != 0)
weakCasCtl(c, c & ~REQS); // exhausted demand
else if (d != 0L && (c & REQS) == 0)
weakCasCtl(c, c | REQS); // new demand
else if (t == (t = tail)) { // stability check
if ((empty = (t == h)) && (c & COMPLETE) != 0) {
closeOnComplete(s); // end of stream
break;
}
else if (empty || d == 0L) {
int bit = ((c & ACTIVE) != 0) ? ACTIVE : RUN;
if (weakCasCtl(c, c & ~bit) && bit == RUN)
break; // un-keep-alive or exit
}
}
}
}
}
// 14-1、java.util.concurrent.SubmissionPublisher.BufferedSubscription#subscribeOnOpen
// 此处的入参s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final void subscribeOnOpen(Subscriber<? super T> s) {
if ((ctl & OPEN) == 0 && (getAndBitwiseOrCtl(OPEN) & OPEN) == 0)
consumeSubscribe(s);
}
// 14-2、java.util.concurrent.SubmissionPublisher.BufferedSubscription#consumeSubscribe
// 此处的入参s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final void consumeSubscribe(Subscriber<? super T> s) {
try {
if (s != null) // ignore if disabled
// 此处开始执行subscriber的onSubscribe方法
s.onSubscribe(this);
} catch (Throwable ex) {
closeOnError(s, ex);
}
}
// 14-3、com.abinge.boot.staging.flow.BizSubscriber#onSubscribe
public void onSubscribe(Flow.Subscription subscription) {
// 绑定Subscription
this.subscription = subscription;
// 通过Subscription向Publisher请求数据
this.subscription.request(1);
}
// 14-4、java.util.concurrent.SubmissionPublisher.BufferedSubscription#request
public final void request(long n) {
if (n > 0L) {
for (;;) {
long p = demand, d = p + n; // saturate
if (casDemand(p, d < p ? Long.MAX_VALUE : d))
break;
}
// 最终仍会执行到最初的startOnSignal方法
startOnSignal(RUN | ACTIVE | REQS);
}
else
onError(new IllegalArgumentException(
"non-positive subscription request"));
}
// 15-2、java.util.concurrent.SubmissionPublisher.BufferedSubscription#takeItems
// 这里的入参s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final int takeItems(Subscriber<? super T> s, long d, int h) {
Object[] a;
int k = 0, cap;
// 在Publisher尚未submit提交数据时,k永远是0,不会继续执行到15-4中去真实的执行subscriber中的逻辑
if ((a = array) != null && (cap = a.length) > 0) {
int m = cap - 1, b = (m >>> 3) + 1; // min(1, cap/8)
int n = (d < (long)b) ? (int)d : b;
// 15-3、这里又是一个死循环,仍然是对背压容器array的相关操作
for (; k < n; ++h, ++k) {
Object x = QA.getAndSet(a, h & m, null);
if (waiting != 0)
signalWaiter();
if (x == null)
break;
// 15-4、首次执行,会进入到consumeNext方法, 其中s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
else if (!consumeNext(s, x))
break;
}
}
return k;
}
// 15-5、java.util.concurrent.SubmissionPublisher.BufferedSubscription#consumeNext
// 这里s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final boolean consumeNext(Subscriber<? super T> s, Object x) {
try {
@SuppressWarnings("unchecked") T y = (T) x;
if (s != null)
// 15-6、此处会去真正的执行BizSubscriber实例的onNext方法
s.onNext(y);
return true;
} catch (Throwable ex) {
handleOnNext(s, ex);
return false;
}
}
// 15-6、com.abinge.boot.staging.flow.BizSubscriber#onNext
// 这里就会去执行我们自定义的BizSubscriber实例的onNext方法,其中可包含具体的业务逻辑,执行完成后再次向Publisher请求数据
public void onNext(Integer item) {
log.info("subscriber 接收数据:" + item);
// TODO
// do some biz things ……
// 处理完成后,重新通过Subscription向Publisher请求数据,具体可参见14-4
this.subscription.request(1);
}
(3)Publisher真实的提交数据
// 4、发布者开始发布数据
publisher.submit(1);
publisher.submit(2);
publisher.submit(3);
publisher.submit(4);
此处源码跟踪可自行查询,其大致逻辑是把item放入背压容器array中,然后执行10、java.util.concurrent.SubmissionPublisher.BufferedSubscription#tryStart方法,真实的启动subscriber的相应业务逻辑。