Zademy

Spring Boot 4 中的 RestClient:同步通信现代化

spring-boot; spring-framework; restclient; http-client
1426 字

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.enabledtrue 时,Spring Boot 4 中 VT 使用的客户端配置可以自动化。连接和读取超时也通过统一配置属性集中,如 spring.http.clients.connect-timeoutspring.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 做好准备。