知識庫 / Spring WebFlux RSS 訂閱

將單對象轉換為另一個單對象(Spring WebFlux)

Reactive,Spring WebFlux
HongKong
10
10:51 AM · Dec 06 ,2025

1. 簡介

Spring WebFlux 是一個反應式編程框架,它支持異步、非阻塞的通信。 使用 WebFlux 的關鍵在於處理 Mono 對象,該對象代表一個單一的異步結果。 在實際應用中,我們經常需要將一個 Mono 對象轉換為另一個 Mono 對象,無論是為了豐富數據、處理外部服務調用,還是重構報文負載。

在本次教程中,我們將探討如何使用 Project Reactor 提供的各種方法將 Mono 對象轉換為另一個 Mono 對象。

2. 轉換 個對象

在探討各種將 個對象轉換的方法之前,我們先設置我們的編碼示例。我們將使用圖書借閲示例貫穿整個教程,以演示不同的轉換方法。為了捕捉這個場景,我們將使用三個關鍵類。

User 類來表示圖書館用户:

public class User {
    private String userId;
    private String name;
    private String email;
    private boolean active;

    // standard setters and getters
}

每個用户都通過 userId 這一唯一標識符進行識別,並且擁有姓名和電子郵件等個人信息。此外,還有一個 active 標誌,用於指示用户當前是否可以借閲書籍。

Book 類用於表示圖書館的書籍收藏:

public class Book {
    private String bookId;
    private String title;
    private double price;
    private boolean available;

    //standard setters and getters
}

每個圖書都通過 bookId 進行標識,並具有諸如 titleprice 等屬性。 available 標誌指示圖書是否可以借閲。

BookBorrowResponse 類用於封裝借閲操作的結果:

public class BookBorrowResponse {
    private String userId;
    private String bookId;
    private String status;

    //standard setters and getters
}

本類將 userIdbookId 在流程中關聯起來,並提供一個 status 字段,用於指示借閲是否被接受或拒絕。

3. 隨行轉換與 <em >map()</em >>

<em >map()</em >> 運算符將同步函數應用於 <em >Mono</em >> 內的數據。它適用於諸如格式化、過濾或簡單計算等輕量級操作。例如,如果我們想要從 <em >Mono</em >> 中的用户對象獲取電子郵件地址,可以使用 <em >map()</em >> 進行轉換:

@Test
void givenUserId_whenTransformWithMap_thenGetEmail() {
    String userId = "U001";
    Mono<User> userMono = Mono.just(new User(userId, "John", "[email protected]"));
    Mockito.when(userService.getUser(userId))
      .thenReturn(userMono);

    Mono<String> userEmail = userService.getUser(userId)
      .map(User::getEmail);

    StepVerifier.create(userEmail)
      .expectNext("[email protected]")
      .verifyComplete();
}

4. 使用 <em flatMap()</em> 進行異步轉換

<em flatMap()</em> 方法將每個從 <em Mono</em> 發出的項目轉換為另一個 <em Publisher</em>。它尤其適用於需要啓動新的異步流程的轉換,例如調用另一個 API 或查詢數據庫。<em flatMap()</em> 在轉換結果是 <em Mono</em> 時,會將結果扁平化為一個單一序列。

讓我們來看一下我們的圖書借閲系統。當用户請求借閲圖書時,系統會驗證用户的會員狀態,然後檢查圖書是否可用。如果這兩個檢查都通過,系統將處理借閲請求並返回一個 <em BookBorrowResponse</em>

public Mono<BookBorrowResponse> borrowBook(String userId, String bookId) {
    return userService.getUser(userId)
      .flatMap(user -> {
          if (!user.isActive()) {
              return Mono.error(new RuntimeException("User is not an active member"));
          }
          return bookService.getBook(bookId);
      })
      .flatMap(book -> {
          if (!book.isAvailable()) {
              return Mono.error(new RuntimeException("Book is not available"));
          }
          return Mono.just(new BookBorrowResponse(userId, bookId, "Accepted"));
      });
}

在本示例中,如檢索用户信息和圖書詳情等操作,都是異步的,並返回 Mono 對象。 通過使用 flatMap(), 我們可以以可讀且邏輯的方式鏈接這些操作,而無需嵌套多個級別的 Mono 序列中的每個步驟都依賴於前一個步驟的結果。 例如,圖書可用性僅在用户處於活動狀態時才進行檢查。 flatMap() 確保我們可以在保持流的反應式特性不變的情況下,動態地做出這些決策。

5. 使用 transform() 方法實現可複用邏輯

transform() 方法是一個多功能的工具,允許我們封裝可複用的邏輯。與其在應用程序的多個部分中重複執行轉換,不如一次定義它們並根據需要隨時應用。 這促進了代碼重用、分層架構和可讀性。

讓我們來看一個例子,其中我們需要在應用税費和折扣後返回書籍的最終價格:

public Mono<Book> applyDiscount(Mono<Book> bookMono) {
    return bookMono.map(book -> {
        book.setPrice(book.getPrice() - book.getPrice() * 0.2);
        return book;
    });
}

public Mono<Book> applyTax(Mono<Book> bookMono) {
    return bookMono.map(book -> {
        book.setPrice(book.getPrice() + book.getPrice() * 0.1);
        return book;
    });
}

public Mono<Book> getFinalPricedBook(String bookId) {
    return bookService.getBook(bookId)
      .transform(this::applyTax)
      .transform(this::applyDiscount);
}

在本示例中,applyDiscount()方法應用20%的折扣,applyTax()方法則應用10%的税費。transform方法將這兩個方法應用於流水線,並返回一個包含最終價格的Mono對象,類型為Book

6. 從多個來源合併數據

zip() 方法將多個 Mono 對象組合起來,產生一個單一結果。 它不進行併發合併,而是等待所有 Mono 對象發出數據後才應用組合器函數。

我們再次回顧我們的圖書借閲示例,其中我們獲取用户信息和圖書信息以創建 BookBorrowResponse

public Mono<BookBorrowResponse> borrowBookZip(String userId, String bookId) {
    Mono userMono = userService.getUser(userId)
      .switchIfEmpty(Mono.error(new RuntimeException("User not found")));
    Mono bookMono = bookService.getBook(bookId)
      .switchIfEmpty(Mono.error(new RuntimeException("Book not found")));
    return Mono.zip(userMono, bookMono,
      (user, book) -> new BookBorrowResponse(userId, bookId, "Accepted"));
}

在本實現中,zip() 方法確保在創建響應之前,用户信息和圖書信息可用。如果用户或圖書檢索失敗(例如,如果用户不存在或圖書不可用),則錯誤將傳播,並導致 Mono 對象以適當的錯誤信號終止。

7. 條件轉換

通過結合 <em >filter()</em ><em >switchIfEmpty()</em >> 方法,我們可以為 <em >Mono</em >> 對象應用條件邏輯,根據謂詞進行轉換。如果謂詞為真,則返回原始的 <em >Mono</em >> 對象,否則 <em >switchIfEmpty()</em >> 方法會將其切換到由其提供的一個不同 <em >Mono</em >> 對象,反之亦然。

讓我們考慮一個場景,即如果用户處於活動狀態,則僅應用折扣,否則不提供折扣:

public Mono<Book> conditionalDiscount(String userId, String bookId) {
    return userService.getUser(userId)
      .filter(User::isActive)
      .flatMap(user -> bookService.getBook(bookId).transform(this::applyDiscount))
      .switchIfEmpty(bookService.getBook(bookId))
      .switchIfEmpty(Mono.error(new RuntimeException("Book not found")));
}

在本示例中,我們使用 Mono 類型的 User 對象,通過 userId 進行獲取。 過濾方法檢查用户是否處於活動狀態。 如果用户處於活動狀態,我們將應用折扣後返回一個 Mono 類型的 Book 對象。 如果用户處於非活動狀態,Mono 將變為空,switchIfEmpty() 方法啓動,用於在不應用折扣的情況下獲取書籍。 最終,如果書籍本身不存在,另一個 switchIfEmpty() 確保適當的錯誤被傳播,使整個流程具有魯棒性和直觀性。

8. 轉換過程中的錯誤處理

錯誤處理確保轉換過程中的彈性,允許採用優雅的降級機制或替代數據源。當轉換失敗時,適當的錯誤處理有助於優雅地恢復、記錄問題或返回替代數據。

onErrorResume() 方法用於通過提供替代的 <em Mono</em> 來從錯誤中恢復。這在我們需要提供默認數據或從替代源獲取數據時尤其有用。

讓我們重新審視我們的圖書借閲示例:如果在獲取 <em User</em><em Book</em> 對象時發生任何錯誤,我們通過返回具有“已拒絕”狀態的 <em BookBorrowResponse</em> 對象來優雅地處理失敗:

public Mono<BookBorrowResponse> handleErrorBookBorrow(String userId, String bookId) {
    return borrowBook(userId, bookId)
      .onErrorResume(ex -> Mono.just(new BookBorrowResponse(userId, bookId, "Rejected")));
}

這種錯誤處理策略確保,即使在故障場景下,系統也能以可預測的方式響應,並保持無縫的用户體驗。

9. 轉換單對象最佳實踐

在轉換 Mono 對象時,遵循一些最佳實踐對於確保我們的反應式管道乾淨、高效和易於維護至關重要。當我們需要簡單的同步轉換,例如豐富或修改數據時,map() 方法是理想的選擇,而 flatMap() 則適用於涉及異步工作流的任務,例如調用外部 API 或查詢數據庫。為了保持管道的清潔和可重用性,我們使用 transform() 方法封裝邏輯,從而促進模塊化和分層設計。為了保持可讀性,我們應優先使用鏈式操作而非嵌套操作。

錯誤處理在確保健壯性方面發揮着關鍵作用。通過使用諸如 onErrorResume() 等方法,我們可以優雅地處理錯誤,通過提供備用響應或替代數據源。最後,在每個階段驗證輸入和輸出有助於防止問題向下傳播,從而確保管道的健壯性和可擴展性。

10. 結論

在本教程中,我們學習瞭如何將一個 <em >Mono</em> 對象轉換為另一個對象的各種方法。 重要的是要理解正確的運算符,無論它是否為 <em >map()</em><em >flatMap()</em><em >transform()</em>。 通過使用這些技術並遵循最佳實踐,我們可以構建在 Spring WebFlux 中靈活且易於維護的反應式流水線。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.