前言
Reactor3的生产元素有很多种方式。比如常用的Flux.just
等等。但是Flux.just
是一个热数据源,它的元素在调用Flux.just
的时候就已经确定了,并且无论多少个订阅者订阅多少次,它们拿到的元素都是一样的。如果有一个生产者和一个消费者,在发生订阅的时候生产者可能还不知道要下发的元素是什么,可能要等一个事件来触发消息的下发,当这个事件到来需要马上下发给消费者,这个时候Flux.jus
t就无能为力了。这个时候就需要用到Flux.create或者Flux.push
了。
版本
Reactor 3.4.9
阅读须知
虽然本篇是Flunx.create
与Flux.push
的最佳实践,后面笔者都是拿create
来举例与说明,在本篇的例子不涉及到两者区别的地方,读者可以将create
直接替换成push
,效果是一样的。两者的区别会在下篇文章说明。
抛出问题
一个生产者,一个消费者。可以往生产者不断的提交元素,然后由消费者来进行消费。这个被提交的元素在创建生产者是不知道,可能后面某个时候由某个事件触发。就好比12306放票一样,火车票在某个时间点触发放票事件,然后丢到队列,由消费者执行放票后的逻辑。
简单的例子
Flux.create(sink -> {
for (int i = 0; i < 10; i++) {
sink.next(i);
}
sink.complete();
}).subscribe(System.out::println);
上面的例子很容易看懂,在一个for循环中使用sink来下发元素然后结束。
很多文章甚至书籍都是拿这样的例子来介绍Flux.create()
,这样的用法和Flux.just其实没有太大的区别,在创建的时候就已经确定了怎么去下发元素。这样例子其实并不能让我们真正的使用Flux.create
去解决实际开发中的问题,意义不大。
一个Bad的解决方案
上面的例子之所以不能解决抛出的问题,关键在于下发元素只能在create的函数表达式中进行,也就是函数式的参数FluxSink,那如果在这个函数式中将FluxSink开放出来呢?
FluxSink<String> outSink;
@Test
public void testFluxCreate() {
Flux<String> f = Flux.create(sink -> {
outSink = sink;
});
f.subscribe(e -> System.out.println(e))
//do something
//下发元素
outSink.next("我来了");
outSink.next("我来了2");
}
- 声明一个
FluxSink
类型成员变量outSink
,用来接收Flux.create
中的sink
。 - 在
Flux.create
函数式方式中将sink
赋值给成员变量 - 在外部通过
outSink
随时下发元素。
这样确实能解决上面的抛出的问题,但是也引发了其他的问题。我们只是想进行元素的下发,但是将FluxSink开放出来它可不止能进行元素下发,还有其他的方法。这样做破坏了封装性,如果其他人使用不当,比如提前结束FluxSink等等会引发异常的Bug。
使用的官网例子来解决
通过上面的例子,我们可以得到一个结论:不能把FluxSink放出来。问题只能在内部解决,不能公开😂。
在Reactor3 Guide中有下面这一段Flux.create
的用法。
interface MyEventListener<T> {
void onDataChunk(List<T> chunk);
void processComplete();
}
Flux<String> bridge = Flux.create(sink -> {
myEventProcessor.register(
new MyEventListener<String>() {
public void onDataChunk(List<String> chunk) {
for(String s : chunk) {
sink.next(s);
}
}
public void processComplete() {
sink.complete();
}
});
});
有一个监听者的接口,在create
的lambda
表达式中创建一个监听者并将它注册到监听器中。而这个监听者在事件触发的时候就会调用sink
的下发元素的方法。它并没有将FluxSink
的直接暴露出去,而是使用一个订阅者对它进行了一层封装,只暴露一些需要的方法。
简化后的方案
上面我们看到虽然能解决问题,但是需要一个监听者接口并实现它,还需要一个监听器,如果是一个简单的问题,只有一个监听者需要这么步骤就显得太复杂了。那我们可以结合第一个Bad方案和官方的方案得到一个简化的方案:
Consumer<String> producer;
@Test
public void testFluxCreate() {
Flux.create(sink -> {
producer = nextData -> sink.next(nextData);
}).subscribe(e -> System.out.println(e));
//do something
//下发元素
producer.accept("我来了");
producer.accept("我来了2");
}
- 声明一个
Consumer
类型成员变量producer
,在Flux.create
中进行初始化,定义如何使用sink
。 - 在外部通过
producer
随时下发元素。
上面的例子我们即简单的解决了上面抛出的问题,也没有破话封装性,只暴露关键的功能出来。
总结
本篇介绍了使用Flux.create与Flux.push去解决一些实际开发中会遇到的问题,正确的使用它们并且发挥出它们的真实价值。而不是像其他文章那样简单的使用埋没了它们的真实能力。
感谢阅读,希望对你有帮助。