优秀的编程知识分享平台

网站首页 > 技术文章 正文

Spring WebFlux中使用WebClient远程接口调用

nanyue 2024-08-20 17:31:55 技术文章 6 ℃

环境:SpringBoot 2.4.12


概述

Spring WebFlux包括一个用来执行HTTP请求的客户端。WebClient有一个基于Reactor(反应式)的功能性的、流畅的API,它支持异步逻辑的声明式组合,而不需要处理线程或并发性。它是完全非阻塞的,它支持流,并依赖于同样的编解码器,这些编解码器也用于在服务器端对请求和响应内容进行编码和解码。

WebClient需要一个Http Client库来执行请求。内置支持以下功能:

  • Reactor Netty

https://github.com/reactor/reactor-netty

  • Jetty Reactive HttpClient

https://github.com/jetty-project/jetty-reactive-httpclient

  • Apache HttpComponents

https://hc.apache.org/index.html

  • 其他可以通过ClientHttpConnector插入。

WebClient配置

创建WebClient最简单的方法是通过静态工厂方法之一:

  • WebClient#create()
  • WebClient#create(String baseUrl)

你也可以使用WebClient#builder提供更多的选项:

  • uriBuilderFactory:定制的uriBuilderFactory用作基础URL。
  • defaultUriVariables:在展开URI模板时使用的默认值。
  • defaultHeader:每个请求的头文件。
  • defaultCookie:每个请求的cookie。
  • defaultRequest:自定义每个请求的使用者。
  • filter:针对每个请求的客户端过滤器。
  • exchangeStrategies: HTTP消息读取器/写入器自定义。
  • clientConnector: HTTP客户端库设置。

示例1:

WebClient client = WebClient.builder()
  .codecs(configurer -> ... )
  .build();

一旦创建了WebClient就是不可变的。不过,你可以克隆它并建立一个修改后的副本如下:

示例2:

WebClient client1 = WebClient.builder()
  .filter(filterA).filter(filterB).build();
// 创建一个副本
WebClient client2 = client1.mutate()
  .filter(filterC).filter(filterD).build();

最大内存配置

为了避免应用程序内存问题,编解码器对内存中的数据缓存有限制。默认情况下,它们被设置为256KB。如果这还不够,你会得到以下错误:

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer

修改默认的最大内存

WebClient webClient = WebClient.builder()
  .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
  .build();

Reactor Netty

网络请求Http Client。

自定义Reactor Netty设置,超时设置

HttpClient httpClient = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000).doOnConnected(con -> {
  con.addHandlerFirst(new ReadTimeoutHandler(2, TimeUnit.SECONDS)) ;
  con.addHandlerLast(new WriteTimeoutHandler(10));
});
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient) ;
Builder builder = WebClient.builder().clientConnector(connector) ;
// 响应超时配置
HttpClient httpClient = HttpClient.create().responseTimeout(Duration.ofSeconds(2));

获取响应数据

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

仅仅获取响应数据

Mono<Person> result = client.get()
  .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .bodyToMono(Person.class);

默认情况下,4xx或5xx响应会导致WebClientResponseException,包括特定HTTP状态码的子类。要自定义错误响应的处理,使用onStatus处理程序如下所示:

Mono<Person> result = client.get()
  .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
  .retrieve()
  .onStatus(HttpStatus::is4xxClientError, response -> ...)
  .onStatus(HttpStatus::is5xxServerError, response -> ...)
  .bodyToMono(Person.class);

Exchange操作

exchangeToMono()和exchangeToFlux()方法对于需要更多控制的更高级情况很有用,比如根据响应状态对响应进行不同的处理:

@GetMapping("/removeInvoke3")
public Mono<R> remoteInvoke3() {
  return wc.get()
    .uri("http://localhost:9000/users/get?id={id}", new Random().nextInt(1000000))
    .exchangeToMono(clientResponse -> {
      if (clientResponse.statusCode().equals(HttpStatus.OK)) {
        return clientResponse.bodyToMono(Users.class);
      } else {
        return clientResponse.createException().flatMap(Mono::error) ;
      }
    })
    .log()
    .flatMap(user -> Mono.just(R.success(user)))
    .retry(3) // 重试次数
    .onErrorResume(ex -> {
      return Mono.just(R.failure(ex.getMessage())) ;
    });
}

请求体

请求体可以从ReactiveAdapterRegistry处理的任何异步类型进行编码,比如Mono如下例所示:

Mono<Person> personMono = ... ;
Mono<Void> result = client.post()
  .uri("/persons/{id}", id)
  .contentType(MediaType.APPLICATION_JSON)
  .body(personMono, Person.class)
  .retrieve()
  .bodyToMono(Void.class);

如果你有实际值,你可以使用bodyValue方法,如下例所示:

Person person = ... ;
Mono<Void> result = client.post()
  .uri("/persons/{id}", id)
  .contentType(MediaType.APPLICATION_JSON)
  .bodyValue(person)
  .retrieve()
  .bodyToMono(Void.class);

Form Data

要发送表单数据,可以提供MultiValueMap作为主体。注意,内容被FormHttpMessageWriter自动设置为application/x-www-form-urlencoded。下面的例子展示了如何使用MultiValueMap:

MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
  .uri("/path", id)
  .bodyValue(formData)
  .retrieve()
  .bodyToMono(Void.class);

便捷方法

import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
  .uri("/path", id)
  .body(fromFormData("k1", "v1").with("k2", "v2"))
  .retrieve()
  .bodyToMono(Void.class);

过滤器

你可以通过WebClient注册客户端过滤器(ExchangeFilterFunction)。生成器,以便拦截和修改请求,如下例所示:

WebClient client = WebClient.builder()
  .filter((request, next) -> {
    ClientRequest filtered = ClientRequest.from(request)
      .header("foo", "bar")
      .build();
      return next.exchange(filtered);
    })
  .build();

完毕!!!

Spring WebFlux请求处理流程
Spring WebFlux使用函数式编程之HandlerFunction(1)
Spring WebFlux使用函数式编程之RouterFunction(2)
一文带你彻底理解Spring WebFlux的工作原理
SpringBoot WebFlux整合Spring Security进行权限认证
SpringBoot WebFlux整合MongoDB实现CRUD及分页功能
Spring WebFlux核心处理组件DispatcherHandler
Spring WebFlux入门实例并整合数据库实现基本的增删改查
Spring WebFlux使用函数式编程之Filtering Handler Functions
SpringBoot WebFlux整合R2DBC实现数据库反应式编程

Tags:

最近发表
标签列表