知識庫 / Reactive RSS 訂閱

Spring WebClient exchange() 與 retrieve() 區別詳解

Reactive,Spring Web
HongKong
12
11:14 AM · Dec 06 ,2025

1. 概述

WebClient 是一個簡化執行 HTTP 請求過程的接口。與 RestTemplate 不同,它是一個反應式且非阻塞的客户端,可以消費和操作 HTTP 響應。雖然它被設計為非阻塞的,但也可以在阻塞場景中使用。

在本教程中,我們將深入研究 WebClient 接口中的關鍵方法,包括 retrieve()exchangeToMono()exchangeToFlux()。我們還將探討這些方法之間的差異和相似之處,並提供示例以展示不同的用例。此外,我們還將使用 JSONPlaceholder API 獲取用户數據。

2. 示例配置

首先,讓我們創建一個 Spring Boot 應用程序,並添加 spring-boot-starter-webflux 依賴項到 pom.xml 中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <version>3.2.4</version>
</dependency>

這個依賴項提供WebClient接口,使我們能夠執行HTTP請求

此外,讓我們來看一個從請求https://jsonplaceholder.typicode.com/users/1返回的示例GET響應:

{
  "id": 1,
  "name": "Leanne Graham",
// ...
}

此外,讓我們創建一個名為 User 的 POJO 類:

class User {

    private int id;
    private String name;

   // standard constructor,getter, and setter

}

來自 JSONPlaceholder API 的 JSON 響應將被反序列化並映射到 User 類的一個實例。

最後,我們創建一個 WebClient 實例,並設置基本 URL:

WebClient client = WebClient.create("https://jsonplaceholder.typicode.com/users");

這裏,我們定義了 HTTP 請求的基URL。

3. exchange() 方法

exchange() 方法直接返回 ClientResponse 對象,從而提供對 HTTP 狀態碼、頭部和響應體訪問。 簡單來説,ClientResponse 代表由 WebClient 返回的 HTTP 響應。

然而,自 Spring 5.3 版本起,此方法已被棄用,並被 exchangeToMono()exchangeToFlux() 方法取代,具體取決於我們發出的內容。這兩個方法允許我們根據響應狀態解碼響應。

3.1. 發射 Mono 對象

下面是一個使用 exchangeToMono() 方法發射 Mono 對象的示例:

@GetMapping("/user/exchange-mono/{id}")
Mono<User> retrieveUsersWithExchangeAndError(@PathVariable int id) {
    return client.get()
      .uri("/{id}", id)
      .exchangeToMono(res -> {
          if (res.statusCode().is2xxSuccessful()) {
              return res.bodyToMono(User.class);
          } else if (res.statusCode().is4xxClientError()) {
              return Mono.error(new RuntimeException("Client Error: can't fetch user"));
          } else if (res.statusCode().is5xxServerError()) {
              return Mono.error(new RuntimeException("Server Error: can't fetch user"));
          } else {
              return res.createError();
           }
     });
}

在上述代碼中,我們檢索用户並根據 HTTP 狀態碼解碼響應。

3.2. 發出 Flux 信號

此外,我們使用 exchangeToFlux() 方法來獲取用户集合:

@GetMapping("/user-exchange-flux")
Flux<User> retrieveUsersWithExchange() {
   return client.get()
     .exchangeToFlux(res -> {
         if (res.statusCode().is2xxSuccessful()) {
             return res.bodyToFlux(User.class);
         } else {
             return Flux.error(new RuntimeException("Error while fetching users"));
         }
    });
}

在這裏,我們使用 exchangeToFlux() 方法將響應體映射為 FluxUser 對象,並在請求失敗時返回自定義錯誤消息。

3.3. 直接獲取響應體

值得注意的是,exchangeToMono()exchangeToFlux() 可以無需指定響應狀態碼進行使用:

@GetMapping("/user-exchange")
Flux<User> retrieveAllUserWithExchange(@PathVariable int id) {
    return client.get().exchangeToFlux(res -> res.bodyToFlux(User.class))
      .onErrorResume(Flux::error);
}

在這裏,我們檢索用户,不指定狀態碼。

3.4. 修改響應體

此外,我們來看一個修改響應體的示例:

@GetMapping("/user/exchange-alter/{id}")
Mono<User> retrieveOneUserWithExchange(@PathVariable int id) {
    return client.get()
      .uri("/{id}", id)
      .exchangeToMono(res -> res.bodyToMono(User.class))
      .map(user -> {
          user.setName(user.getName().toUpperCase());
          user.setId(user.getId() + 100);
          return user;
      });
}

在上述代碼中,在將響應體映射到POJO類後,我們通過在id中添加100和將name轉換為大寫來修改響應體。

特別地,我們還可以使用retrieve()方法來修改響應體。

3.5. 提取響應頭信息

此外,我們還可以提取響應頭信息:

@GetMapping("/user/exchange-header/{id}")
Mono<User> retrieveUsersWithExchangeAndHeader(@PathVariable int id) {
  return client.get()
    .uri("/{id}", id)
    .exchangeToMono(res -> {
        if (res.statusCode().is2xxSuccessful()) {
            logger.info("Status code: " + res.headers().asHttpHeaders());
            logger.info("Content-type" + res.headers().contentType());
            return res.bodyToMono(User.class);
        } else if (res.statusCode().is4xxClientError()) {
            return Mono.error(new RuntimeException("Client Error: can't fetch user"));
        } else if (res.statusCode().is5xxServerError()) {
            return Mono.error(new RuntimeException("Server Error: can't fetch user"));
        } else {
            return res.createError();
        }
    });
}

在這裏,我們記錄 HTTP 頭部和內容類型到控制枱。 與需要返回 ResponseEntity才能訪問頭部和響應碼的 retrieve()方法不同,exchangeToMono()由於它返回 ClientResponse,因此我們能夠直接訪問。

4. retrieve() 方法

retrieve() 方法簡化了從 HTTP 請求中提取響應體。它返回 ResponseSpec,允許我們指定響應體應如何處理,而無需訪問完整的 ClientResponse

ClientResponse 包含響應代碼、頭信息和響應體。因此,ResponseSpec 包含響應體,但不包含響應代碼和頭信息。

4.1. 發出 單聲道 數據包

以下是一個用於檢索 HTTP 響應體示例代碼:

@GetMapping("/user/{id}")
Mono<User> retrieveOneUser(@PathVariable int id) {
    return client.get()
      .uri("/{id}", id)
      .retrieve()
      .bodyToMono(User.class)
      .onErrorResume(Mono::error);
}

在上述代碼中,我們通過向 /users 終點進行 HTTP 調用,並指定一個 id,從基礎 URL 獲取 JSON 數據。然後,我們將響應體映射到 User 對象。

4.2. 發送 Flux</em/> 請求

此外,讓我們來看一個通過向 /users</em/> 終點發送 GET 請求的示例:

@GetMapping("/users")
Flux<User> retrieveAllUsers() {
    return client.get()
      .retrieve()
      .bodyToFlux(User.class)
      .onResumeError(Flux::error);
}

在此,該方法在將 HTTP 響應映射到 POJO 類時,會發出 Flux 類型的 User 對象流。

4.3. 返回 ResponseEntity

當我們使用 retrieve() 方法來訪問響應狀態和頭部時,我們可以返回 ResponseEntity

@GetMapping("/user-id/{id}")
Mono<ResponseEntity<User>> retrieveOneUserWithResponseEntity(@PathVariable int id) {
    return client.get()
      .uri("/{id}", id)
      .accept(MediaType.APPLICATION_JSON)
      .retrieve()
      .toEntity(User.class)
      .onErrorResume(Mono::error);
}

使用 toEntity()方法獲得的響應包含 HTTP 標頭、狀態碼和響應正文。

4.4. 自定義錯誤使用 <em onStatus()> 處理程序

此外,當發生 400 或 500 HTTP 錯誤時,默認返回 異常。但是,我們可以使用 處理程序自定義異常,從而提供自定義錯誤響應。

@GetMapping("/user-status/{id}")
Mono<User> retrieveOneUserAndHandleErrorBasedOnStatus(@PathVariable int id) {
    return client.get()
      .uri("/{id}", id)
      .retrieve()
      .onStatus(HttpStatusCode::is4xxClientError, 
        response -> Mono.error(new RuntimeException("Client Error: can't fetch user")))
      .onStatus(HttpStatusCode::is5xxServerError, 
        response -> Mono.error(new RuntimeException("Server Error: can't fetch user")))
      .bodyToMono(User.class);
}

在這裏,我們檢查HTTP狀態碼,並使用onStatus()處理程序來定義自定義錯誤響應。

5. 性能比較

接下來,我們編寫一個性能測試,以使用 Java Microbench Harness (JMH) 比較 retrieve() exchangeToFlux() 的執行時間。

首先,讓我們創建一個名為 RetrieveAndExchangeBenchmarkTest 的類:

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 10, timeUnit = TimeUnit.MICROSECONDS)
@Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.MICROSECONDS)
public class RetrieveAndExchangeBenchmarkTest {
  
    private WebClient client;

    @Setup
    public void setup() {
        this.client = WebClient.create("https://jsonplaceholder.typicode.com/users");
    }
}

在這裏,我們設置基準模式為 AverageTime,這意味着它會測量測試執行的平均時間。此外,我們還定義了迭代次數和每個迭代的時間。

接下來,我們創建了一個 WebClient 的實例,並使用 @Setup 註解使其在每次基準測試之前運行。

讓我們編寫一個基準方法,使用 retrieve() 方法檢索用户集合:

@Benchmark
Flux<User> retrieveManyUserUsingRetrieveMethod() {
    return client.get()
      .retrieve()
      .bodyToFlux(User.class)
      .onErrorResume(Flux::error);;
}

最後,讓我們定義一個方法,該方法使用 exchangeToFlux() 方法,以 Flux 形式發出 User 對象:

@Benchmark
Flux<User> retrieveManyUserUsingExchangeToFlux() {
    return client.get()
      .exchangeToFlux(res -> res.bodyToFlux(User.class))
      .onErrorResume(Flux::error);
}

以下是基準測試結果:

Benchmark                             Mode  Cnt   Score    Error  Units
retrieveManyUserUsingExchangeToFlux   avgt   15  ≈ 10⁻⁴            s/op
retrieveManyUserUsingRetrieveMethod   avgt   15  ≈ 10⁻³            s/op

兩種方法都展示了高效的性能。但是,exchangeToFlux()在檢索用户集合時,比 retrieve() 方法略快。

6. 關鍵差異與相似之處

retrieve()exchangeToMono()exchangeToFlux() 都可以用於發起 HTTP 請求並提取 HTTP 響應。

retrieve() 方法僅允許我們消費 HTTP 響應體併發出 MonoFlux,因為它返回 ResponseSpec 但是,如果我們想訪問狀態碼和頭部信息,可以使用 retrieve() 方法與 ResponseEntity 一起使用。 此外,它還允許我們根據 HTTP 狀態碼使用 onStatus() 處理程序報告錯誤。

retrieve() 方法不同,exchangeToMono()exchnageToFlux() 允許我們消費 HTTP 響應並直接訪問頭部信息和響應碼,因為它們返回 ClientResponse 此外,它們還提供了更精細的錯誤處理控制,因為我們可以根據 HTTP 狀態碼解碼響應。

值得注意的是,如果意圖是僅消費響應體,則建議使用 retrieve() 方法。

但是,如果我們需要對響應進行更精細的控制,exchangeToMono()exchangeToFlux() 可能是更好的選擇。

7. 結論

在本文中,我們學習瞭如何使用 retrieve()exchangeToMono()exchangeToFlux() 方法來處理 HTTP 響應,並將其映射到 POJO 類。此外,我們還比較了 retrieve()exchangeToFlux() 方法的性能。

retrieve() 方法適用於我們只需要消費響應主體,而不需要訪問狀態碼或標頭的情況。它通過返回 ResponseSpec,簡化了處理響應體過程。

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

發佈 評論

Some HTML is okay.