-
背景
本文主要通过装饰器模式以及JDK8中Stream集合流操作再到Reactive响应式编程来探索Java运用上的一些相同点和不通点
-
一个例子
public static void main(String[] args) {
// 1、装饰器模式
ProvincePublisher provincePublisher = new ProvincePublisher(new CityPublisher(new RegionPubliser(new ActPublisher())));
provincePublisher.publish( "Hello, World!" );
System.out.println( "-------------------" );
//2、java stream
// Example of using Java Streams to process a list of integers
Arrays.asList(1, 2, 3, 4, 5)
.stream()
.map(i -> i * 2) // Double each integer
.filter(i -> i > 5) // Filter out integers less than or equal to 5
.forEach(System.out::println); // Print each integer
System.out.println( "-------------------" );
//3、reactive programming
// Example of using Reactive Programming with Project Reactor
Flux.just(1, 2, 3, 4, 5)
.map(i -> i * 2) // Double each integer
.filter(i -> i > 5) // Filter out integers less than or equal to 5
.subscribe(System.out::println); // Print each integer
}
说明:上面的例子主要三种不同的方式:1-装饰器模式 2-java stream流的方式 3-reactive响应式方式 ,展示了不同的处理数据源的方式,三种处理方式在架构设计外观上有相似的地方,也有不同的地方。
相似的地方:
- 都是链接调用
- 都是基于pipleline的处理方式,将不同的处理逻辑组合起来,流水线执行
不同的地方:
-
装饰器模式可以看出是 先把流水线 组装好,最后在真正需要对数据加工的时候,启动处理流水线对数据进行处理,整个处理过程还是属于半声明半手动的模式,属于正向处理方式
-
而对于java stream流以及reactive programming的方式,可以看到是先给出了数据源,然后对数据源定义了一系列的操作符(也就是加工处理handler) ,然后通过最后一个操作符(JDK Stream是通过定义一些终止操作符,而Reactive是通过固定叫subscribe的操作符)来真正启动整个流水线的开关,让整个加工逻辑动起来(否则没有这些操作符加工流水线是不会运转起来的),
-
为什么这么设计呢?
- 可以看出不管是JDK的Stream或是Reactive都是完全声明式的操作, 不需要像裸装饰器模式那样,在外部先定义好对一个原始数据源层层装饰的流程(这个是正向的,放入数据源,数据就会先经过装饰层最后到达原始处理层),而是完全声明式的定义处理的流程,至于对原始数据的层层加工的装饰则是在内部实现中进行的
-
还有一个问题 - 一般装饰器都是数据先经过装饰层的处理,一层一层装饰,最后到达最底层的(比如发送HTTP请求等等),对于原生装饰器模式来说先定义处理流程,最后添加原料。但是对于JDK Stream或是Reactive的操作符来说都是对原始数据源不断添加操作符,最后遇到"开关"-终止符时触发整个机器的启动,但是在启动机器的时候,原始数据源已经被声明的操作符层层包装,最后到终止符拿到的是 一个被层层包装的(类似于写满了应该如何操作一个原材料的原材料)原材料,而不是直接将原材料放到对应的处理流水线(此时还不知道应该将原材料放到一个怎样的流水线),那么就需要对原材料按照加工说明书,重新组装成一个正向的处理流水线,然后放入原材料启动流水线
-
-
JDK Stream以及Reactive是如何处理的
-
JDK Stream流处理流程
java.util.stream.AbstractPipeline#wrapSink
@Override
@SuppressWarnings( "unchecked" )
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
Objects.requireNonNull(sink);
for ( @SuppressWarnings( "rawtypes" ) AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
}
return (Sink<P_IN>) sink;
}
可以看到在最后的终止符的foreach的操作处理中,是会对最后一个操作符也就是foreach 通过查找当前AbstractPipeline 的前一个进行不断装饰,最终就构成了一个正向的从 map->filter->foreach consumer 的处理链,之后填入原数据就可以正常触发整个流水线的执行了
-
先看Reactive处理流程
reactor.core.publisher.Flux#subscribe(org.reactivestreams.Subscriber<? super T>)
@Override
@SuppressWarnings( "unchecked" )
public final void subscribe(Subscriber<? super T> actual) {
CorePublisher publisher = Operators.onLastAssembly(this);
CoreSubscriber subscriber = Operators.toCoreSubscriber(actual);
if (subscriber instanceof Fuseable.QueueSubscription && this != publisher && this instanceof Fuseable && !(publisher instanceof Fuseable)) {
subscriber = new FluxHide.SuppressFuseableSubscriber<>(subscriber);
}
try {
if (publisher instanceof OptimizableOperator) {
OptimizableOperator operator = (OptimizableOperator) publisher;
while (true) {
subscriber = operator.subscribeOrReturn(subscriber);
if (subscriber == null) {
// null means "I will subscribe myself", returning...
return;
}
OptimizableOperator newSource = operator.nextOptimizableSource();
if (newSource == null) {
publisher = operator.source();
break;
}
operator = newSource;
}
}
subscriber = Operators.restoreContextOnSubscriberIfPublisherNonInternal(publisher, subscriber);
publisher.subscribe(subscriber);
}
catch (Throwable e) {
Operators.reportThrowInSubscribe(subscriber, e);
return;
}
}
可以看到在reactive中也是一样,也是先对订阅者进行操作符的层层包装,构成一个正向的 map->filter->真实消费者 的处理链。最后装饰好的 subscriber 已经是一个正向的处理链了。其实如果 Reactive 没有背压处理的话,其实完全可以在 publisher.subscribe(subscriber); 直接通过subscriber#onNext进行下发数据了(就像JDK中处理的那样),但是因为reactive中又增加了一步背压处理,所以reactive在 publisher.subscribe(subscriber); 的处理要更复杂一些,并不是直接在这一步就直接开启流水线运行处理数据,而是又增加了一步背压的处理流程