Java中的反應流詳解

什麼是反應流?
在許多應用程序中,不是從固定存儲設備中檢索數據,而是幾乎實時地處理數據,而用戶或其他系統會迅速將信息注入到我們的系統中。多數情況下,此數據注入是異步的,因此我們無法提前知道何時會出現數據。爲了促進這種異步的數據處理方式,我們必須重新考慮較舊的基於輪詢的模型,而應使用更輕便,更簡化的方法。
處理器
如果實體既是發佈者又是訂閱者,則稱爲處理器。處理器通常充當另一個發佈者和訂閱者(​​兩者都可以是另一個處理器)之間的中介,對數據流執行某種轉換。例如,可以創建處理器,該處理器在將符合某些條件的項目傳遞給其訂戶之前將其過濾掉。下圖顯示了處理器的直觀表示。
通過對反應流的工作方式有基本的瞭解,我們可以通過將它們編碼爲接口將這些概念轉換爲Java領域。
接口表示
根據上面的描述,響應流由四個主要實體組成:(1)發佈者,(2)訂閱者,(3)訂閱和(4)處理器。從界面的角度來看,僅要求發佈者允許訂閱者訂閱它們。因此,我們可以爲發佈者創建一個簡單的接口,其中正式的泛型類型參數T代表發佈者生產的商品的類型:
public interface Publisher {
public void subscribe(Subscriber<? super T> s);
}

此接口定義要求我們隨後爲訂戶定義接口。如上所述,訂閱者具有四個主要交互:(1)訂閱的通知;(2)接受推送的項目;(3)接受在訂閱的發佈者中發生的錯誤;以及(4)發佈者完成時的通知。這將導致以下界面,該界面同樣由其請求的項目類型進行參數化:
public interface Subscriber {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}

接下來,我們必須定義訂閱的接口。該實體比訂戶更簡單,並且僅負責以下兩個動作:(1)接受物品請求;(2)被取消。這將導致以下接口定義:
public interface Subscription {
public void request(long n);
public void cancel();
}

最後,我們將處理器定義爲發佈者和訂閱者接口的組合,這有一個重要的怪癖:處理器產生的商品類型可能不同於其消費的商品類型。因此,我們將使用形式化的泛型類型參數T表示處理器消耗的項目類型,並R表示其返回(或產生)的項目類型。請注意,生產者的實現可以使用和生產相同類型的項目,但是沒有編譯時必須這樣做的限制。這將產生以下界面:
public interface Processor<T, R> extends Subscriber, Publisher {}

儘管這四個接口構成了反應流的統一合同,但這些接口還必須遵守許多其他限制和預期的行爲。這些規範以及上面的接口定義可以在Reactive Streams JVM Specification中找到。正如我們將在下一節中看到的那樣,Reactive Stream規範的標準Java實現與Reactive Streams JVM規範的實現幾乎相同,並且充當Java Standard Library中 Reactive Streams合同的標準化。
反應性流如何在Java中工作?
Reactive Streams接口的標準Java端口可在java.util.concurrent.Flow類中找到,並捆綁爲Flow該類中的靜態接口。刪除JavaDocs後,Flow該類的定義如下:
public final class Flow {
private Flow() {} // uninstantiable
@FunctionalInterface
public static interface Publisher {
public void subscribe(Subscriber<? super T> subscriber);
}
public static interface Subscriber {
public void onSubscribe(Subscription subscription);
public void onNext(T item);
public void onError(Throwable throwable);
public void onComplete();
}
public static interface Subscription {
public void request(long n);
public void cancel();
}
public static interface Processor<T,R> extends Subscriber, Publisher {}
}

將Reactive Streams JVM規範與標準Java定義進行比較時,沒有太多新的討論要討論,但是標準Java版本確實包含一個發佈者實現:SubmissionPublisher。的SubmissionPublisher類作爲一個簡單的發佈者,它接受項推到使用訂戶submit(T item)方法。將項目提交給submit方法後,它將異步推送給訂閱者,如以下示例所示:
public class PrintSubscriber implements Subscriber {
private Subscription subscription;
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(Integer item) {
System.out.println("Received item: " + item);
subscription.request(1);
}
@Override
public void onError(Throwable error) {
System.out.println("Error occurred: " + error.getMessage());
}
@Override
public void onComplete() {
System.out.println(“PrintSubscriber is complete”);
}
}
public class SubmissionPublisherExample {
public static void main(String… args) throws InterruptedException {
SubmissionPublisher publisher = new SubmissionPublisher<>();
publisher.subscribe(new PrintSubscriber());
System.out.println(“Submitting items…”);
for (int i = 0; i < 10; i++) {
publisher.submit(i);
}
Thread.sleep(1000);
publisher.close();
}
}

運行此示例將產生以下輸出:
Submitting items…
Received item: 0
Received item: 1
Received item: 2
Received item: 3
Received item: 4
Received item: 5
Received item: 6
Received item: 7
Received item: 8
Received item: 9
PrintSubscriber is complete

在我們的訂閱者中,我們捕獲Subscription傳遞給該onSubscribe方法的對象,從而允許我們Subscription 在以後與進行交互。一旦存儲了Subscription 對象,我們立即通知Subscription訂戶已準備好接受一項(通過調用subscription.request(1))。onNext在打印收到的項目之後,我們同樣會在方法中進行操作。這等於通知發佈者,我們在完成項目處理後就可以接受其他項目。
在我們的主要方法中,我們只需實例化a SubmissionPublisher 和our PrintSubscriber 並將後者訂閱到前者。一旦認購成立以來,我們提交的值0通過9到發佈,這反過來又推動異步的值給用戶。然後,訂戶通過將其值打印到標準輸出中來處理每個項目,並通知訂閱它已準備好接受另一個值。然後,我們將主線程暫停1秒鐘,以允許異步提交完成。這是非常重要的一步,因爲該submit 方法是異步的將提交的項目推送給其訂閱者。因此,我們必須提供一個合理的時間來完成異步操作。最後,我們關閉發佈者,這又通知我們的訂閱者訂閱已完成。
我們還可以引入處理器,並使用該處理器鏈接原始的發佈者和訂閱者。在下面的示例中,我們創建一個處理器,將接收到的值增加10,然後將增加的值推送到其訂戶:
public class PlusTenProcessor extends SubmissionPublisher implements Subscriber {
private Subscription subscription;
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
subscription.request(1);
}
@Override
public void onNext(Integer item) {
submit(item + 10);
subscription.request(1);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
closeExceptionally(error);
}
@Override
public void onComplete() {
System.out.println(“PlusTenProcessor completed”);
close();
}
}
public class SubmissionPublisherExample {
public static void main(String… args) throws InterruptedException {
SubmissionPublisher publisher = new SubmissionPublisher<>();
PlusTenProcessor processor = new PlusTenProcessor();
PrintSubscriber subscriber = new PrintSubscriber();
publisher.subscribe(processor);
processor.subscribe(subscriber);
System.out.println(“Submitting items…”);
for (int i = 0; i < 10; i++) {
publisher.submit(i);
}
Thread.sleep(1000);
publisher.close();
}
}

運行此示例將產生以下輸出:
Submitting items…
Received item: 10
Received item: 11
Received item: 12
Received item: 13
Received item: 14
Received item: 15
Received item: 16
Received item: 17
Received item: 18
Received item: 19
PlusTenProcessor completed
PrintSubscriber is complete

就像我們期望的那樣,每個推入的值都增加10,並且處理器接收到的事件(例如接收到錯誤或完成)被轉發給訂戶,從而導致爲PlusTenProcessor和發送了一條完成的消息PrintSubscriber。
結論
在近實時數據處理時代,反應流是事實上的標準。這種編程風格的普遍性導致了該標準的衆多實現,每個實現都有自己的重複接口集。爲了將這些通用接口收集到Java通用標準中,默認情況下,JDK 9現在包括Reactive Streams接口以及強大的發佈者實現。正如我們在本文中所看到的,雖然這些接口在外觀上並不張揚,但它們提供了一種豐富的方法來以標準的,可互換的方式處理流數據流。
最後,開發這麼多年我也總結了一套學習Java的資料與面試題,如果你在技術上面想提升自己的話,可以關注我,私信發送領取資料或者在評論區留下自己的聯繫方式,有時間記得幫我點下轉發讓跟多的人看到哦。在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章