SpringBoot中RestClient、WebClient和RestTemplate比较选择

在本文中,将比较用于在 Spring Boot 应用程序中调用 REST API 的 RestClient、WebClient 和 RestTemplate 库。还将针对不同情况下的正确选择提供建议。

RestTemplate与竞争对手相比缺少什么?
在WebFlux堆栈出现之前,RestTemplate 是从3.0 版本开始为 Spring 框架提供成熟的同步 HTTP 客户端库的最佳选择。在开始了解为什么我们需要在 Spring 框架中使用另一个 HTTP 客户端库之前,让我们先了解一下 RestTemplate 及其优缺点。

在本文的示例中,我们有一个名为 echo-service 的假设服务,它提供 echo REST API(使用 HTTP GET),并且我们希望使用 Spring Boot 提供的这三个库来调用该 API,以了解差异和相似之处。

使用RestTemplate调用echo服务
在 Spring Boot 中使用 RestTemplate 非常简单。我们可以从控制器或服务内部的 RestTemplateBuilder 创建它:

private final RestTemplate restTemplate;
public RestTemplateController(RestTemplateBuilder builder) {
    this.restTemplate = builder.build();


在一个单独的配置类中:

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}

现在我们可以使用 RestTemplate Bean 了,在我们的示例中,我们直接在 RestController 中使用它(为了简单起见,我们保持示例的简洁性,直接在控制器中使用这三个库中的任何一个都不是一个好主意,最好定义一个 EchoClient 并在那里使用它们):

@GetMapping("/echo/{message}")
   public Echo echo(@PathVariable String message) {
       return restTemplate.getForObject(
"http://localhost:8080/echo/" + message, Echo.class);
   }

RestTemplate 根据 Spring 模板模式提供了几组重载方法:

  • XXXForObject(最直接的方法)
  • XXXForEntity(代表更多信息)
  • exchange (更通用,具有额外的灵活性)
  • execute (最通用,可完全控制)

RestTemplate 支持声明式 HTTP 接口!
令人难以置信的是,许多 Spring 开发人员并不知道,与WebClient和RestClient类似,RestTemplate也支持使用 @HttpExchange 注解将 HTTP 服务定义为 Java 接口。Spring 框架中的这个很酷的功能允许我们将 HTTP 服务定义为接口。

例如,echo-service 接口将与此类似:

@HttpExchange(url = "/echo")
public interface EchoService {
   @GetExchange(
"/{message}")
   Echo echo(@PathVariable String message);


现在,我们应该创建一个代理,通过 RestTemplate 实例执行请求,并从 EchoService 接口创建一个 Bean:

@Bean
   public EchoService echoService() {
       RestTemplate restTemplate = new RestTemplate();
       restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("http://localhost:8080"));
       RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
       HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
       return factory.createClient(EchoService.class);
   }


RestTemplate 的优点和缺点
和其他库一样,RestTemplate 也有优点和缺点。了解这些优点和缺点将有助于我们更好地利用它,并在下文中将它与其他两个库进行比较:

优点

  • 可切换的底层 HTTP 客户端库
  • 支持声明式 HTTP 接口
  • 高度可配置
  • 可用于旧版本的 Spring Framework

缺点
  • 一个方法有多个重载,这使得该库难以使用
  • 经典的 Spring 模板模式已过时
  • 不适合非阻塞环境(例如 WebFlux)

WEBCLIENT给我们带来了什么新的东西?
正如我们在前面几节中讨论的,RestTemplate 是一个简单而灵活的调用 HTTP 服务的库,但它是同步和阻塞的,这也是Spring 非阻塞堆栈(WebFlux)引入一种新的、现代的、完全的主要原因。非块和异步 HTTP 客户端库,具有功能齐全且流畅的 API,称为 WebClient。

WebClient 是专为WebFlux 堆栈引入和设计的。与 RestTemplate 不同,它不是基于老式的基于模板的 API,而是遵循现代函数式和流畅风格的 API。

WebClient API 比 RestTemplate 清晰得多,并且它们相当于 HTTP 方法。

使用WebClient调用echo服务
与 RestTemplate 非常相似,首先,我们需要使用 WebClient.Builder 类创建一个 bean:

@Configuration
public class WebClientConfig {
   @Bean
   public WebClient webClient(WebClient.Builder builder) {
       return builder
               .baseUrl("http://localhost:8080")
               .build();
   }
}

然后将 WebClient Bean 注入控制器并使用它:
@GetMapping("/echo/{message}")
   public Mono<Echo> echo(@PathVariable String message) {
       return webClient
               .get()
               .uri(
"/echo/" + message)
               .retrieve()
               .bodyToMono(Echo.class);
   }

正如我之前所说,WebClient 是为 WebFlux 设计的,其 API 和线程模型与 RestTemplate 不同。WebClient 是在 Spring Framework 5 中引入的,虽然它是为 Spring WebFlux 设计的,但开发人员通过调用 WebClient 中的 block() 操作,在阻塞栈(Web MVC)中大量使用了它:

@GetMapping("/echo/{message}")
   public Echo echo(@PathVariable String message) {
       return webClient
               .get()
               .uri(
"/echo/" + message)
               .retrieve()
               .bodyToMono(Echo.class)
               .block();

在 Web MVC 堆栈中使用 WebClient 并调用 block() 操作在技术上没有任何问题:

  • 当我们在 WebClient 返回类型上调用 block() 方法时,它会从 WebMVC 线程池中阻塞调用线程,因此,WebClient 可以继续异步、非阻塞地调用外部服务或 API。
  • 通过这种方式,您可以获得 WebClient API 及其基础架构的所有优点(如非阻塞、性能和流式支持等),但代价是要在您的 Web MVC 项目(WebFlux 和 Project Reactor)中添加额外的库依赖关系。

使用 WebClient 定义声明式 HTTP 接口
与 RestTemplate 一样,WebClient 也支持使用 @HttpExchange 注解将 HTTP 服务定义为 Java 接口。接口及其方法的实现与 RestTemplate 类似,唯一的区别是当我们想创建一个代理来执行请求时,将其附加到 WebClient 实例,并从 EchoService 接口创建一个 Bean:

@Bean("wc")
   public EchoService echoService() {
       WebClient webClient = WebClient.builder().baseUrl(
"http://localhost:8080").build();
       WebClientAdapter adapter = WebClientAdapter.create(webClient);
       HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
       return factory.createClient(EchoService.class);
   }


为什么需要RestClient?
既然我们已经有了 RestTemplate 和 WebClient,为什么还要在 Spring Framework 中再添加一个 HTTP 客户端?

现在,通过了解 RestTemplate 和 WebClient 背后的故事,你就能理解为什么我们需要在 Spring Framework 中再添加一个 HTTP 客户端库了。

RestClient 是在 Spring Framework 6.1 中引入的,它拥有与 RestTemplate 相同的基础架构和抽象,这意味着它是阻塞的,但它拥有与 WebClient 类似的流畅 API。

使用 RestClient 调用 echo 服务
同样,与 RestTemplate 和 WebClient 非常相似,我们可以使用 RestClient.builder() 方法创建一个 Bean:

@Bean
   public RestClient restClient() {
       return RestClient.builder()
               .baseUrl("http://localhost:8080")
               .build();
   }

然后将 RestClient Bean 注入控制器并使用它:

@GetMapping("/echo/{message}")
   public Echo echo(@PathVariable String message) {
       return restClient
               .get()
               .uri(
"/echo/" + message)
               .retrieve()
               .body(Echo.class);
   }

正如您在本例中看到的,RestClient API 与 WebClient API 几乎完全相同,只是我们不需要在依赖关系中安装 WebFlux 库,也不需要调用 block() 方法!

如果你不想在 Spring MVC 项目中依赖 Spring WebFlux,那么 RestClient 是一个不错的选择,它能让你的项目中的代码保持不混杂。

从 RestTemplate 迁移到 RestClient
Spring Framework 团队建议在新的 Spring MVC 项目中使用 RestClient,并提供从 RestTemlate迁移到 RestClient 的指南。

阅读Spring 框架参考文档的这一部分,,了解有关从 RestTemplate 迁移到 RestClient 的更多信息。

使用 RestClient 定义声明式 HTTP 接口
RestClient 可用于类似于 RestTemplate 和 WebClient 的声明式 HTTP 接口。它可以与 @GetExchange 和 @HttpExchange 注释一起使用,以声明性方法定义我们的 HTTP 客户端。

实现该接口及其方法与 RestTemplate 和 WebClient 类似,唯一的区别是当我们想要创建一个代理来执行请求并将其附加到 RestClient 实例并从 EchoService 接口创建一个 bean 时:

@Bean("rc")
   public EchoService echoService() {
       RestClient restClient = RestClient.builder().baseUrl(
"http://localhost:8080/").build();
       RestClientAdapter adapter = RestClientAdapter.create(restClient);
       HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
       return factory.createClient(EchoService.class);
   }

HTTP 客户端可观察性
可观察性是每个库和框架的一个重要方面。如果在某个时间点,担心可见性和可观察性是 SRE 和 DevOps 团队的职权范围,那么今天,每个开发人员都必须确保可以跟踪和分析代码。HTTP 客户端尤其重要,因为它们在微服务之间传播可观察性上下文。

作为开发者我们为什么要关心这个?三个主要原因(排名不分先后):

  1. 清楚地了解应用程序内的代码执行路径和流程可以提高我们的工作效率
  2. 通过适当的分析,可以检测超时、性能不佳甚至更多问题。

我一直在使用一个名为Digma的免费 IDE 插件,它提供了一种即使在开发和测试中也可以快速观察代码的方法。它的独特属性之一是它直接集成到 IDE 中。我将利用此功能来演示如何在开发中使用 HTTP 库的可观测性数据。

我使用三个微服务创建了一个快速示例。

  • echo-service 服务为另外两个调用者服务提供了一个简单的echo REST API。
  • 第一个调用者服务称为reactive-caller,它使用WebFlux堆栈和WebClient来调用echo REST API,
  • 第二个调用者服务称为blocking-caller,它使用Web MVC堆栈和所有3个HTTP客户端库(RestClient、WebClient和RestTemplate)来调用 echo REST API。

这些服务的代码可以在此GitHub 存储库中找到。

要快速启动并运行可观察性堆栈,您可以安装IntelliJ IDEA 的 Digma 插件,您可以克隆存储库并在本地运行服务。例如,您可以使用阻塞调用者服务中的 RestTemplate 库来调用 echo REST API,如下所示(使用HTTPie):
http :8081/rt/echo/hello

现在,在 Digma 视图的asset列表中,你可以看到 Digma 检测到一个 HTTP 客户端和我们的两个端点。
Digma 允许我们在 IntelliJ IDEA 内本地观察 HTTP 客户端的活动和跟踪。

最终比较和建议
RestClient、WebClient 和 RestTemplate 都是允许您执行 HTTP 请求的网络库的包装器。最重要的区别在于编程范式和 API 设计方法。

RestClient 是一个游戏规则改变者。如果您正在 Spring MVC 堆栈中寻找 HTTP 客户端,我们不再需要使用 WebClient 来拥有现代且功能齐全的 API。因此,如果我们要启动一个基于 Spring MVC 堆栈的绿地项目,RestClient 是我们的最佳选择。

对于基于 Spring MVC 堆栈且已使用 RestTemplate 的现有项目,将其替换为 RestClient 可能是一个不错的选择,因为 RestTemplate 现在处于维护模式,并且不会再更新。

对于使用 WebClient 的阻塞 Spring 项目,由于其功能强大且现代化的 API,是时候考虑使用 RestClient 了,因为 Web MVC 堆栈从 WebClient 迁移到 RestClient 不会花费很长时间,而且值得付出努力。

当然,WebClient 仍然是 Spring WebFlux项目最好也是唯一的官方选择。

总结
通过引入 RestClient,Spring 开发人员现在拥有了 RestTemplate 的现代替代品,其功能和流畅的 API 类似于 WebClient,但适用于 Spring WebMVC 堆栈中的同步编程模型。尽管 RestTemplate 已被弃用,但它仍处于维护模式,并将根据需要接收更新。但是,强烈建议在新项目中使用 RestClient 而不是 RestTemplate,甚至在现有项目中迁移到 RestClient。