0. 简述
此文将简要介绍Spring 中的两种 web client 实现 - RestTemplate 和 WebClient
并说明两者的差异
1. RestTemplate 阻塞型客户端
Spring 很早就提供了 RestTemplate 作为 web 客户端的抽象。在底层,RestTemplate 使用了基于每个请求对应一个线程模型(thread-per-request model)的 Java Servlet API
这意味着客户端线程在收到服务器响应之前,将一直被阻塞。当访问大量响应速度较慢的服务时,数量众多的阻塞线程将占用大量的服务器资源,严重影响性能。
2. WebClient 非阻塞型客户端
为了解决上述问题 ,在 Spring 5 中引入了 WebClient ,使用了 Spring Reactive 框架提供的异步、非阻塞解决方案。
Spring Reactive 框架 使用事件驱动模型,通过 Streams API 提供了组合异步逻辑的方法。与同步/阻塞方法相比,它可以使用更少的线程和系统资源来处理更多的业务逻辑。
与 RestTemplate 为每一个事件都新建一个线程不同, WebClient 为每一个事件创建一个“任务”,Spring Reactive 框架 将对这些“任务”进行队列处理,仅在收到适当的响应时才执行任务。
3 . 准备
Maven 依赖
org.springframework.boot
spring-boot-starter-webflux
我们先创建一个响应时间较慢的服务
@GetMapping("/slow-service-tweets")
private List 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"));
}
4. 使用 RestTemplate 调用慢服务
@GetMapping("/tweets-blocking")
public List getTweetsBlocking() {
log.info("Starting BLOCKING Controller!");
final String uri = getSlowServiceUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity> response = restTemplate.exchange(
uri, HttpMethod.GET, null,
new ParameterizedTypeReference>(){});
List result = response.getBody();
result.forEach(tweet -> log.info(tweet.toString()));
log.info("Exiting BLOCKING Controller!");
return result;
}
当我们调用这个endpoint时,由于RestTemplate的同步特性,代码将阻塞以等待来自慢服务的响应。只有在接收到响应时,才会执行此方法中的其余代码。在日志中,我们可以看到:
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!
5. 使用 WebClient 调用慢速服务
@GetMapping(value = "/tweets-non-blocking",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux getTweetsNonBlocking() {
log.info("Starting NON-BLOCKING Controller!");
Flux 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 然后gets()方法执行完毕。代码继续执行,当结果可用时,publisher 将会打印返回的数据。
执行结果:
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)
我们注意到,此方法在接收到响应之前已经完成。
6. 总结
RestTemplate 使用 Java Servlet API,是同步和阻塞的方法。
WebClient 是异步的,在等待响应返回时不会阻塞正在执行的线程。只有当响应就绪时,才会产生通知。
在某些情况下,非阻塞方法使用的系统资源要少得多。因此,在这些情况下,WebClient 将是更好的选择。
本文中提到的所有代码,可以点此下载 Github