摘要
本文是《Spring Boot 实战派》系列的第八篇,标志着我们从单体应用向微服务思想的过渡。文章将聚焦于解决一个核心问题:在分布式系统中,一个服务如何调用另一个服务的 API。
我们将详细对比并实战三种在 Spring 生态中进行 HTTP 调用的主流技术:
RestTemplate
: Spring 经典的、同步阻塞式 HTTP 客户端。WebClient
: Spring 5 引入的、现代的、异步非阻塞的响应式客户端。OpenFeign
: Spring Cloud 生态中的声明式客户端,能让你像调用本地方法一样调用远程 API。
读者将通过实战,清晰地了解每种技术的优缺点和适用场景,并重点掌握 OpenFeign 的优雅与便捷,为构建真正的微服务应用打下坚实的通信基础。
系列回顾:
至此,我们已经精心打磨了一个功能强大、性能优越的单体应用。它就像一个装备精良的“独立作战单位”。然而,在现代软件架构中,系统往往由多个更小、更专注的“微服务”组成。比如,一个电商系统可能被拆分为用户服务、商品服务、订单服务、支付服务等。这些服务需要相互协作,彼此“对话”,才能完成一个完整的业务流程。
欢迎来到通往微服务世界的第一座桥梁!
“服务间的对话”本质上就是远程过程调用 (RPC),而基于 HTTP 的 RESTful API 则是当今最主流的实现方式。今天,我们的任务就是学习如何在一个 Spring Boot 应用中,去调用另一个 Spring Boot 应用提供的 API。
为了方便演示,我们假设之前构建的 my-first-app
(运行在 8080 端口) 是“服务端”,它提供用户信息查询的 API。现在,我们将创建一个全新的 Spring Boot 项目,作为“客户端”,来调用这些 API。
第一幕:经典老将 —— RestTemplate
RestTemplate
是 Spring 框架早期提供的 HTTP 客户端,它简单直接,采用同步阻塞模型。也就是说,当客户端发起请求后,当前线程会一直等待,直到服务端返回响应。
1. 准备工作:在客户端项目中配置 RestTemplate
- 创建一个新的 Spring Boot 项目,命名为
api-client-demo
,并添加 Spring Web 依赖。 - 在
api-client-demo
的配置类中,将RestTemplate
注册为一个 Bean,以便在任何地方注入使用。这是一个最佳实践。
package com.example.apiclientdemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2. 编写一个测试 Controller 来调用服务端 API
确保你的 my-first-app
(服务端) 正在运行,并且可以通过 http://localhost:8080/users/{id}
访问到用户。
在 api-client-demo
中创建一个 TestController.java
:
package com.example.apiclientdemo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
private final String USER_API_URL = "http://localhost:8080/users/{id}";
@GetMapping("/test/user/{id}")
public Map<String, Object> getUserFromRemote(@PathVariable Long id) {
System.out.println("使用 RestTemplate 调用远程 API...");
// 发起 GET 请求,并将响应体直接映射为 Map
// 注意:这里的返回类型需要与服务端 Result<User> 的结构匹配
ResponseEntity<Map> responseEntity = restTemplate.getForEntity(USER_API_URL, Map.class, id);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
return responseEntity.getBody();
} else {
// 简单的错误处理
return Map.of("error", "Failed to fetch user", "status", responseEntity.getStatusCode());
}
}
}
3. 测试
- 启动
api-client-demo
(确保端口不与 8080 冲突,比如默认的 8081)。 - 访问
http://localhost:8081/test/user/1
。 - 你会看到
api-client-demo
的控制台打印日志,然后返回从my-first-app
获取到的用户信息。
RestTemplate
总结:
- 优点: 简单易用,同步模型易于理解和调试。
- 缺点:
- 阻塞式: 在高并发场景下,大量线程会因等待响应而被阻塞,严重影响系统吞吐量。
- URL 硬编码: API 地址直接写在代码里,难以维护。
- API 众多:
getForObject
,getForEntity
,postForObject
… 记忆成本高。
- 结论: 适用于请求量不大的简单场景或老项目维护。新项目不推荐作为首选。
第二幕:响应式新星 —— WebClient
WebClient
是 Spring 5 引入的,作为 RestTemplate
的继任者。它基于 Project Reactor,是一个完全异步非阻塞的响应式编程客户端。
1. 添加依赖
需要 spring-boot-starter-webflux
,它包含了 WebClient
和 Reactor 核心库。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
2. 配置 WebClient
Bean
// AppConfig.java
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class AppConfig {
// ... restTemplate Bean ...
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://localhost:8080") // 配置基础 URL
.build();
}
}
3. 使用 WebClient
调用 API
// TestController.java
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono; // 引入 Mono
@RestController
public class TestController {
// ... restTemplate ...
@Autowired
private WebClient webClient;
@GetMapping("/test-webclient/user/{id}")
public Mono<Map> getUserWithWebClient(@PathVariable Long id) {
System.out.println("使用 WebClient 调用远程 API...");
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(Map.class); // 将响应体转换为 Mono<Map>
}
}
代码解读:
Mono
是 Reactor 中的一个核心类型,代表一个包含 0 或 1 个元素的异步序列。- 整个调用链是声明式的,只有当有订阅者(在这里是 Spring MVC 框架)订阅这个
Mono
时,请求才会真正发出。 - 这是非阻塞的,发出请求后,线程可以去处理其他事情,响应返回后通过回调处理。
WebClient
总结:
- 优点: 异步非阻塞,资源利用率高,适合高并发场景。链式 API 流畅。
- 缺点: 需要学习响应式编程(Reactor)的思想,对新手有一定学习曲线。
- 结论: 是 Spring 官方推荐的未来方向,特别适合构建高性能、高吞吐量的网关和后端服务。
第三幕:声明式王者 —— OpenFeign
OpenFeign
是 Spring Cloud 家族的明星成员,它将 HTTP 远程调用提升到了一个全新的境界。你只需要定义一个 Java 接口,并添加一些注解,OpenFeign
就会在运行时为你自动生成实现类,让你像调用本地方法一样调用远程 API。
1. 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud 版本管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2022.0.4</version> <!-- 使用与你的 Spring Boot 版本兼容的 Cloud 版本 -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. 开启 Feign 功能
在 api-client-demo
的主启动类上添加 @EnableFeignClients
注解。
@SpringBootApplication
@EnableFeignClients // 开启 Feign 功能
public class ApiClientDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ApiClientDemoApplication.class, args);
}
}
3. 创建一个 Feign 客户端接口
在 api-client-demo
项目中创建一个 client
包,并在其中定义 UserClient.java
接口。
package com.example.apiclientdemo.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Map;
// name: 客户端名称,任意取,保证唯一
// url: 要调用的服务的根地址
@FeignClient(name = "user-service", url = "http://localhost:8080")
public interface UserClient {
// 完全复制服务端的 Controller 方法签名(路径、参数)
@GetMapping("/users/{id}")
Map<String, Object> getUserById(@PathVariable("id") Long id);
// 你可以定义多个方法,对应服务端的不同 API
}
这太神奇了! 我们只是定义了一个接口,@FeignClient
注解告诉 Spring Cloud 这是一个 Feign 客户端。@GetMapping
等注解则完全与服务端的 Controller 保持一致。
4. 注入并使用 Feign 客户端
// TestController.java
@RestController
public class TestController {
// ... restTemplate, webClient ...
@Autowired
private UserClient userClient;
@GetMapping("/test-feign/user/{id}")
public Map<String, Object> getUserWithFeign(@PathVariable Long id) {
System.out.println("使用 OpenFeign 调用远程 API...");
// 就像调用一个本地方法一样!
return userClient.getUserById(id);
}
}
代码变得异常简洁!所有的 HTTP 请求细节、URL 拼接、参数处理、JSON 反序列化,都被 OpenFeign 优雅地隐藏了。
OpenFeign
总结:
- 优点:
- 极简开发体验: 声明式接口,代码优雅,可读性极高。
- 完美集成: 与 Spring Cloud 生态(如 Ribbon/LoadBalancer 负载均衡、Hystrix/Resilience4J 熔断降级)无缝集成。
- 高度可插拔: 支持自定义编码器、解码器、日志、拦截器等。
- 缺点: 底层默认仍是阻塞式的(但可配置为使用其他客户端如 OkHttp 或 Apache HttpClient,甚至 WebClient),且引入了 Spring Cloud 的依赖,稍微重一些。
- 结论: 在微服务架构中,
OpenFeign
是进行服务间调用的事实标准和首选方案。
总结与展望
今天,我们走过了服务间通信的“三代同堂”,深刻体验了技术的演进:
RestTemplate
: 元老级,简单粗暴,同步阻塞,适用于简单场景。WebClient
: 响应式新贵,异步非阻塞,性能卓越,是未来的趋势。OpenFeign
: 声明式王者,开发体验最佳,是微服务架构中的首选利器。
你已经掌握了微服务架构中最基础、最核心的“对话”能力。现在,你的应用不再是一个孤岛,它已经准备好与更广阔的世界连接。
我们的代码已经写好,功能也已完善,但它如何从开发者的电脑,可靠、标准地部署到服务器上呢?“在我电脑上明明是好的”,这个魔咒如何破解?
在下一篇 《【部署篇】从代码到云端:使用 Docker 容器化你的 Spring Boot 应用》 中,我们将学习如何使用当今最火的容器化技术 Docker,将我们的应用打包成一个标准的、可移植的“集装箱”,实现一次构建,处处运行。这是每一位现代后端工程师的必备技能,我们下期不见不散!