WebClient

WebClient 简介及常用操作。


简介

WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。

pom依赖

1
2
3
4
5
6
7
8
9
<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>

请求流程

  • 创建实例
  • 发起请求
  • 处理相应

创建实例

  • 使用create()

    1
    2
    3
    4
    5
    WebClient webClient = WebClient.create();

    // 如果是调用特定服务的API,可以在初始化webclient 时使用,baseUrl

    WebClient webClient = WebClient.create("https://api.github.com");
  • 使用WebClient builder

    1
    2
    3
    4
    5
    6
    // buidler 可以使用自定义选项
    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();

发起请求

GET请求示例

1
2
3
4
5
6
7
8
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);
}

在uri中使用参数

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
11
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);
}

POST请求

body方法参数为Mono/Flux

1
2
3
4
5
6
7
8
9
10
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);
}

使用syncBody快捷方式

1
2
3
4
5
6
7
8
9
10
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类包含从Object,Publisher,Resource,FormData,MultipartData等创建BodyInserter的方法。

1
2
3
4
5
6
7
8
9
10
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);
}

处理返回结果

retrieve方法是获取响应body的最简单方法。但是,如果需要响应的头信息、Cookie等,可以使用exchange方法,该方法可以访问整个ClientResponse。

1
2
3
4
5
6
7
8
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));
}

将response body 转换为对象/集合

  • bodyToMono

    如果返回结果是一个Object,WebClient将接收到响应后把JSON字符串转换为对应的对象。

  • bodyToFlux

    如果响应的结果是一个集合,则不能继续使用bodyToMono(),应该改用bodyToFlux(),然后依次处理每一个元素。


过滤器

WebClient支持使用ExchangeFilterFunction进行请求过滤。可以使用过滤器功能以任何方式拦截和修改请求。
例如,可以使用过滤器函数为每个请求添加Authorization标头,或记录每个请求的详细信息。

ExchangeFilterFunction有两个参数:

  • ClientRequest
  • 过滤器链中的下一个ExchangeFilterFunction

增加基本身份验证

1
2
3
4
5
6
WebClient webClient = WebClient.builder()
.baseUrl(GITHUB_API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
.filter(ExchangeFilterFunctions
.basicAuthentication(username, token))
.build();

使用过滤器记录日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();


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);
};
}

使用工厂方法来创建过滤器

  • ofRequestProcessor
  • ofResponseProcessor
1
2
3
4
5
6
7
8
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);
});
}

使用过滤器拦截response

1
2
3
4
5
6
private ExchangeFilterFunction logResposneStatus() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
logger.info("Response Status {}", clientResponse.statusCode());
return Mono.just(clientResponse);
});
}

处理WebClient异常

只要收到状态代码为4xx或5xx的响应,WebClient中的retrieve方法就会抛出WebClientResponseException。

1
2
3
4
5
6
7
8
9
10
11
12
13
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来处理WebClientResponseException

1
2
3
4
5
@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());
}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×