反應式程式設計中的 Mono just() vs defer() vs create()
一、簡介
Mono是反應式程式設計的核心概念,特別是在 Project Reactor 中。它表示最多發出一項然後成功完成或出現錯誤的流。 Mono用於傳回單一結果或根本不傳回結果的非同步操作。
Mono對於表示非同步計算的結果特別有用,例如資料庫查詢、HTTP 請求或傳回單一值或完成而不發出任何值的任何操作。
在本文中,我們將探討創建Mono三種常見方法之間的差異: just() 、 defer()和create() 。
2. Mono.just()
Mono.just()方法是建立Mono最簡單的方法。它採用現有值並將其包裝在Mono中,有效地使其立即可供訂閱者使用。當資料可用且不需要複雜的計算或延遲執行時,我們使用此方法。
當我們使用Mono.just()時,該值會在Mono創建時計算並存儲,而不是等到它被訂閱。一旦創建,該值就無法更改,即使在訂閱之前修改了基礎資料來源。這確保了訂閱期間發出的值始終是Mono建立時可用的值。
讓我們透過一個例子來看看這種行為是如何運作的:
@Test
void whenUsingMonoJust_thenValueIsCreatedEagerly() {
String[] value = {"Hello"};
Mono<String> mono = Mono.just(value[0]);
value[0] = "world";
mono.subscribe(actualValue -> assertEquals("Hello", actualValue));
}
在這個例子中,我們可以看到Mono.just()在Mono創建時就急切地創建了值。 Mono使用數組中的值“Hello”進行初始化,即使在訂閱Mono之前將數組元素修改為“world” ,發出的值仍然是“Hello” 。
讓我們探討Mono.just()的幾個常見用例:
- 當我們有一個已知的、準備要發出的靜態值時
- 它非常適合不涉及任何計算或副作用的簡單用例
3. Mono.defer()
Mono.defer()啟用延遲執行,這表示Mono在訂閱之前不會建立。當我們想要避免不必要的資源分配或計算直到真正需要Mono時,這特別有用。
讓我們檢查一個範例來驗證Mono是在訂閱時創建的,而不是在最初定義時創建的:
@Test
void whenUsingMonoDefer_thenValueIsCreatedLazily() {
String[] value = {"Hello"};
Mono<String> mono = Mono.defer(() -> Mono.just(value[0]));
value[0] = "World";
mono.subscribe(actualValue -> assertEquals("World", actualValue));
}
在這個例子中,我們可以看到Mono創建後數組中的值發生了變化。由於Mono.defer()延遲創建Mono ,因此它不會在創建時捕獲值,而是等待Mono被訂閱。因此,當我們訂閱時,我們收到更新後的值“World” ,而不是原始值“Hello” ,這表示Mono.defer()將評估推遲到訂閱時。
Mono.defer()在我們需要為每個訂閱者建立一個新的、不同的Mono實例的情況下特別有用。這使我們能夠產生一個單獨的實例,反映訂閱時的當前狀態或資料。當我們需要根據訂閱之間可能發生變化的條件動態導出值時,這種方法至關重要。
讓我們來看看根據方法參數建立延遲Mono一種場景:
public Mono<String> getGreetingMono(String name) {
return Mono.defer(() -> {
String greeting = "Hello, " + name;
return Mono.just(greeting);
});
}
這裡, Mono.defer()方法使用傳遞給getGreetingMono()方法的名稱將 Mono 的建立推遲到訂閱為止:
@Test
void givenNameIsAlice_whenMonoSubscribed_thenShouldReturnGreetingForAlice() {
Mono<String> mono = generator.getGreetingMono("Alice");
StepVerifier.create(mono)
.expectNext("Hello, Alice")
.verifyComplete();
}
@Test
void givenNameIsBob_whenMonoSubscribed_thenShouldReturnGreetingForBob() {
Mono<String> mono = generator.getGreetingMono("Bob");
StepVerifier.create(mono)
.expectNext("Hello, Bob")
.verifyComplete();
}
當使用參數“Alice”呼叫時,該方法會產生一個發出“Hello, Alice”的 Mono。類似地,當用“Bob”呼叫時,它會產生一個發出“Hello, Bob”的 Mono。
讓我們看看Mono.defer()的一些常見用例:
- 當我們的
Mono創建涉及資料庫查詢或網路呼叫等昂貴的操作時,使用defer(),我們可以阻止這些操作被執行,除非確實需要這個結果 - 它對於動態生成值或計算取決於外部因素或用戶輸入時非常有用
Mono.create()
Mono.create()是最靈活、最強大的方法,使我們能夠完全控制Mono的發射過程。它允許我們根據自訂邏輯以程式設計方式產生值、訊號和錯誤。
關鍵特性是它提供了MonoSink 。我們可以使用接收器透過sink.success()發出一個值。如果發生錯誤,我們使用sink.error() 。要傳送不含值的完成訊號,我們呼叫不含參數的sink.success() 。
讓我們探討一個執行外部操作的範例,例如查詢遠端服務。在這種情況下,我們將使用Mono.create()來封裝處理回應的邏輯。根據服務呼叫是成功還是導致錯誤,我們將向訂閱者發出有效值或錯誤訊號:
public Mono<String> performOperation(boolean success) {
return Mono.create(sink -> {
if (success) {
sink.success("Operation Success");
} else {
sink.error(new RuntimeException("Operation Failed"));
}
});
}
讓我們驗證一下這個Mono在操作成功時是否發出成功訊息:
@Test
void givenSuccessScenario_whenMonoSubscribed_thenShouldReturnSuccessValue() {
Mono<String> mono = generator.performOperation(true);
StepVerifier.create(mono)
.expectNext("Operation Success")
.verifyComplete();
}
當操作失敗時, Mono會發出錯誤並顯示訊息「 Operation Failed 」:
@Test
void givenErrorScenario_whenMonoSubscribed_thenShouldReturnError() {
Mono<String> mono = generator.performOperation(false);
StepVerifier.create(mono)
.expectErrorMatches(throwable -> throwable instanceof RuntimeException
&& throwable.getMessage()
.equals("Operation Failed"))
.verify();
}
讓我們來探討一下Mono.create()的一些常見場景:
- 當我們需要實作複雜的邏輯來發出值、處理錯誤或管理背壓時,此方法提供了必要的彈性
- 它非常適合與遺留系統整合或執行需要對發射和完成進行細粒度控制的複雜非同步任務
5.主要差異
現在我們已經分別探討了這三種方法,讓我們總結一下它們的主要差異:
| 特徵 | **Mono.just()** |
**Mono.defer()** |
**Mono.create()** |
|---|---|---|---|
| 執行時間 | 渴望(創建時) | 懶惰(訂閱後) | 懶惰(手動發射) |
| 價值狀態 | |||
| 靜態/預定義值 |
| 每個訂閱的動態價值 | 手動產生的價值 |
| 使用案例 | 當數據可用且不發生變化時 |
當需要按需產生資料時
| 與複雜邏輯或外部來源整合時 |
| 錯誤處理 | 無錯誤處理 |
可以處理Mono創建期間的錯誤
|
對成功和錯誤的明確控制
|
| 表現 |
對於靜態、已知的值有效
|
對於動態或昂貴的操作很有用
|
適合複雜或非同步程式碼
|
六、結論
在本教程中,我們研究了創建Mono的三種方法,包括每種方法最適合的場景。
了解Mono.just() 、 Mono.defer()和Mono.create()之間的差異是在 Java 中有效使用響應式程式設計的關鍵。透過選擇正確的方法,我們可以使反應式程式碼更有效率、可維護並適合我們的特定用例。
與往常一樣,本文的源代碼可在 GitHub 上取得。