1. 程式人生 > >Spring 5 WebClient和WebTestClient使用教程 原 leftso

Spring 5 WebClient和WebTestClient使用教程 原 leftso

1.引言

Spring開發人員,您是否曾經覺得需要一個易於使用且高效的流暢功能樣式 API 的非同步/非阻塞 HTTP客戶端?

如果是,那麼我歡迎您閱讀關於WebClient的文章,WebClient是Spring 5中引入的新的被動HTTP客戶端。

 

2.如何使用WebClient

WebClient是Spring 5的反應性Web框架Spring WebFlux的一部分。要使用WebClient,您需要將spring-webflux模組包含在您的專案中。

在現有的Spring Boot專案中新增依賴項

如果您有一個現有的Spring Boot專案,則可以spring-webflux

通過在該pom.xml檔案中新增以下依賴項來新增該模組-

 複製<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

請注意,您需要Spring Boot 2.xx版本才能使用Spring WebFlux模組。

從Scratch建立一個新專案

如果您從頭開始建立專案,那麼您可以使用Spring Initializr

網站的spring-webflux模組生成初始專案-

  1. 轉到http://start.spring.io
  2. 選擇彈簧引導版本2.xx的
  3. 在依賴項部分新增反應性Web依賴項。
  4. 如果需要,請更改工件的詳細資訊,然後單擊生成工程下載專案。

3.使用WebClient消費遠端API

讓我們做一些有趣的事情,並使用WebClient來使用Real World API。

在本文中,我們將使用WebClient來使用Github的API。我們將使用WebClient在使用者的Github儲存庫上執行CRUD操作。

建立WebClient的一個例項

1.使用該create()

方法建立WebClient

您可以使用create()工廠方法建立WebClient的例項-

 複製WebClient webClient = WebClient.create();

如果您只使用特定服務的API,那麼您可以使用該服務的baseUrl來初始化WebClient

 複製WebClient webClient = WebClient.create("https://api.github.com");

2.使用WebClient構建器建立WebClient

WebClient還附帶了一個構建器,它為您提供了一些自定義選項,包括過濾器,預設標題,cookie,客戶端聯結器等 -

 複製WebClient webClient = WebClient.builder()
        .baseUrl("https://api.github.com")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
        .defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
        .build();

使用WebClient發出請求並檢索響應

以下是如何使用WebClient GETGithub的List Repositories API發出請求-

 複製public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri("/user/repos")
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToFlux(GithubRepo.class);
}

瞭解API呼叫的簡單性和簡潔性!

假設我們有一個名為類GithubRepo,確認到GitHub的API響應,上面的函式會返回一個FluxGithubRepo物件。

請注意,我使用Github的基本認證機制來呼叫API。它需要您的github使用者名稱和個人訪問令牌,您可以從https://github.com/settings/tokens中生成該令牌。

使用exchange()方法來檢索響應

retrieve()方法是獲取響應主體的最簡單方法。但是,如果您希望對響應擁有更多的控制權,那麼您可以使用可exchange()訪問整個ClientResponse標題和正文的方法 -

 複製public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri("/user/repos")
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .exchange()
            .flatMapMany(clientResponse -> clientResponse.bodyToFlux(GithubRepo.class));
}

在請求URI中使用引數

您可以在請求URI中使用引數,並在uri()函式中分別傳遞它們的值。所有引數都被花括號包圍。在提出請求之前,這些引數將被WebClient自動替換 -

 複製public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri("/user/repos?sort={sortField}&direction={sortDirection}", 
                     "updated", "desc")
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToFlux(GithubRepo.class);
}

使用URIBuilder構造請求URI

您也可以使用UriBuilder類似的方法獲取對請求URI的完全程式控制,

 複製public Flux<GithubRepo> listGithubRepositories(String username, String token) {
     return webClient.get()
            .uri(uriBuilder -> uriBuilder.path("/user/repos")
                    .queryParam("sort", "updated")
                    .queryParam("direction", "desc")
                    .build())
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToFlux(GithubRepo.class);
}

在WebClient請求中傳遞Request Body

如果你有一個Mono或一個形式的請求體Flux,那麼你可以直接將它傳遞給body()WebClient中的方法,否則你可以從一個物件中建立一個單聲道/通量並像這樣傳遞 -

 複製public Mono<GithubRepo> createGithubRepository(String username, String token, 
    RepoRequest createRepoRequest) {
    return webClient.post()
            .uri("/user/repos")
            .body(Mono.just(createRepoRequest), RepoRequest.class)
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToMono(GithubRepo.class);
}

如果您具有實際值而不是PublisherFluxMono),則可以使用syncBody()快捷方式傳遞請求正文 -

 複製public Mono<GithubRepo> createGithubRepository(String username, String token, 
    RepoRequest createRepoRequest) {
    return webClient.post()
            .uri("/user/repos")
            .syncBody(createRepoRequest)
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToMono(GithubRepo.class);
}

最後,你可以使用BodyInserters類提供的各種工廠方法來構造一個BodyInserter物件並將其傳遞給該body()方法。本BodyInserters類包含的方法來建立一個BodyInserterObjectPublisherResourceFormDataMultipartData等-

 複製public Mono<GithubRepo> createGithubRepository(String username, String token, 
    RepoRequest createRepoRequest) {
    return webClient.post()
            .uri("/user/repos")
            .body(BodyInserters.fromObject(createRepoRequest))
            .header("Authorization", "Basic " + Base64Utils
                    .encodeToString((username + ":" + token).getBytes(UTF_8)))
            .retrieve()
            .bodyToMono(GithubRepo.class);
}

新增過濾器功能

WebClient支援使用ExchangeFilterFunction。您可以使用過濾器函式以任何方式攔截和修改請求。例如,您可以使用過濾器函式為Authorization每個請求新增一個標頭,或記錄每個請求的詳細資訊。

ExchangeFilterFunction需要兩個引數 -

  1. ClientRequest
  2. ExchangeFilterFunction過濾器鏈中的下一個。

它可以修改ClientRequest並呼叫ExchangeFilterFucntion過濾器鏈中的下一個來繼續下一個過濾器或ClientRequest直接返回修改以阻止過濾器鏈。

1.使用過濾器功能新增基本認證

在上面的所有示例中,我們都包含一個Authorization用於使用Github API進行基本身份驗證的標頭。由於這是所有請求共有的內容,因此您可以在建立過濾器函式時將此邏輯新增到過濾器函式中WebClient

ExchaneFilterFunctionsAPI已經為基本認證提供了一個過濾器。你可以像這樣使用它 -

 複製WebClient webClient = WebClient.builder()
        .baseUrl(GITHUB_API_BASE_URL)
        .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
        .filter(ExchangeFilterFunctions
                .basicAuthentication(username, token))
        .build();

現在,您不需要Authorization在每個請求中新增標題。過濾器函式將攔截每個WebClient請求新增此標頭。

2.使用過濾器功能記錄所有請求

我們來看一個習慣的例子ExchangeFilterFunction。我們將編寫一個過濾器函式來攔截並記錄每個請求 -

 複製WebClient webClient = WebClient.builder()
        .baseUrl(GITHUB_API_BASE_URL)
        .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
        .filter(ExchangeFilterFunctions
                .basicAuthentication(username, token))
        .filter(logRequest())
        .build();

這裡是logRequest()過濾器功能的實現-

 複製private ExchangeFilterFunction logRequest() {
    return (clientRequest, next) -> {
        logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
        clientRequest.headers()
                .forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
        return next.exchange(clientRequest);
    };
}

3.使用ofRequestProcessor()和ofResponseProcessor()工廠方法來建立過濾器

ExchangeFilterFunction API提供兩個名為工廠方法ofRequestProcessor()ofResponseProcessor()用於建立分別截獲該請求和響應濾波器的功能。

logRequest()我們在前一節中建立的過濾器函式可以使用ofRequestProcessor()這種工廠方法建立-

 複製private ExchangeFilterFunction logRequest() {
    ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
        logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
        clientRequest.headers()
                .forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
        return Mono.just(clientRequest);
    });
}        

如果您想攔截WebClient響應,則可以使用該ofResponseProcessor()方法建立像這樣的過濾器功能 -

 複製private ExchangeFilterFunction logResposneStatus() {
    return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        logger.info("Response Status {}", clientResponse.statusCode());
        return Mono.just(clientResponse);
    });
}

處理WebClient錯誤

只要接收到狀態碼為4xx或5xx的響應retrieve(),WebClient中的方法WebClientResponseException就會丟擲一個。

您可以使用onStatus()像這樣的方法來自定義,
 

 複製public Flux<GithubRepo> listGithubRepositories() {
     return webClient.get()
            .uri("/user/repos?sort={sortField}&direction={sortDirection}", 
                     "updated", "desc")
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, clientResponse ->
                Mono.error(new MyCustomClientException())
            )
            .onStatus(HttpStatus::is5xxServerError, clientResponse ->
                Mono.error(new MyCustomServerException())
            )
            .bodyToFlux(GithubRepo.class);

}

請注意,與retrieve()方法不同,該exchange()方法在4xx或5xx響應的情況下不會引發異常。您需要自己檢查狀態程式碼,並以您想要的方式處理它們。

使用@ExceptionHandler控制器內部的WebClientResponseExceptions處理

您可以@ExceptionHandler在控制器內部使用這種方式來處理WebClientResponseException並返回適當的響應給客戶端 -

 複製@ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<String> handleWebClientResponseException(WebClientResponseException ex) {
    logger.error("Error from WebClient - Status {}, Body {}", ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex);
    return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString());
}

使用Spring 5 WebTestClient測試Rest API

WebTestClient包含類似於WebClient的請求方法。另外,它還包含檢查響應狀態,標題和正文的方法。您也可以像AssertJ使用WebTestClient 一樣使用斷言庫。

檢視以下示例以瞭解如何使用WebTestClient執行其他API測試 -

提示:專案原始碼下載