知識庫 / HTTP Client-Side RSS 訂閱

Spring WebClient

HTTP Client-Side,Spring Web
HongKong
11
02:24 PM · Dec 06 ,2025

1. 概述

在本教程中,我們將研究 WebClient,這是一個在 Spring 5 中引入的反應式 Web 客户端。

我們還將探討 WebTestClient,這是一個專為測試設計的 WebClient

2. 什麼是 WebClient ?

簡單來説,WebClient 是執行 Web 請求的主要入口點表示的接口。

它是在 Spring Web Reactive 模塊中創建的,並將會在這些場景中取代經典的 RestTemplate。 此外,新的客户端是一個反應式、非阻塞的解決方案,它在 HTTP/1.1 協議上工作。

需要注意的是,儘管它是非阻塞客户端,並且屬於 spring-webflux 庫,但該解決方案支持同步和異步操作,因此也適用於在 Servlet 堆棧上運行的應用程序。

可以通過阻塞操作來獲取結果來實現這一點。 當然,如果我們在反應式堆棧上工作,則不建議採用這種做法。

最後,該接口只有一個實現,即 DefaultWebClient 類,我們將使用它。

3. 依賴項

由於我們正在使用 Spring Boot 應用程序,我們只需要 spring-boot-starter-webflux 依賴項,即可獲得 Spring Framework 的 Reactive Web 支持。

3.1. 使用 Maven 構建

讓我們將以下依賴項添加到 pom.xml 文件中:

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

3.2. 使用 Gradle 構建

使用 Gradle 時,我們需要在 build.gradle 文件中添加以下條目:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

4. 使用 WebClient

為了正確地使用客户端,我們需要知道如何:

  • 創建實例
  • 發起請求
  • 處理響應

4.1. 創建 WebClient 實例

有三種選擇可供您選擇。第一種方法是使用默認設置創建 WebClient 對象:

WebClient client = WebClient.create();

第二種選項是使用給定的基礎 URI 初始化一個 WebClient 實例:

WebClient client = WebClient.create("http://localhost:8080");

第三種選項(也是最先進的一種)是使用 DefaultWebClientBuilder 類構建客户端,它允許完全自定義:

WebClient client = WebClient.builder()
  .baseUrl("http://localhost:8080")
  .defaultCookie("cookieKey", "cookieValue")
  .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
  .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
  .build();

4.2. 創建帶有超時時間的 WebClient 實例

通常情況下,默認的 HTTP 超時時間(30 秒)對於我們的需求來説過於緩慢。為了自定義此行為,我們可以創建 HttpClient 實例並配置 WebClient 使用它。

我們可以:

  • 通過 ChannelOption.CONNECT_TIMEOUT_MILLIS 選項設置連接超時時間
  • 分別使用 ReadTimeoutHandlerWriteTimeoutHandler 設置讀取和寫入超時時間
  • 使用 responseTimeout 指令配置響應超時時間

正如我們所説,所有這些都需要在我們將要配置的 HttpClient 實例中指定。

HttpClient httpClient = HttpClient.create()
  .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
  .responseTimeout(Duration.ofMillis(5000))
  .doOnConnected(conn -> 
    conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
      .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));

WebClient client = WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(httpClient))
  .build();

請注意,雖然我們也可以在客户端請求上調用 timeout,但這只是一個信號超時,而不是 HTTP 連接超時、讀/寫超時或響應超時;它是一個 Mono/Flux publisher 的超時。

4.3. 準備請求 – 定義方法

首先,我們需要通過調用 <imethod(HttpMethod method)> 來指定請求的 HTTP 方法。

UriSpec<RequestBodySpec> uriSpec = client.method(HttpMethod.POST);

或者通過調用其快捷方法,例如 getpostdelete

UriSpec<RequestBodySpec> uriSpec = client.post();

注意:雖然看起來我們重用請求規範變量(WebClient.UriSpecWebClient.RequestBodySpecWebClient.RequestHeadersSpecWebClient.ResponseSpec),但這只是為了簡化演示不同的方法。這些指令不應在不同的請求中使用,它們獲取引用,因此後續操作會修改我們在先前步驟中定義的定義。

4.4. 準備請求 – 定義 URL

下一步是提供 URL。 再次強調,有多種方式可以做到這一點。

我們可以將其傳遞給 uri API 作為 String

RequestBodySpec bodySpec = uriSpec.uri("/resource");

使用 UriBuilder 函數

RequestBodySpec bodySpec = uriSpec.uri(
  uriBuilder -> uriBuilder.pathSegment("/resource").build());

或者,作為一個 java.net.URL 實例:

RequestBodySpec bodySpec = uriSpec.uri(URI.create("/resource"));

請注意,如果為 WebClient 定義了默認基本 URL,則此方法將覆蓋該值。

4.5. 準備請求 – 定義請求體

然後我們可以設置請求體、內容類型、長度、Cookie 或 Header,如果需要的話。

例如,如果我們想設置請求體,有幾種可用的方法。 最常見和最直接的選項通常是使用 bodyValue 方法:

RequestHeadersSpec<?> headersSpec = bodySpec.bodyValue("data");

通過向 body 方法呈現 Publisher (以及將要發佈的元素的類型)來完成。

RequestHeadersSpec<?> headersSpec = bodySpec.body(
  Mono.just(new Foo("name")), Foo.class);

當然,以下是翻譯後的內容:

或者,我們還可以利用 BodyInserters 實用類。例如,讓我們看看如何使用簡單的對象填充請求體,就像我們使用 bodyValue 方法時所做的那樣:

RequestHeadersSpec<?> headersSpec = bodySpec.body(
  BodyInserters.fromValue("data"));

同樣,如果我們在使用 Reactor 實例時,可以使用 BodyInserters#fromPublisher 方法。

RequestHeadersSpec headersSpec = bodySpec.body(
  BodyInserters.fromPublisher(Mono.just("data")),
  String.class);

此類還提供其他直觀函數,以覆蓋更高級的場景。例如,如果需要發送多部分請求:

LinkedMultiValueMap map = new LinkedMultiValueMap();
map.add("key1", "value1");
map.add("key2", "value2");
RequestHeadersSpec<?> headersSpec = bodySpec.body(
  BodyInserters.fromMultipartData(map));

所有這些方法都會創建一個 BodyInserter 實例,然後我們可以將其呈現為請求的 主體

BodyInserter 是一個負責將給定的輸出消息和在插入過程中使用的上下文填充到 ReactiveHttpOutputMessage 主體中的接口。

一個 Publisher 是一個負責提供可能具有無限數量的序列化元素的反應式組件。它也是一個接口,最流行的實現是 MonoFlux

4.6. 準備請求 – 定義頭部

在設置請求體之後,我們可以設置頭部、Cookie 以及可接受的媒體類型。在實例化客户端時,已設置的值將被添加到其中。

此外,對於最常用的頭部,例如 “If-None-Match”, “If-Modified-Since”, “Accept”,“Accept-Charset”. 提供了支持。

以下是一個這些值如何使用的示例。

ResponseSpec responseSpec = headersSpec.header(
    HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
  .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
  .acceptCharset(StandardCharsets.UTF_8)
  .ifNoneMatch("*")
  .ifModifiedSince(ZonedDateTime.now())
  .retrieve();

4.7. 獲取響應

最終階段是發送請求並接收響應。我們可以通過使用 exchangeToMono/exchangeToFluxretrieve 方法來實現。

exchangeToMonoexchangeToFlux 方法允許訪問 ClientResponse 及其狀態和頭部信息:

Mono<String> response = headersSpec.exchangeToMono(response -> {
  if (response.statusCode().equals(HttpStatus.OK)) {
      return response.bodyToMono(String.class);
  } else if (response.statusCode().is4xxClientError()) {
      return Mono.just("Error response");
  } else {
      return response.createException()
        .flatMap(Mono::error);
  }
});

雖然 retrieve 方法是直接獲取內容的最簡短路徑:

Mono<String> response = headersSpec.retrieve()
  .bodyToMono(String.class);

務必注意 ResponseSpecbodyToMono 方法會在狀態碼為 4xx (客户端錯誤) 或 5xx (服務器錯誤) 時拋出 WebClientException

5. 使用 WebTestClient

WebTestClient 是測試 WebFlux 服務器端點的主要入口。它的 API 與 WebClient 極其相似,並且它將大部分工作委託給一個內部的 WebClient 實例,主要提供測試上下文。DefaultWebTestClient 類是一個單一接口實現。

客户端可以綁定到真實的服務器,也可以與特定的控制器或函數一起工作。

5.1. 與服務器綁定

要完成使用實際請求與正在運行的服務器進行端到端集成測試,可以使用 bindToServer 方法:

WebTestClient testClient = WebTestClient
  .bindToServer()
  .baseUrl("http://localhost:8080")
  .build();

5.2. 綁定到路由器

我們可以通過將 RouterFunction 傳遞給 bindToRouterFunction 方法來測試特定的路由器函數:

RouterFunction function = RouterFunctions.route(
  RequestPredicates.GET("/resource"),
  request -> ServerResponse.ok().build()
);

WebTestClient
  .bindToRouterFunction(function)
  .build().get().uri("/resource")
  .exchange()
  .expectStatus().isOk()
  .expectBody().isEmpty();

5.3. 綁定到 Web 處理程序

可以使用 bindToWebHandler 方法來實現相同的功能,該方法接受一個 WebHandler 實例:

WebHandler handler = exchange -> Mono.empty();
WebTestClient.bindToWebHandler(handler).build();

5.4. 綁定到應用程序上下文

當使用 bindToApplicationContext 方法時,會發生更復雜的情況。該方法接受一個 ApplicationContext,並分析上下文中的控制器 Bean 和 @EnableWebFlux 配置。

如果注入一個 ApplicationContext 實例,簡單的代碼片段可能如下所示:

@Autowired
private ApplicationContext context;

WebTestClient testClient = WebTestClient.bindToApplicationContext(context)
  .build();

5.5. 綁定到控制器

一種更簡便的方法是提供一個數組,用於通過 bindToController 方法進行控制器的測試。 假設我們已經擁有一個 Controller 類,並且將其注入到需要的類中,我們可以這樣編寫:

@Autowired
private Controller controller;

WebTestClient testClient = WebTestClient.bindToController(controller).build();

5.6. 發送請求

在構建 <i >WebTestClient</i> 對象後,鏈式操作將類似於 <i >WebClient</i>,直到 <i >exchange</i> 方法(一種獲取響應的方式),該方法提供 <i >WebTestClient.ResponseSpec</i> 接口,用於使用諸如 <i >expectStatus</i>, <i >expectBody</i>, 和 <i >expectHeader</i> 等有用的方法:

WebTestClient
  .bindToServer()
    .baseUrl("http://localhost:8080")
    .build()
    .post()
    .uri("/resource")
  .exchange()
    .expectStatus().isCreated()
    .expectHeader().valueEquals("Content-Type", "application/json")
    .expectBody().jsonPath("field").isEqualTo("value");

6. 結論

在本文中,我們探討了 WebClient,這是一種增強的 Spring 機制,用於在客户端進行請求。

我們還考察了它提供的優勢,通過配置客户端、準備請求和處理響應來完成。

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

發佈 評論

Some HTML is okay.