Spring 5 WebClient

1.概述

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

我們還將看一下WebTestClient,是一個旨在用於測試的WebClient

2.什麼是WebClient

簡而言之, WebClient是代表執行Web請求的主要入口點的接口。

它是作為Spring Web Reactive模塊的一部分創建的, RestTemplate在這些情況下替代經典的RestTemplate 。此外,新客戶端是一種反應式,無阻塞的解決方案,可通過HTTP / 1.1協議工作。

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

3.依存關係

由於我們使用的是Spring Boot應用程序,因此我們需要spring-boot-starter-webflux依賴項以及Reactor項目。

3.1。用Maven構建

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

<dependency>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-webflux</artifactId>

 </dependency>

 <dependency>

 <groupId>org.projectreactor</groupId>

 <artifactId>reactor-spring</artifactId>

 <version>1.0.1.RELEASE</version>

 </dependency>

3.2。用Gradle構建

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

dependencies {

 compile 'org.springframework.boot:spring-boot-starter-webflux'

 compile 'org.projectreactor:reactor-spring:1.0.1.RELEASE'

 }

4.使用WebClient

為了與客戶正常合作,我們需要知道如何:

  • 創建一個實例
  • 發出請求
  • 處理回應

4.1。創建一個WebClient實例

有三個選項可供選擇。第一個是使用默認設置創建一個WebClient對象:

WebClient client1 = WebClient.create();

第二種選擇是使用給定的基本URI初始化WebClient實例:

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

第三個選項(也是最高級的一個)是使用DefaultWebClientBuilder類構建客戶端,該類允許完全自定義:

WebClient client3 = 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實例

通常,默認的30秒HTTP超時對於我們的需求來說太慢了,因此讓我們看看如何為WebClient實例配置它們。

我們使用的核心類是TcpClient.

在這裡,我們可以通過ChannelOption.CONNECT_TIMEOUT_MILLIS值設置連接超時。我們還可以分別使用ReadTimeoutHandlerWriteTimeoutHandler設置讀取和寫入超時:

TcpClient tcpClient = TcpClient

 .create()

 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)

 .doOnConnected(connection -> {

 connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS));

 connection.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS));

 });



 WebClient client = WebClient.builder()

 .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))

 .build();

請注意,雖然我們也可以在客戶端請求上調用timeout ,但這是信號超時,而不是HTTP連接或讀/寫超時; Mono / Flux發布者超時。

4.3 準備請求

首先,我們需要通過調用method(HttpMethod method)或調用其快捷方式(例如getpostdelete 來指定請求的HTTP方法:

WebClient.UriSpec<WebClient.RequestBodySpec> request1 = client3.method(HttpMethod.POST);

 WebClient.UriSpec<WebClient.RequestBodySpec> request2 = client3.post();

下一步是提供URL。我們可以將它作為Stringjava.net.URL實例傳遞給uri API:

WebClient.RequestBodySpec uri1 = client3

 .method(HttpMethod.POST)

 .uri("/resource");



 WebClient.RequestBodySpec uri2 = client3

 .post()

 .uri(URI.create("/resource"));

然後,我們可以根據需要設置請求正文,內容類型,長度,cookie或標頭。

例如,如果我們要設置一個請求主體,有兩種可用的方法:用BodyInserter填充請求主體或將此工作委託給Publisher

WebClient.RequestHeadersSpec requestSpec1 = WebClient

 .create()

 .method(HttpMethod.POST)

 .uri("/resource")

 .body(BodyInserters.fromPublisher(Mono.just("data")), String.class);



 WebClient.RequestHeadersSpec<?> requestSpec2 = WebClient

 .create("http://localhost:8080")

 .post()

 .uri(URI.create("/resource"))

 .body(BodyInserters.fromObject("data"));

BodyInserter是一個接口,負責用給定的輸出消息和插入期間使用的上下文填充ReactiveHttpOutputMessage主體。 Publisher是一種反應性組件,負責提供可能無限數量的已排序元素。

第二種方法是body方法,它是原始body(BodyInserter inserter)方法的快捷方式。

為了減輕BodyInserter,的填充過程, BodyInserters類具有許多有用的實用程序方法:

BodyInserter<Publisher<String>, ReactiveHttpOutputMessage> inserter1 = BodyInserters

 .fromPublisher(Subscriber::onComplete, String.class);

MultiValueMap也可以:

LinkedMultiValueMap map = new LinkedMultiValueMap();



 map.add("key1", "value1");

 map.add("key2", "value2");



 BodyInserter<MultiValueMap, ClientHttpRequest> inserter2

 = BodyInserters.fromMultipartData(map);

或通過使用單個對象:

BodyInserter<Object, ReactiveHttpOutputMessage> inserter3

 = BodyInserters.fromObject(new Object());

設置正文之後,我們可以設置標題,cookie和可接受的媒體類型。實例化客戶端時,會將值添加到已設置的值中。

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

這是如何使用這些值的示例:

WebClient.ResponseSpec response1 = uri1

 .body(inserter3)

 .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)

 .accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)

 .acceptCharset(Charset.forName("UTF-8"))

 .ifNoneMatch("*")

 .ifModifiedSince(ZonedDateTime.now())

 .retrieve();

4.4。得到回應

最後階段是發送請求並接收響應。這可以通過exchangeretrieve方法來完成。

這些方法的返回類型有所不同。 exchange方法提供ClientResponse以及其狀態和標頭,而retrieve方法是直接獲取主體的最短路徑:

String response2 = request1.exchange()

 .block()

 .bodyToMono(String.class)

 .block();

 String response3 = request2

 .retrieve()

 .bodyToMono(String.class)

 .block();

重要的是要注意bodyToMono方法,如果狀態代碼是4xx (客戶端錯誤)或5xx (服務器錯誤),它將拋出WebClientException 。我們在Mono上使用block方法來訂閱和檢索與響應一起發送的實際數據。

5.使用WebTestClient

WebTestClient是測試WebFlux服務器端點的主要入口點。它具有與WebClient非常相似的API,並且將大部分工作委託給內部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。發出請求

構建WebTestClient對象之後,鏈中的所有後續操作都將與WebClient相似,直到交換方法(一種獲得響應的方法)為止,該方法提供了WebTestClient.ResponseSpec接口以與諸如ExpectStatusExpectBody之類的有用方法一起使用ExpectHeader

WebTestClient

 .bindToServer()

 .baseUrl("http://localhost:8080")

 .build()

 .post()

 .uri("/resource")

 .exchange()

 .expectStatus().isCreated()

 .expectHeader().valueEquals("Content-Type", "application/json")

 .expectBody().isEmpty();

六,結論

在本文中,我們探討了WebClient,是一種新的增強型Spring機制,用於在客戶端發出請求。

我們還研究了它通過配置客戶端,準備請求和處理響應而提供的好處。

文章中提到的所有代碼片段都可以在我們的GitHub存儲庫中找到。