从设计模式到Stream再到Reactive

96 阅读4分钟
  1. 背景

本文主要通过装饰器模式以及JDK8中Stream集合流操作再到Reactive响应式编程来探索Java运用上的一些相同点和不通点

  1. 一个例子

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响应式方式 ,展示了不同的处理数据源的方式,三种处理方式在架构设计外观上有相似的地方,也有不同的地方。

相似的地方:

  1. 都是链接调用
  2. 都是基于pipleline的处理方式,将不同的处理逻辑组合起来,流水线执行

不同的地方:

  1. 装饰器模式可以看出是 先把流水线 组装好,最后在真正需要对数据加工的时候,启动处理流水线对数据进行处理,整个处理过程还是属于半声明半手动的模式,属于正向处理方式

  2. 而对于java stream流以及reactive programming的方式,可以看到是先给出了数据源,然后对数据源定义了一系列的操作符(也就是加工处理handler) ,然后通过最后一个操作符(JDK Stream是通过定义一些终止操作符,而Reactive是通过固定叫subscribe的操作符)来真正启动整个流水线的开关,让整个加工逻辑动起来(否则没有这些操作符加工流水线是不会运转起来的),

    1. 为什么这么设计呢?

      1. 可以看出不管是JDK的Stream或是Reactive都是完全声明式的操作, 不需要像裸装饰器模式那样,在外部先定义好对一个原始数据源层层装饰的流程(这个是正向的,放入数据源,数据就会先经过装饰层最后到达原始处理层),而是完全声明式的定义处理的流程,至于对原始数据的层层加工的装饰则是在内部实现中进行的
    2. 还有一个问题 - 一般装饰器都是数据先经过装饰层的处理,一层一层装饰,最后到达最底层的(比如发送HTTP请求等等),对于原生装饰器模式来说先定义处理流程,最后添加原料。但是对于JDK Stream或是Reactive的操作符来说都是对原始数据源不断添加操作符,最后遇到"开关"-终止符时触发整个机器的启动,但是在启动机器的时候,原始数据源已经被声明的操作符层层包装,最后到终止符拿到的是 一个被层层包装的(类似于写满了应该如何操作一个原材料的原材料)原材料,而不是直接将原材料放到对应的处理流水线(此时还不知道应该将原材料放到一个怎样的流水线),那么就需要对原材料按照加工说明书,重新组装成一个正向的处理流水线,然后放入原材料启动流水线

  3. JDK Stream以及Reactive是如何处理的

  1. 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 的处理链,之后填入原数据就可以正常触发整个流水线的执行了

  1. 先看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); 的处理要更复杂一些,并不是直接在这一步就直接开启流水线运行处理数据,而是又增加了一步背压的处理流程