Spring Boot 4 中的 RestClient:同步通信现代化
Spring Boot 4.0 的到来,基于 Spring Framework 7,标志着 Java 应用程序处理 HTTP 通信方式的关键范式转变。重点是采用现代架构,消除 RestTemplate 的技术债务,并充分利用 Java 21+ 的并发能力。
关键新功能和改进
RestClient 在 Spring Framework 6.1(和 Spring Boot 3.2)中引入,作为 RestTemplate 的现代替代品,提供同步和灵活的替代方案。在 Spring Boot 4 中,此工具巩固为同步开发的标准。
新的同步客户端抽象
RestClient 是一个同步 HTTP 客户端,提供流畅的构建器式 API 来发送 HTTP 请求。它被设计为使用传统 Spring MVC 栈的应用程序的默认客户端。
| 功能 | 描述 |
|---|---|
| 流畅 API 设计 | 允许使用方法链构建请求,模仿 WebClient 语法,提高可读性并减少重复代码(样板代码)。 |
| 专用错误处理 | 集成支持基于 HTTP 状态码(4xx 和 5xx)处理错误,使用 onStatus() 方法。 |
| 简化自定义 | 通过 API 方法直接添加头部、查询参数和路由变量。 |
| 拦截器和过滤器 | 支持拦截器,允许修改请求或响应,适用于日志记录或身份验证任务。 |
声明式 HTTP 服务客户端
Spring Boot 4 中最显著的新颖之处是 原生且改进的声明式 HTTP 服务客户端支持。
这种方法,被称为"Feign 杀手",允许通过 注解纯 Java 接口 定义与外部服务的通信,消除了手动编写 RestClient 实现的需要。
- 配置简化: Spring Framework 7 引入了
@ImportHttpServices注解,结合 Spring Boot 4 的自动配置,简化了客户端代理作为 Spring bean 的创建,消除了HttpServiceProxyFactory的繁琐手动代码。
性能和与 Project Loom 的对齐
虽然 RestClient 是同步的(阻塞),但它与 Project Loom(虚拟线程 - VTs)从 Java 21+ 提供的 性能和可扩展性提升 有内在联系。
虚拟线程并发
以前,RestTemplate 的同步代码通过在等待 I/O 时消耗昂贵的平台线程限制了可扩展性。RestClient 受益于 JVM 优化:当虚拟线程进行 I/O 调用(如 HTTP 请求)时,它快速"停放"(让出底层平台线程),允许该平台线程(载体线程)执行另一个任务。
效率
这允许使用简单阻塞代码的 Spring MVC 应用程序使用 RestClient 处理 I/O 绑定工作负载中的极高并发,实现与响应式编程(WebClient)相当的可扩展性,但代码复杂性更低。VTs 比传统平台线程轻得多(约 1kB 每线程,而平台线程可能使用 1 到 8 MB)。
性能配置
当 spring.threads.virtual.enabled 为 true 时,Spring Boot 4 中 VT 使用的客户端配置可以自动化。连接和读取超时也通过统一配置属性集中,如 spring.http.clients.connect-timeout 和 spring.http.clients.read-timeout。
新语法:与 RestTemplate 的比较和示例
语法比较:RestClient vs. RestTemplate
最明显的改变是 RestClient 中 流畅 API 的采用,它取代了 RestTemplate 的冗长和重载的模板模式。
| 功能 | RestTemplate(旧样式) | RestClient(现代流畅样式) |
|---|---|---|
| 状态 | 自 Spring Framework 6.1 起已弃用;计划在 Spring Framework 8.0 中移除。 | 同步的事实标准。 |
| 基本创建 | var restTemplate = new RestTemplate(); | var restClient = RestClient.create(); |
| 获取(GET) | String res = restTemplate.getForObject(url, String.class); | String res = restClient.get().uri(url).retrieve().body(String.class); |
| 发送(POST) | ResponseEntity<T> res = restTemplate.postForEntity(url, request, T.class); | ResponseEntity<T> res = restClient.post().uri(url).body(request).retrieve().toEntity(T.class); |
几个清晰且解释的示例(RestClient)
配置 Bean 创建
在 Spring Boot 中,注入 RestClient.Builder 来创建具有全局配置(基础 URL、头部等)的 bean 是推荐的做法。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
public class RestClientConfiguration {
@Bean
public RestClient githubRestClient(RestClient.Builder builder) {
// 为所有请求配置基础 URL 和默认头部
return builder
.baseUrl("https://api.github.com")
.defaultHeader("Accept", "application/json")
.build();
}
}带路由变量和查询参数的 GET 请求
显示如何在 URI 中注入变量并添加查询参数。
// 假设 'githubRestClient' 已注入
public String getRepositoryInfo(String owner, String repo, boolean verbose) {
String uriTemplate = "/repos/{owner}/{repo}";
return githubRestClient.get()
.uri(uriTemplate, owner, repo) // 映射 {owner} 和 {repo}
.queryParam("verbose", verbose) // 添加 ?verbose=true 或 false
.retrieve()
.body(String.class);
}POST 请求(资源创建)
客户端自动将 Java 对象(例如 NewUserDTO)序列化为 JSON 并处理响应。
import org.springframework.http.MediaType;
public User createNewUser(NewUserDTO userData) {
return githubRestClient.post()
.uri("/users")
.contentType(MediaType.APPLICATION_JSON) // 设置 Content-Type
.body(userData) // 将被序列化为 JSON 的对象
.retrieve()
.body(User.class); // 将响应反序列化为 User 对象
}基于 HTTP 状态的错误处理
允许在 RestClient 抛出通用 RestClientException 之前定义特定的错误处理。
import org.springframework.http.HttpStatus;
public User safeGetUser(long id) {
return githubRestClient.get()
.uri("/users/{id}", id)
.retrieve()
.onStatus(HttpStatus.NOT_FOUND, (request, response) -> {
// 404 的特定处理
System.out.println("用户未找到,返回默认值");
// 可以抛出异常或在这种情况下返回默认值
throw new CustomResourceNotFoundException("用户 ID " + id + " 未找到");
})
.onStatus(HttpStatus::is5xxServerError, (request, response) -> {
// 5xx(服务器错误)的处理
throw new RuntimeException("内部服务错误。");
})
.body(User.class);
}声明式 HTTP 客户端(Spring Boot 4)
这是最现代的模式,使用带有 @HttpExchange 注解的接口。
// 1. 客户端接口(契约定义)
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
@HttpExchange(url = "https://api.external.com/api/v1") // 基础 URL
public interface ExternalApi {
@GetExchange("/items/{itemId}") // 映射到 GET https://api.external.com/api/v1/items/{itemId}
Item getItem(@PathVariable String itemId);
}
// 2. 在服务中的使用(Spring Boot 4.0 在配置 @ImportHttpServices 后自动注入代理)
@Service
public class ItemService {
private final ExternalApi externalApi;
// 生成的代理实现被注入
public ItemService(ExternalApi externalApi) {
this.externalApi = externalApi;
}
public Item findItem(String id) {
// 感觉像调用本地方法
return externalApi.getItem(id);
}
}建议: 鉴于 RestTemplate 正在被移除(在 Spring Framework 8.0 中),且 RestClient 的新同步模型针对虚拟线程进行了优化,强烈建议将所有 Spring MVC 项目迁移到 RestClient 并为所有新的服务到服务客户端采用声明式方法(@HttpExchange)。这保证了更清洁、更易维护的代码,为现代高并发 Java 做好准备。