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文件中添加以下依赖项来添加该模块-
org.springframework.boot spring-boot-starter-webflux 请注意,您需要Spring Boot 2.xx版本才能使用Spring WebFlux模块。从Scratch创建一个新项目
如果您从头开始创建项目,那么您可以使用Spring Initializr网站的spring-webflux模块生成初始项目-
在依赖项部分添加反应性Web依赖项。
如果需要,请更改组和工件的详细信息,然后单击生成工程下载项目。
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 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。
该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());
}