Spring WebClient 与 RestTemplate 比较
发布于 1 个月前 作者 yan 57 次浏览

(给ImportNew加星标,提高Java技能)

编译:ImportNew/唐尤华

www.baeldung.com/spring-webclient-resttemplate

1. 导读

** **

在这篇教程中,我们将对比两种 Spring Web 客户端实现,RestTemplate 和新的 Spring 5 Reactive WebClient。

2. 阻塞式客户端 vs 非阻塞客户端

在 Web 应用程序中,向其他服务发起 HTTP 调用是常见需求。因此,需要一个 Web 客户端工具。

2.1. RestTemplate 阻塞式客户端

长期以来,Spring 一直把 RestTemplate 作为默认 Web 客户端。 RestTemplate 使用 Java Servlet API,这种模型在底层会为每个请求分配处理线程

这意味着线程会一直保持阻塞,直到 Web 客户端收到响应。阻塞式模型的问题在于每个线程都会消耗大量内存和 CPU 资源。

让我们考虑这样的情况:系统收到大批请求时,等待某些服务返回结果,而这些服务本身执行缓慢。

结果,等待的请求会发生堆积。 应用程序将创建许多线程,耗尽线程池并占掉所有可用内存 。还可能因为 CPU 上下文(线程)频繁切换导致性能下降。

2.2. WebClient 非阻塞客户端

另一种 WebClient 方案,利用 Spring Reactive 框架提供异步、非阻塞式解决方案。

RestTemplate 为每个事件(HTTP 调用)创建一个新线程,而 WebClient 为每个事件创建类似 task 的结构。Reactive 框架会在后台对这些 task 进行排队,且只在响应结果就绪后才开始执行。

Reactive 框架采用事件驱动,通过Reactive Streams API 实现异步逻辑。相比同步阻塞式调用,Reactive 方法用更少的线程和系统资源处理了更多业务逻辑。

Webclient 是 Spring WebFlux 开发库的一部分。因此, 写客户端代码时,还可以应用函数式编程与流式 API,支持 Reactive 类型(Mono 和 Flux)

  1. 实例比较

为了展示两种方法差异,需要多客户端并行请求进行性能测试。可以看到,收到多个客户端请求后,阻塞方法的性能显著下降。

而 Reactive 非阻塞方法的表现应该与请求数量无关,性能稳定。

这里会实现两个 REST Endpoint:一个用 RestTemplate,另一个用 WebClient。它们的任务是调用另一个耗时的 REST Web 服务,返回推文列表。

首先,需要添加 Spring Boot WebFlux Starter 依赖:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-webflux</artifactId>
</dependency> 

下面 REST Endpoint,比较耗时:

[@GetMapping](/user/GetMapping)("/slow-service-tweets")
private List<Tweet> getAllTweets() {
   Thread.sleep(2000L); // 延迟
   return Arrays.asList(
     new Tweet("RestTemplate rules", "[@user1](/user/user1)"),
     new Tweet("WebClient is better", "[@user2](/user/user2)"),
     new Tweet("OK, both are useful", "[@user1](/user/user1)"));
} 

3.1. RestTemplate 调用耗时服务

接着实现 REST Endpoint,通过 WebClient 调用耗时服务。

先使用 RestTemplate:

[@GetMapping](/user/GetMapping)("/tweets-blocking")
public List<Tweet> getTweetsBlocking() {
   log.info("Starting BLOCKING Controller!");
   final String uri = getSlowServiceUri();
   RestTemplate restTemplate = new RestTemplate();
   ResponseEntity<List<Tweet>> response = restTemplate.exchange(
     uri, HttpMethod.GET, null,
     new ParameterizedTypeReference<List<Tweet>>(){});
   List<Tweet> result = response.getBody();
   result.forEach(tweet -> log.info(tweet.toString()));
   log.info("Exiting BLOCKING Controller!");
   return result;
} 

由于 RestTemplate 是同步调用,调用 Endpoint 时代码将进入阻塞等待被调用的耗时服务响应。只有在收到响应后,才会执行方法中的后续代码。通过日志可以看到:

Starting BLOCKING Controller!
Tweet(text=RestTemplate rules, username=[@user1](/user/user1))
Tweet(text=WebClient is better, username=[@user2](/user/user2))
Tweet(text=OK, both are useful, username=[@user1](/user/user1))
Exiting BLOCKING Controller! 

3.2. WebClient 调用耗时服务

接下来用 WebClient 调用耗时服务:

[@GetMapping](/user/GetMapping)(value = "/tweets-non-blocking",
           produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Tweet> getTweetsNonBlocking() {
   log.info("Starting NON-BLOCKING Controller!");
   Flux<Tweet> tweetFlux = WebClient.create()
     .get()
     .uri(getSlowServiceUri())
     .retrieve()
     .bodyToFlux(Tweet.class);
   tweetFlux.subscribe(tweet -> log.info(tweet.toString()));
   log.info("Exiting NON-BLOCKING Controller!");
   return tweetFlux;
} 

上面的情况,WebClient 返回 Flux publisher 后就执行完成了。结果就绪时,publisher 会向订阅者发送推文列表。注意:客户端(这里指 Web 浏览器)调用 /tweets-non-blocking Endpoint 也可以订阅 Flux 对象。

这次的日志:

Starting NON-BLOCKING Controller!
Exiting NON-BLOCKING Controller!
Tweet(text=RestTemplate rules, username=[@user1](/user/user1))
Tweet(text=WebClient is better, username=[@user2](/user/user2))
Tweet(text=OK, both are useful, username=[@user1](/user/user1)) 

请注意:这个 Endpoint 方法在收到响应前就已执行完成。

4. 总结

这篇文章讨论了 Spring 中使用 Web Client 的两种不同方式。

RestTemplate 采用 Java Servlet API,因而是阻塞式同步调用。相反,WebClient 是异步的,等待响应的同时不会阻塞正在执行的线程。只有在响应结果准备就绪时,才会发起通知。

RestTemplate 仍然有用武之地。非阻塞模式在某些场景下比阻塞方法占用系统资源要少得多,这时 WebClient 是一种更好的选择。

本文所有源代码可以在 GitHub 上找到。

github.com/eugenp/tutorials/tree/master/spring-5-reactive-2

推荐阅读

(点击标题可跳转阅读)

神秘的 Eclipse Collections API :  forEachInBoth

Java 新特性实例之自动化测试

如何访问 redis 中的海量数据?避免事故产生

看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

好文章,我在看❤️

回到顶部