spring-webClient-响应式http客户端

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() 阻塞方式
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());

参考文献

官方文档
官方文档翻译版

posted @ 2024-03-07 16:08  primaryC  阅读(1107)  评论(0编辑  收藏  举报