1. WebClient 简介
WebClient 是 Spring WebFlux 模块提供的一个非阻塞的基于响应式编程的进行 Http 请求的客户端工具。WebFlux 对标 SpringMvc,WebClient 相当于 RestTemplate,同时也是 Spring 官方的 Http 请求工具。
2. 传统阻塞IO模型 VS 响应式IO模型
- 传统阻塞IO模型 RestTemplate
Spring3.0引入了RestTemplate,SpringMVC或Struct等框架都是基于Servlet的,其底层IO模型是阻塞IO模型。采用阻塞IO模式获取输入数据。每个连接都需要独立的线程,完成数据输入、业务处理、返回。传统阻塞IO模型的问题是,当并发数很大时,就要创建大量线程,占用很大的系统资源。连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源浪费。
- 响应式IO模型 WebClient
Spring5中引入了WebClient作为非阻塞式Reactive Http客户端。Spring社区为了解决SpringMVC的阻塞模型在高并发场景下的性能瓶颈,推出了Spring WebFlux,WebFlux底层实现是久经考验的Netty非阻塞IO通信框架。其实WebClient处理单个HTTP请求的响应时长并不比RestTemplate更快,但是它处理并发的能力更强,非阻塞的方式可以使用较少的线程以及硬件资源来处理更多的并发。
所以响应式非阻塞IO模型的核心意义在于,提高了单位时间内有限资源下的服务请求的并发处理能力,而不是缩短了单个服务请求的响应时长。
- 与RestTemplate相比,WebClient的优势
- 非阻塞响应式IO,单位时间内有限资源下支持更高的并发量。
- 支持使用Java8 Lambda表达式函数。
- 支持同步、异步、Stream流式传输。
3. WebClient 依赖
WebClient 在 spring 提供的 WebFlux 中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
4. WebClient Api
1. 创建实例
- create() 创建实例
WebClient cli = WebClient.create();
- create(String baseUrl) 创建实例并指定 baseURL
WebClient webClient = WebClient.create("http://localhost:8080");
2. 构建器
- WebClient.builder().build() 使用构建器
WebClient build = WebClient.builder().build();
WebClient.builder 的额外配置:
1. uriBuilderFactory:要用作基URL的自定义uriBuilderFactory。
2. defaultUriVariables:展开URI模板时使用的默认值。
3. defaultHeader:每个请求的标头。
4. defaultCookie:每个请求的Cookie。
5. defaultRequest:消费者自定义每个请求。
6. filter:每个请求的客户端筛选器。
7. exchangeStrategies:HTTP消息读取器/写入器自定义。
8. clientConnector:HTTP客户端库设置。
9. observationRegistry:用于启用Observability支持的注册表。
10. observationConvention:一种可选的自定义约定,用于提取记录观测的元数据。
- 创建副本
一旦构建,WebClient是不可变的。但是,您可以按如下方式克隆它并构建修改后的副本
WebClient client1 = WebClient.builder()
.filter(filterA).filter(filterB).build();
WebClient client2 = client1.mutate()
.filter(filterC).filter(filterD).build();
- 编码器
//默认 256 kb
WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.build();
3. 获取响应
retrieve() 方法用于声明如何提取响应。
WebClient client = WebClient.create("https://example.org");
Mono<ResponseEntity<Person>> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(Person.class);
或者只获得 body
WebClient client = WebClient.create("https://example.org");
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
要获取解码对象流,请执行以下操作:
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
bodyToFlux 和 bodyToMono:
-
bodyToFlux 方法用于将响应结果处理为 Flux 对象,Flux 是 Reactor 框架中表示包含零个、一个或多个元素的异步序列的类。这意味着响应结果可能是一个包含多个元素的流,而不是单个值。
-
bodyToMono 方法用于将响应结果处理为 Mono 对象,Mono 是 Reactor 框架中表示包含零个或一个元素的异步序列的类。这意味着响应结果是一个单个值或者没有值。
accept(MediaType... var1)
响应数据类型
acceptCharset(Charset... var1)
响应字符集
4. RequestBody
@RequestMapping("/body")
public void test5(){
WebClient webClient = WebClient.create("http://localhost:8080/api/restful");
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("name", "张和");
formData.add("color", "blue");
Mono<Dog> dogMono = webClient.post()
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.bodyValue(formData)
.retrieve()
.bodyToMono(Dog.class);
System.out.println(dogMono.block());
}
@RequestMapping("/body1")
public void test6(){
WebClient webClient = WebClient.create("http://localhost:8080/api/json");
Mono<Dog> mono = Mono.just(new Dog("和", "33"));
Mono<Dog> dogMono = webClient.post()
.contentType(MediaType.APPLICATION_JSON)
.body(mono, Dog.class)
.retrieve()
.bodyToMono(Dog.class);
System.out.println(dogMono.block());
}
-
contentType()
设置请求参数格式
-
body()/bodyValue()
请求参数
5. 过滤器
filter() 方法添加过滤器
public void test7(){
Mono<Dog> mono = Mono.just(new Dog("和", "33"));
WebClient webClient = WebClient.builder().filter(new ExchangeFilterFunction() {
@Override
public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction exchangeFunction) {
ClientRequest build = ClientRequest.from(clientRequest).body(mono, Dog.class).build();
return exchangeFunction.exchange(build);
}
}).build();
Mono<Dog> dogMono = webClient.post()
.uri("http://localhost:8080/api/json")
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Dog.class);
System.out.println(dogMono.block());
}
6. 同步
WebClient 的 block() 方法可以通过在末尾阻止结果以同步方式使用
@RequestMapping("/filter")
public void test7(){
Mono<Dog> mono = Mono.just(new Dog("和", "33"));
WebClient webClient = WebClient.builder().filter(new ExchangeFilterFunction() {
@Override
public Mono<ClientResponse> filter(ClientRequest clientRequest, ExchangeFunction exchangeFunction) {
ClientRequest build = ClientRequest.from(clientRequest).body(mono, Dog.class).build();
return exchangeFunction.exchange(build);
}
}).build();
Mono<Dog> dogMono = webClient.post()
.uri("http://localhost:8080/api/json")
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Dog.class);
//阻塞
System.out.println(dogMono.subscribe(dog -> {
System.out.println(dog);
}));
//非阻塞
System.out.println(dogMono.block());
}
- subscribe() 非阻塞方式
- block() 阻塞方式
7. 从请求中获取 cookie
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("loginid", "ranjunfeng");
map.add("logintype", "1");
map.add("userpassword", "feng520.");
Mono<String> dogMono = webClient.post()
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.bodyValue(map)
.exchange()
.flatMap(clientResponse -> {
return clientResponse.bodyToMono(String.class).map(myResponse -> {
MultiValueMap<String, ResponseCookie> cookies = clientResponse.cookies();
List<ResponseCookie> ecology_jSessionid = cookies.get("ecology_JSessionid");
//List<String> headers = response.headers().header("session-id");
// here you build your new object with the response
// and your header and return it.
// return new MyNewObject(myResponse, headers);
return ecology_jSessionid.get(0).getValue();
});
});
System.out.println(dogMono.block());