響應式程式設計庫RxJava初探

琴水玉發表於2021-01-24

引子

在讀 Hystrix 原始碼時,發現一些奇特的寫法。稍作搜尋,知道使用了最新流行的響應式程式設計庫RxJava。那麼響應式程式設計究竟是怎樣的呢? 本文對響應式程式設計及 RxJava 庫作一個初步的探索。

在學習新的程式設計模型時,我喜歡將其與原來的程式設計模型聯絡起來。因為新的程式設計模型往往是對原來程式設計模型的承襲和組合。響應式程式設計的兩個基本要素是:

  • 基於觀察者模式的事件驅動機制。
  • 函數語言程式設計:通過裝飾與組合,讓響應式程式設計的處理更流暢靈活;

函數語言程式設計,在之前的文章 “完全”函數語言程式設計”“Java8函數語言程式設計探祕”“精練程式碼:一次Java函數語言程式設計的重構之旅” 等有較多探索,觀察者模式在 “設計模式之觀察者模式:實現配置更新實時推送” 有講述過。我們將在這兩者的基礎上探索響應式程式設計。

基礎

初次接觸 RxJava ,很容易被一連串的 Observer, Observable, Disposable, subscribeOn, onSubscribe, onNext, onError, onComplete 等繞暈。不過軟體裡面無新鮮事。大多無非是用一種新的方式來組織邏輯罷了。基於觀察者模式的事件驅動也不例外。我們只要梳理清楚脈絡,就可以容易地理解。觀察者模式有三個基本參與者:

  • 被觀察者:Observable ;
  • 發射裝置:Emitter;
  • 觀察者: Observer。

基本流程是:被觀察者 Observable 裝備發射裝置 Emitter,發射訊息,建立事件;觀察者 Observer 監聽到事件,接收到被觀察者發射的訊息,呼叫對應的函式 onNext, onError 和 onComplete 進行處理。onError 和 OnComplete 只能有一個被觸發。

不妨寫個基本 Demo 來模擬下基本流程。為了更好滴理解,我把三者都區分開了。

Demo

首先定義觀察者 MyObserver,繼承抽象類 DefaultObserver ,這樣實現成本最小。


package zzz.study.reactor;

import com.alibaba.fastjson.JSON;
import io.reactivex.observers.DefaultObserver;

/**
 * @Description 觀察者定義
 * @Date 2021/1/23 4:13 下午
 * @Created by qinshu
 */
public class MyObserver extends DefaultObserver {

    @Override
    public void onStart() {
        System.out.println("MyObserver: Start");
    }

    @Override
    public void onNext(Object o) {
        System.out.println("Observed: " + JSON.toJSONString(o));
    }

    @Override
    public void onError(Throwable e) {
        System.out.println("Observed: " + e.getMessage());
    }

    @Override
    public void onComplete() {
        System.out.println("MyObserver: Complete");
    }
}

接著,定義發射裝置(發射訊息) MyEmitter:

package zzz.study.reactor;

import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @Description 發射裝置
 * @Date 2021/1/24 7:04 上午
 * @Created by qinshu
 */
public class MyEmitter implements ObservableOnSubscribe {

    Random random = new Random(System.currentTimeMillis());

    @Override
    public void subscribe(ObservableEmitter emitter) throws Exception {
        TimeUnit.SECONDS.sleep(1);
        emitter.onNext("next");
        if (random.nextInt(3) == 0) {
            emitter.onError(new RuntimeException("A RuntimeException"));
        }
        else {
            emitter.onComplete();
        }
    }
}

最後,建立被觀察者,並串起流程:

package zzz.study.reactor;

import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;

/**
 * @Description RxJava基本Demo
 * @Date 2021/1/23 12:28 下午
 * @Created by qinshu
 */
public class RxJavaBasic {

    public static void main(String[] args) {
        for (int i=0; i<5; i++) {
            ObservableOnSubscribe observableOnSubscribe = new MyEmitter();
            Observable observable = Observable.create(observableOnSubscribe);
            Observer observer = new MyObserver();
            observable.subscribe(observer);
        }
    }
}

執行,可得結果:

MyObserver: Start
Observed: "next"
MyObserver: Complete
MyObserver: Start
Observed: "next"
MyObserver: Complete
MyObserver: Start
Observed: "next"
Observed: A RuntimeException
MyObserver: Start
Observed: "next"
MyObserver: Complete
MyObserver: Start
Observed: "next"
MyObserver: Complete

講解

如何理解上述流程及結果呢?最好的辦法就是單步除錯。經過單步除錯,可以知道整個過程如下:

步驟1: 整個過程由這一行觸發 observable.subscribe(observer); ,會去呼叫 Observable.subscribeActual 方法,分派給具體實現類 ObservableCreate.subscribeActual ;單步除錯的好處就是能確定具體實現者;

步驟2: ObservableCreate.subscribeActual 所做的事情,呼叫 observer.onSubscribe ( MyObserver.onStart 方法 ),然後轉發給 MyEmitter.subscribe 來發射訊息。

    @Override
    protected void subscribeActual(Observer<? super T> observer) {
        CreateEmitter<T> parent = new CreateEmitter<T>(observer);
        observer.onSubscribe(parent);

        try {
            source.subscribe(parent);
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            parent.onError(ex);
        }
    }

步驟3:MyEmitter 執行 onNext ,分派給具體實現類 CreateEmitter.onNext ,進而呼叫 observer.onNext 方法;
步驟4:MyEmitter 執行 onError ,分派給具體實現類 CreateEmitter.onError ,進而 呼叫 observer.onError 方法;如果 MyEmitter 發射 onComplete ,那麼就會分派給具體實現類 CreateEmitter.onComplete ,進而呼叫 observer.onComplete 方法。注意,onError 和 onComplete 兩者只可能執行一個。

基本流程就是這樣。

引申

Disposable

除了訂閱自定義 Emitter 來發射訊息,類 Observable 還提供了各種工具方法,更便捷滴做訂閱和推送。比如:

public static void testDirectSubscribe() {
    Observable.fromArray("I", "Have", "a", "dream").subscribe(new MyObserver());
}

會輸出:

MyObserver: Start
Observed: "I"
Observed: "Have"
Observed: "a"
Observed: "dream"
MyObserver: Complete

具體實現是: fromArray 方法會建立一個 Observable 的具體類 ObservableFromArray,而這個類的 subscribeActual 方法會建立一個 FromArrayDisposable 來處理。FromArrayDisposable 的 run 方法被呼叫,依次遍歷所指定列表,呼叫 observer.onNext ,最後呼叫 observer.onComplete。具體原始碼如下:

public final class ObservableFromArray<T> extends Observable<T> {
    final T[] array;
    public ObservableFromArray(T[] array) {
        this.array = array;
    }

    @Override
    public void subscribeActual(Observer<? super T> observer) {
        FromArrayDisposable<T> d = new FromArrayDisposable<T>(observer, array);
        observer.onSubscribe(d);
        if (d.fusionMode) {
            return;
        }
        d.run();
    }

    static final class FromArrayDisposable<T> extends BasicQueueDisposable<T> {
        final Observer<? super T> downstream;
        final T[] array;
        int index;
        boolean fusionMode;
        volatile boolean disposed;

        FromArrayDisposable(Observer<? super T> actual, T[] array) {
            this.downstream = actual;
            this.array = array;
        }

       // other methods

        @Override
        public void dispose() {
            disposed = true;
        }

        @Override
        public boolean isDisposed() {
            return disposed;
        }

        void run() {
            T[] a = array;
            int n = a.length;

            for (int i = 0; i < n && !isDisposed(); i++) {
                T value = a[i];
                if (value == null) {
                    downstream.onError(new NullPointerException("The element at index " + i + " is null"));
                    return;
                }
                downstream.onNext(value);
            }
            if (!isDisposed()) {
                downstream.onComplete();
            }
        }
    }
}

那麼 Disposable 的意義何在呢 ? 我的理解是:它作為訂閱完成的一個流程閉環。比如重複訂閱同一個觀察者,如下程式碼:

    public static void testDirectSubscribe() {
        Observer observer = new MyObserver();
        Observable.fromArray("I", "Have", "a", "dream").subscribe(observer);
        Observable.fromArray("changed").subscribe(observer);
    }

會丟擲異常:

io.reactivex.exceptions.ProtocolViolationException: It is not allowed to subscribe with a(n) zzz.study.reactor.MyObserver multiple times. Please create a fresh instance of zzz.study.reactor.MyObserver and subscribe that to the target source instead.
    at io.reactivex.internal.util.EndConsumerHelper.reportDoubleSubscription(EndConsumerHelper.java:148)
    at io.reactivex.internal.util.EndConsumerHelper.validate(EndConsumerHelper.java:57)
    at io.reactivex.observers.DefaultObserver.onSubscribe(DefaultObserver.java:70)
    at io.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:34)
    at io.reactivex.Observable.subscribe(Observable.java:12284)
    at zzz.study.reactor.RxJavaBasic.testDirectSubscribe(RxJavaBasic.java:34)
    at zzz.study.reactor.RxJavaBasic.main(RxJavaBasic.java:17)

這個異常是在呼叫 DefaultObserver.onSubscribe 丟擲的:

    @Override
    public final void onSubscribe(@NonNull Disposable d) {
        if (EndConsumerHelper.validate(this.upstream, d, getClass())) {
            this.upstream = d;
            onStart();
        }
    }

   public static boolean validate(Disposable upstream, Disposable next, Class<?> observer) {
        ObjectHelper.requireNonNull(next, "next is null");
        if (upstream != null) {
            next.dispose();
            if (upstream != DisposableHelper.DISPOSED) {
                reportDoubleSubscription(observer);
            }
            return false;
        }
        return true;
    }

這就是說,如果同一個觀察者,它的上一個 Disposable 訂閱沒有結束,那麼再次訂閱 Disposable 就會出錯。怎麼解決呢?可以在 MyObserver 的 onError 和 onComplete 新增 super.cancel 呼叫,可以結束上一次的訂閱,再次訂閱就不丟擲異常了:

    @Override
    public void onError(Throwable e) {
        System.out.println("Observed: " + e.getMessage());
        super.cancel();
    }

    @Override
    public void onComplete() {
        System.out.println("MyObserver: Complete");
        super.cancel();
    }

   /**
     * Cancels the upstream's disposable.
     */
    protected final void cancel() {
        Disposable upstream = this.upstream;
        this.upstream = DisposableHelper.DISPOSED;
        upstream.dispose();
    }

但是,即便這樣,也無法發射我們新的訂閱訊息。這是因為上一次的 upstream 不為 null,本次的訂閱就無法發射。

我們沒法覆寫 DefaultObserver.onSubscribe 方法,因為該方法宣告為 final 的,且 upstream 宣告為 private ,也沒有公共方法可以設定 upstream。這明確表明了設計者的意圖:這是 Observer 訂閱 Disposable 的前置檢測約定,不可被破壞,否則後果自負。

我們可以繞過 DefaultObserver , 不繼承它,而是直接實現 Observer 介面:


public static void testDirectSubscribe() {
    Observer observer = new RepeatedSubscribeMyObserver();
    Observable.fromArray("I", "Have", "a", "dream").subscribe(observer);
    Observable.fromArray("changed").subscribe(observer);
}

/**
 * @Description 可重複訂閱的觀察者
 * @Date 2021/1/24 10:11 上午
 * @Created by qinshu
 */
public class RepeatedSubscribeMyObserver<T> implements Observer<T> {

    public Disposable upstream;

    @Override
    public void onSubscribe(@NonNull Disposable d){
        System.out.println(getName() + ": Start");
        this.upstream = d;
    }

    @Override
    public void onNext(T o) {
        System.out.println(getName() + ": " + JSON.toJSONString(o));
    }

    @Override
    public void onError(Throwable e) {
        System.out.println(getName() + ": " + e.getMessage());
        cancel();
    }

    @Override
    public void onComplete() {
        System.out.println(getName() + ": Complete");
        cancel();
    }

    public String getName() {
        return this.getClass().getSimpleName();
    }

    /**
     * Cancels the upstream's disposable.
     */
    protected final void cancel() {
        Disposable upstream = this.upstream;
        this.upstream = DisposableHelper.DISPOSED;
        upstream.dispose();
    }
}

這樣就可以實現多次訂閱同一個 Observer 了。執行結果:

RepeatedSubscribeMyObserver: Start
RepeatedSubscribeMyObserver: "I"
RepeatedSubscribeMyObserver: "Have"
RepeatedSubscribeMyObserver: "a"
RepeatedSubscribeMyObserver: "dream"
RepeatedSubscribeMyObserver: Complete
RepeatedSubscribeMyObserver: Start
RepeatedSubscribeMyObserver: "changed"
RepeatedSubscribeMyObserver: Complete

弄懂了 Observable.fromArray 的實現原理,就弄清楚了 Observable 中很多基本方法的基本套路。比如 just 方法有兩個及以上引數時,其實是 fromArray 的包裝,而 range 方法則是建立一個 RangeDisposable 來處理。

Observable.just(1,2,3).subscribe(observer);
Observable.range(1,4).subscribe(observer);

裝飾器

RxJava 大量使用了裝飾器模式。在 observable 包下有一個繼承自 Observable 的抽象類 AbstractObservableWithUpstream,所有繼承它的子類,都遵循一個相同的套路:一個 Observable 子類,以及一個相應的 Observer 裝飾者。 比如 ObservableDelay 裡面就相應有一個 DelayObserver ,實現觀察者的延遲或週期性接收。學會舉一反三、觸類旁通。相應實現如下:

public final class ObservableDelay<T> extends AbstractObservableWithUpstream<T, T> {
    final long delay;
    final TimeUnit unit;
    final Scheduler scheduler;
    final boolean delayError;

    public ObservableDelay(ObservableSource<T> source, long delay, TimeUnit unit, Scheduler scheduler, boolean delayError) {
        super(source);
        this.delay = delay;
        this.unit = unit;
        this.scheduler = scheduler;
        this.delayError = delayError;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void subscribeActual(Observer<? super T> t) {
        Observer<T> observer;
        if (delayError) {
            observer = (Observer<T>)t;
        } else {
            observer = new SerializedObserver<T>(t);
        }

        Scheduler.Worker w = scheduler.createWorker();

        source.subscribe(new DelayObserver<T>(observer, delay, unit, w, delayError));
    }

    static final class DelayObserver<T> implements Observer<T>, Disposable {
        final Observer<? super T> downstream;
        final long delay;
        final TimeUnit unit;
        final Scheduler.Worker w;
        final boolean delayError;

        Disposable upstream;

        DelayObserver(Observer<? super T> actual, long delay, TimeUnit unit, Worker w, boolean delayError) {
            super();
            this.downstream = actual;
            this.delay = delay;
            this.unit = unit;
            this.w = w;
            this.delayError = delayError;
        }

        @Override
        public void onSubscribe(Disposable d) {
            if (DisposableHelper.validate(this.upstream, d)) {
                this.upstream = d;
                downstream.onSubscribe(this);
            }
        }

        @Override
        public void onNext(final T t) {
            w.schedule(new OnNext(t), delay, unit);
        }

        final class OnNext implements Runnable {
            private final T t;

            OnNext(T t) {
                this.t = t;
            }

            @Override
            public void run() {
                downstream.onNext(t);
            }
        }
    }
}

組合

上文談到了響應式程式設計的一大基本元素是函數語言程式設計。函式式的優勢是可以無限疊加組合,構建出靈活多變的函式和行為。這使得觀察者的行為也可以定製得更加靈活。可以組合多個 Observable 的發射行為。

合併

簡單的組合使用 merge 方法,構造一個 Observable 的列表,依次遍歷合併後的每個 Observable 的發射資訊:

Iterable<? extends ObservableSource<? extends Integer>> observableSourceSet = Sets.newHashSet(
                Observable.fromArray(3,4,5),
                Observable.range(10,3)
        );
        Observable.merge(observableSourceSet).subscribe(observer);

流式

Observable 可以通過 Stream 進行組合,這裡就是函數語言程式設計的用武之地了。如下程式碼所示:

Observable.range(1,10).filter(x -> x%2 ==0).subscribe(observer);

注意到,這裡使用到了裝飾器模式。filter 方法會建立一個 ObservableFilter 物件,而在這個物件裡,subscribeActual 方法會建立一個 FilterObserver 將傳入的 observer 裝飾起來。downstream 即是傳入的 observer。


@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
public final Observable<T> filter(Predicate<? super T> predicate) {
    ObjectHelper.requireNonNull(predicate, "predicate is null");
    return RxJavaPlugins.onAssembly(new ObservableFilter<T>(this, predicate));
}

public final class ObservableFilter<T> extends AbstractObservableWithUpstream<T, T> {
    final Predicate<? super T> predicate;
    public ObservableFilter(ObservableSource<T> source, Predicate<? super T> predicate) {
        super(source);
        this.predicate = predicate;
    }

    @Override
    public void subscribeActual(Observer<? super T> observer) {
        source.subscribe(new FilterObserver<T>(observer, predicate));  // FilterObserver 裝飾了傳入的自定義的 observer 
    }

    static final class FilterObserver<T> extends BasicFuseableObserver<T, T> {
        final Predicate<? super T> filter;

        FilterObserver(Observer<? super T> actual, Predicate<? super T> filter) {
            super(actual);
            this.filter = filter;
        }

        @Override
        public void onNext(T t) {     // 這裡對傳入的 Observer.onNext 做了個裝飾,僅當條件成立時才呼叫
            if (sourceMode == NONE) {
                boolean b;
                try {
                    b = filter.test(t);
                } catch (Throwable e) {
                    fail(e);
                    return;
                }
                if (b) {
                    downstream.onNext(t);  // downstream 即是我們傳入的自定義的 Observer
                }
            } else {
                downstream.onNext(null);
            }
        }
}

正如 filter 對發射資料流進行過濾,map 或 flatMap 則對發射資料流進行對映變換,與 stream.map 或 stream.flatMap 的功能類似:

Observable.range(1,10).map(x -> x*x).subscribe(observer);
Observable.range(1,10).flatMap(x -> Observable.just(x*x)).subscribe(observer);

map 方法將建立一個 ObservableMap 物件,在 subscribeActual 中用 MapObserver 將所傳入的 observer 裝飾起來;flatMap 將建立一個 ObservableFlatMap 物件,在 subscribeActual 中 MergeObserver 將傳入的 observer 裝飾起來。

還可以使用 scan:對於生成的每個值,使用累加器 (x,y) -> x*y 生成新的值併發射。

Observable.range(1, 10).scan(1, (x,y) -> x*y).subscribe(observer);

最後再給個分組的示例:

Observable.just(28,520,25,999).groupBy( i -> ( i > 100 ? "old": "new")).subscribe(new GroupedRepeatedSubscribeMyObserver());

/**
 * @Description 可重複訂閱的分組觀察者
 * @Date 2021/1/24 10:11 上午
 * @Created by qinshu
 */
public class GroupedRepeatedSubscribeMyObserver extends RepeatedSubscribeMyObserver<GroupedObservable> {
    @Override
    public void onNext(GroupedObservable o) {
        o.subscribe(new RepeatedSubscribeMyObserver() {
            @Override
            public void onNext(Object v) {
                String info = String.format("GroupedRepeatedSubscribeMyObserver: [group=%s][value=%s]", o.getKey(), JSON.toJSONString(v));
                System.out.println(info);
            }
        });

    }

}

groupBy 方法生成的是一個 GroupedObservable ,因此要訂閱一個 Observer 的觀察者實現。

本文先寫到這裡。

專案程式碼見工程: “ALLIN” 的包 zzz.study.reactor 下。需要引入 Maven 依賴:

<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxjava</artifactId>
    <version>2.2.20</version>
</dependency>

小結

本文講解了響應式程式設計及 RxJava 庫的最基本概念:Observable , Observer 及 Emitter, Disposable ,也講到了如何組合 Observable 來構建更靈活的訊息發射機制。這些基本構成了響應式程式設計的基本骨架流程。

響應式程式設計的強大能力構建在事件驅動機制和函數語言程式設計上,裡面大量應用了裝飾器模式。因此,熟悉這些基本程式設計思想,對掌握響應式程式設計模型亦大有裨益。

相關文章