大数跨境
0
0

神器来袭!告别RestTemplate,WebClient霸气登场

神器来袭!告别RestTemplate,WebClient霸气登场 Spring全家桶实战案例
2023-10-24
0
导读:Spring WebFlux中使用WebClient远程接口调用

环境:SpringBoot 2.6.12


1. 概述

Spring WebClient是一个基于Reactive编程模型的HTTP客户端,它允许您在Spring应用程序中以非阻塞的方式发送HTTP请求。WebClient是Spring 5中引入的一个重要功能,旨在提供一种更现代、高效和灵活的方式来与远程服务进行交互。

以下是关于Spring WebClient的一些描述信息:

  1. 非阻塞性:WebClient使用Reactor框架实现响应式编程,允许在发送HTTP请求时进行异步处理。这意味着您可以使用WebClient同时发送多个请求,并在它们都完成之前不阻塞应用程序的其他部分。

  2. 集成性:WebClient可以轻松地与Spring框架的其他组件集成,如Spring MVC、Spring WebFlux等。此外,它还支持各种HTTP消息转换器,使您可以轻松地将请求和响应数据转换为所需的Java对象。

  3. 自定义性:WebClient提供了丰富的自定义选项,例如超时设置、连接池配置、重试策略等。您可以通过配置WebClient的参数来满足特定的应用程序需求。

  4. 错误处理:WebClient支持优雅的错误处理机制。您可以使用异常处理程序捕获和处理HTTP请求中发生的错误,并根据需要采取适当的行动。

  5. 易于使用:WebClient提供了简单易用的API,使得发送HTTP请求变得非常简单。您可以使用Fluent API或Lambda表达式等方式来构建和发送HTTP请求,而无需编写大量的代码。

总之,Spring WebClient是一种功能强大的HTTP客户端,适用于基于响应式编程模型的现代应用程序。它提供了一种高效、可扩展和非阻塞的方式来与远程服务进行交互,并且可以轻松地集成到Spring应用程序中。

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插入。


Spring WebClient与RestTemplate都是Spring框架提供的用于发送HTTP请求的工具,但它们之间存在一些关键差异。

2. WebClient & RestTemplate对比

  1. 阻塞与非阻塞:

    • RestTemplate使用Java Servlet API,这种模型在底层会为每个请求分配处理线程。这意味着线程会一直保持阻塞,直到Web客户端收到响应。这可能导致线程堆积,耗尽线程池并占掉所有可用内存。

    • WebClient是利用Spring Reactive框架提供的异步、非阻塞式解决方案。它为每个事件(HTTP调用)创建类似task的结构,而不是创建新线程。Reactive框架会在后台对这些task进行排队,只有在响应结果就绪后才开始执行。这允许应用程序在等待响应的同时不会阻塞正在执行的线程。

  2. 性能:

    • RestTemplate的阻塞式模型在处理大量并发请求时可能会导致性能下降。由于每个请求都需要一个独立的处理线程,因此线程上下文切换的开销可能会变得显著。

    • WebClient的非阻塞模型允许更有效地使用系统资源。由于它不阻塞线程,因此可以更高效地处理并发请求。此外,由于它使用事件驱动和异步逻辑,因此可以减少CPU和内存的使用。

  3. 适用场景:

    • RestTemplate长期以来一直是Spring的默认Web客户端,适用于大多数基于Spring的应用程序。它简单易用,并且已经为广大开发者所熟悉。

    • WebClient是Spring 5及之后版本引入的新特性,适用于构建响应式应用程序。它更适合处理大量并发请求,并且可以利用现代编程模型(如Reactive Streams API)的优势。


3. WebClient配置

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

  1. WebClient#create()

  2. WebClient#create(String baseUrl)

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

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


4. 简单使用示例

示例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();

5. 最大内存配置

为了避免应用程序内存问题,编解码器对内存中的数据缓存有限制。默认情况下,它们被设置为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();

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

7. 获取响应数据

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

8. 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())) ;    });}

9. 请求体

请求体可以从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);

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

11. 过滤器

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

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

完毕!!!

求关注、求转发


【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读285
粉丝0
内容832