查看原文
其他

Spring WebClient 与 RestTemplate 比较

ImportNew ImportNew 2019-10-02

(给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)


3. 实例比较


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


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


3.1. RestTemplate 调用耗时服务


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


先使用 RestTemplate:

@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)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@user1)
Exiting BLOCKING Controller!


3.2. WebClient 调用耗时服务


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


@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)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@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技能

好文章,我在看❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存