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 发送请求。对使用传统 Spring MVC 栈的应用来说,它就是默认选择。
它的 API 设计向 WebClient 看齐,用方法链构建请求,可读性好很多,样板代码也少了。错误处理方面,onStatus() 方法让你能根据 HTTP 状态码(4xx、5xx)做针对性处理。添加 header、query param、路径变量都是链式 API 一气呵成。拦截器也支持,日志、认证这类横切逻辑很方便挂上去。
声明式 HTTP 服务客户端
Spring Boot 4 里最让人兴奋的新东西:原生且改进的声明式 HTTP 服务客户端。
社区管它叫 "Feign 杀手"。你只需要定义一个纯 Java 接口,打上注解,Spring 帮你生成代理实现。不用再手写 RestClient 调用代码了。
Spring Framework 7 新增了 @ImportHttpServices 注解,配合 Spring Boot 4 的自动配置,客户端代理会自动注册为 Spring bean。之前那套 HttpServiceProxyFactory 的繁琐手动配置,可以扔了。
性能和与 Project Loom 的对齐
RestClient 是同步阻塞的,但它和 Project Loom(虚拟线程)深度绑定。Java 21+ 之后,这是个致命组合。
虚拟线程并发
以前 RestTemplate 的同步代码在等 I/O 时会占着一个昂贵的平台线程,可扩展性很差。RestClient 配合虚拟线程就不同了:虚拟线程做 HTTP 请求时,会快速 "停放"(让出底层平台线程),载体线程立刻去执行别的任务。
效率
结果就是,用简单的阻塞代码写 Spring MVC 应用,也能扛住极高的 I/O 并发,可扩展性逼近响应式编程(WebClient),但代码复杂度低一大截。虚拟线程每条大约 1kB,平台线程动辄 1 到 8 MB,量级差异摆在那里。
性能配置
把 spring.threads.virtual.enabled 设成 true,Spring Boot 4 会自动处理虚拟线程相关的客户端配置。连接和读取超时也统一到了 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 创建
推荐注入 RestClient.Builder 来创建带全局配置的 bean(基础 URL、默认 header 等)。
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 中注入变量,添加查询参数,都是链式 API。
// 假设 '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 对象序列化成 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 又针对虚拟线程做了优化。新项目直接上 RestClient,声明式客户端(@HttpExchange)作为服务间通信的默认方案。迁移老项目也不该犹豫了。