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
模組生成初始專案-
- 轉到http://start.spring.io。
- 選擇彈簧引導版本2.xx的。
- 在依賴項部分新增反應性Web依賴項。
- 如果需要,請更改組和工件的詳細資訊,然後單擊生成工程下載專案。
3.使用WebClient消費遠端API
讓我們做一些有趣的事情,並使用WebClient來使用Real World API。
在本文中,我們將使用WebClient來使用Github的API。我們將使用WebClient在使用者的Github儲存庫上執行CRUD操作。
建立WebClient的一個例項
1.使用該create()
您可以使用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 GET
向Github的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響應,上面的函式會返回一個Flux
的GithubRepo
物件。
請注意,我使用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);
}
如果您具有實際值而不是Publisher
(Flux
/ Mono
),則可以使用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
類包含的方法來建立一個BodyInserter
從Object
,Publisher
,Resource
,FormData
,MultipartData
等-
複製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
需要兩個引數 -
- 在
ClientRequest
與 ExchangeFilterFunction
過濾器鏈中的下一個。
它可以修改ClientRequest
並呼叫ExchangeFilterFucntion
過濾器鏈中的下一個來繼續下一個過濾器或ClientRequest
直接返回修改以阻止過濾器鏈。
1.使用過濾器功能新增基本認證
在上面的所有示例中,我們都包含一個Authorization
用於使用Github API進行基本身份驗證的標頭。由於這是所有請求共有的內容,因此您可以在建立過濾器函式時將此邏輯新增到過濾器函式中WebClient
。
該ExchaneFilterFunctions
API已經為基本認證提供了一個過濾器。你可以像這樣使用它 -
複製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測試 -
提示:專案原始碼下載