Spring7新宠:RestClient全面解析

一、基础定位与版本说明

  1. 诞生背景 RestClient 自 Spring 6.1 引入,Spring 7 正式定为同步HTTP客户端首选RestTemplate 被标记废弃,新项目强制推荐 RestClient。

  2. 核心优势

  • 流式链式API,风格贴近 WebClient,易上手;

  • 线程安全,单例Bean全局复用;

  • 统一底层 ClientHttpRequestFactory,无缝切换Apache HttpClient/JDK HttpClient/Jetty;

  • 内置结构化JSON序列化、灵活状态异常处理、拦截器、全局默认Header/Cookie;

  • 支持 retrieve() 简易模式、exchange() 底层原始响应模式;

  • 完美搭配 Spring 7 HTTP Interface 声明式接口客户端。

  1. 与其他客户端对比

    RestClient | Spring MVC 同步业务、微服务普通调用 | 阻塞 | 官方首选

    WebClient | WebFlux响应式、高并发流式 | 非阻塞 | 响应式专用

    RestTemplate | 老旧存量项目 | 阻塞 | @Deprecated,不再新增特性 |

二、环境依赖

仅需 spring-web,Spring Framework 7 自动包含 RestClient:

复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>7.0.0</version>
</dependency>

如需连接池、超时精细化控制,引入Apache HttpClient:

复制代码
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

三、RestClient 实例创建(3种方式)

1. 最简快速创建(默认JDK底层)

复制代码
// 无全局配置,临时使用
RestClient restClient = RestClient.create();

2. Builder 完整自定义(项目标准Bean写法,推荐)

复制代码
import org.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.http.client.HttpComponentsClientHttpRequestFactory;
importorg.springframework.web.client.RestClient;
importjava.time.Duration;

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient restClient() {
        // 底层工厂:ApacheHttpClient 支持连接池、超时
        HttpComponentsClientHttpRequestFactoryfactory = newHttpComponentsClientHttpRequestFactory();
        factory.setConnectTimeout(Duration.ofSeconds(5));    // 连接超时
        factory.setReadTimeout(Duration.ofSeconds(15));      // 读取超时
        factory.setConnectionRequestTimeout(Duration.ofSeconds(3)); // 从连接池获取连接超时

        returnRestClient.builder()
                // 全局基础地址,后续请求可只写路径
                .baseUrl("https://api.example.com")
                // 全局路径变量
                .defaultUriVariables(Map.of("version", "v1"))
                // 全局默认请求头
                .defaultHeader("Content-Type", "application/json")
                .defaultHeader("User-Agent", "spring7-restclient")
                // 全局Cookie
                .defaultCookie("token", "global-token-xxx")
                // 请求拦截器(日志、鉴权、重试统一处理)
                .requestInterceptor(newLogInterceptor())
                // 请求初始化器,统一修改请求
                .requestInitializer(request-> request.getHeaders().set("Trace-Id", UUID.randomUUID().toString()))
                // 自定义消息转换器
                .messageConverters(converters-> {
                    // 追加自定义Jackson转换器
                })
                // 全局统一异常处理
                .defaultStatusHandler(
                        HttpStatusCode::isError,
                        (req, res) -> {
                            throw new BusinessApiException(res.getStatusCode(), "上游接口异常");
                        }
                )
                // 指定底层HTTP工厂
                .requestFactory(factory)
                .build();
    }
}

3. 从 RestTemplate 迁移创建

复制代码
RestTemplate oldTemplate = new RestTemplate();
RestClient restClient = RestClient.create(oldTemplate);

四、核心请求语法全示例

通用流程

  1. 方法:get()/post()/put()/delete()/patch()/method(HttpMethod)

  2. URI:uri() 支持占位符、参数构建器

  3. 请求配置:header、cookie、body、multipart

  4. 响应分支二选一:

    • retrieve():日常使用,内置状态码异常

    • exchange():底层原始响应,完全自定义异常逻辑

1. GET 请求(路径变量 + 查询参数)

复制代码
// 1. 基础路径变量
UserDTOuser = restClient.get()
        .uri("/user/{id}", 1001)
        .header("Authorization", "Bearerxxx")
        .retrieve()
        .onStatus(HttpStatusCode::is4xxClientError, (req, resp) -> {
            throw new UserNotFoundException("用户不存在");
        })
        .body(UserDTO.class);

// 2. 动态拼接查询参数
List<OrderDTO> orders = restClient.get()
        .uri(uriBuilder-> uriBuilder
                .path("/order/list")
                .queryParam("status", 1)
                .queryParam("page", 1)
                .queryParam("size", 20)
                .build())
        .retrieve()
        .body(newParameterizedTypeReference<List<OrderDTO>>() {});

2. POST JSON 请求(传对象)

复制代码
CreateOrderReq req = newCreateOrderReq();
req.setUserId(1001);
req.setAmount(newBigDecimal("99.9"));

ResponseEntity<OrderResp> respEntity = restClient.post()
        .uri("/order/create")
        .body(req) // 自动序列化为JSON
        .retrieve()
        .toEntity(OrderResp.class);

OrderRespbody = respEntity.getBody();
HttpHeadersheaders = respEntity.getHeaders();
HttpStatusCodestatus = respEntity.getStatusCode();

3. PUT / DELETE

复制代码
// PUT 更新
restClient.put()
        .uri("/user/{id}", 1001)
        .body(updateDTO)
        .retrieve()
        .toBodilessEntity();

// DELETE 无返回体
restClient.delete()
        .uri("/user/{id}", 1001)
        .retrieve()
        .toBodilessEntity();

4. Multipart 文件上传(文件+表单+JSON混合)

复制代码
MultipartBodyBuilder multipartBuilder = newMultipartBodyBuilder();
// 文件
multipartBuilder.part("file", newFileSystemResource("/tmp/test.jpg"))
        .filename("upload.jpg");
// 普通表单字段
multipartBuilder.part("desc", "测试图片");
// JSON子对象
multipartBuilder.part("meta", "{\"type\":\"img\"}", MediaType.APPLICATION_JSON);

StringuploadResult = restClient.post()
        .uri("/upload/file")
        .contentType(MediaType.MULTIPART_FORM_DATA)
        .body(multipartBuilder.build())
        .retrieve()
        .body(String.class);

5. Form 表单提交 application/x-www-form-urlencoded

复制代码
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("username", "admin");
formData.add("password", "123456");

TokenResp token = restClient.post()
        .uri("/login")
        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
        .body(formData)
        .retrieve()
        .body(TokenResp.class);

五、两种响应模式:retrieve() vs exchange()

1. retrieve()(90%业务场景使用)

  • 自动判定4xx/5xx并抛出异常,可局部 onStatus 覆盖

  • 简洁API:body() / toEntity() / toBodilessEntity()

    // 直接转实体
    UserDTO dto = restClient.get().uri("/user/1").retrieve().body(UserDTO.class);
    // 返回完整ResponseEntity
    ResponseEntity entity = restClient.get().uri("/user/1").retrieve().toEntity(UserDTO.class);
    // 无响应体
    restClient.delete().uri("/user/1").retrieve().toBodilessEntity();

2. exchange()(高级底层场景)

不会自动抛状态异常,完全手动处理响应,适合:

  • 404/500需要正常读取返回体

  • 下载二进制流、手动读取响应流

  • 自定义所有错误逻辑

    // 手动处理所有状态码
    RestClient.ResponseSpecresponseSpec = restClient.get()
    .uri("/user/{id}", 9999)
    .exchange();

    // 手动判断状态
    if (responseSpec.statusCode().is2xxSuccessful()) {
    UserDTO user = responseSpec.body(UserDTO.class);
    } elseif (responseSpec.statusCode().value() == 404) {
    // 不存在返回空
    return null;
    } else {
    throw new RuntimeException("请求异常:" + responseSpec.statusCode());
    }

    // 二进制文件下载
    InputStreaminputStream = restClient.get()
    .uri("/file/download")
    .exchange()
    .body(InputStream.class);

六、异常处理体系

1. 统一异常父类

所有异常均为 RestClientException 子类:

  • HttpClientErrorException:4xx(NotFound/BadRequest等)

  • HttpServerErrorException:5xx

  • ResourceAccessException:网络超时、连接失败、DNS错误

  • UnknownContentTypeException:返回类型不匹配、序列化失败

2. 三种异常拦截方式

方式1:Builder全局统一异常(所有请求生效)
复制代码
RestClient.builder()
    .defaultStatusHandler(HttpStatusCode::isError, (req, res) -> {
        log.error("上游异常 status={}", res.getStatusCode());
        throw new ApiCallException(res.getStatusCode(), res.body(String.class));
    })
    .build();
方式2:单次请求局部onStatus(覆盖全局)
复制代码
restClient.get()
    .uri("/xxx")
    .retrieve()
    .onStatus(HttpStatusCode::is4xxClientError, (req, resp) -> {
        throw new ClientParamException("参数错误");
    })
    .onStatus(HttpStatusCode::is5xxServerError, (req, resp) -> {
        throw new RemoteServiceException("服务宕机");
    })
    .body(String.class);
方式3:业务层try-catch捕获
复制代码
try {
    return restClient.get().uri("/user/1").retrieve().body(UserDTO.class);
} catch (HttpClientErrorException.NotFound e) {
    log.warn("用户不存在");
    return null;
} catch (ResourceAccessException e) {
    throw new RuntimeException("第三方接口超时");
} catch (RestClientException e) {
    throw new RuntimeException("调用接口失败", e);
}

七、拦截器实现(日志、链路追踪、重试)

实现 ClientHttpRequestInterceptor,Builder注入 requestInterceptor()

示例:全局请求日志拦截器

复制代码
@Slf4j
public class LogInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        log.info("请求地址:{} 方法:{} 请求体:{}",
                request.getURI(), request.getMethod(), new String(body, StandardCharsets.UTF_8));
        ClientHttpResponse response = execution.execute(request, body);
        log.info("响应状态:{}", response.getStatusCode());
        return response;
    }
}

重试拦截器(仅5xx自动重试)

复制代码
public classRetryInterceptorimplementsClientHttpRequestInterceptor {
    private final int maxRetry;
    public RetryInterceptor(int maxRetry) { this.maxRetry = maxRetry; }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        inttimes = 0;
        while (true) {
            try {
                return execution.execute(request, body);
            } catch (HttpServerErrorExceptione) {
                times++;
                if (times > maxRetry) throw e;
                log.warn("5xx异常,第{}次重试", times);
            }
        }
    }
}

八、底层请求工厂切换与连接池配置

Spring7 自动按类路径优先级选择工厂:

  1. HttpComponentsClientHttpRequestFactory(Apache HttpClient,推荐,支持连接池)

  2. JettyClientHttpRequestFactory

  3. JdkClientHttpRequestFactory(JDK11+内置HttpClient)

  4. SimpleClientHttpRequestFactory(JDK HttpURLConnection,无连接池)

Apache HttpClient 连接池完整配置

复制代码
@Bean
public ClientHttpRequestFactory httpRequestFactory() {
    // 连接池管理器
    PoolingHttpClientConnectionManagerpoolManager = newPoolingHttpClientConnectionManager();
    poolManager.setMaxTotal(200);        // 最大总连接数
    poolManager.setDefaultMaxPerRoute(50);// 单域名最大连接

    CloseableHttpClienthttpClient = HttpClients.custom()
            .setConnectionManager(poolManager)
            .evictIdleConnections(Duration.ofSeconds(30)) // 清理空闲连接
            .build();

    HttpComponentsClientHttpRequestFactoryfactory = newHttpComponentsClientHttpRequestFactory(httpClient);
    factory.setConnectTimeout(Duration.ofSeconds(5));
    factory.setReadTimeout(Duration.ofSeconds(10));
    factory.setConnectionRequestTimeout(Duration.ofSeconds(3));
    returnfactory;
}

@Bean
public RestClient restClient(ClientHttpRequestFactory factory) {
    returnRestClient.builder()
            .requestFactory(factory)
            .build();
}

九、Spring7 HTTP Interface 声明式调用(搭配RestClient)

类似Feign,零冗余链式代码,底层复用RestClient:

  1. 定义接口

    @HttpExchange("/api/v1")
    public interface UserApiClient {

    复制代码
     @GetExchange("/user/{id}")
     UserDTO getUser(@PathVariable Long id);
    
     @PostExchange("/user/create")
     UserDTO createUser(@RequestBody CreateUserReq req);

    }

  2. 注册代理Bean

    @Bean
    public UserApiClient userApiClient(RestClient restClient) {
    HttpServiceProxyFactory factory = HttpServiceProxyFactory
    .builderFor(restClient)
    .build();
    return factory.createClient(UserApiClient.class);
    }

  3. 直接注入调用

    @Service
    public class UserService {
    privatefinalUserApiClientuserApiClient;
    publicUserService(UserApiClientuserApiClient) { this.userApiClient = userApiClient; }
    publicUserDTOget(Longid) {
    return userApiClient.getUser(id);
    }
    }

十、RestTemplate 迁移对照表

RestTemplate RestClient 等价写法
getForObject(url, Cls, args) get().uri(url, args).retrieve().body(Cls)
postForEntity(url, req, Cls) post().uri(url).body(req).retrieve().toEntity(Cls)
exchange(url, POST, entity, Cls) post().uri(url).headers(entity.getHeaders()).body(entity.getBody()).exchange()
setErrorHandler builder.defaultStatusHandler / 单次onStatus
addInterceptors builder.requestInterceptor()

十一、最佳实践总结

  1. 统一单例Bean:全局只创建一个RestClient,复用连接池;

  2. 优先builder全局配置:baseUrl、默认Header、拦截器、全局异常统一配置;

  3. 高并发必须使用Apache HttpClient连接池,禁用Simple工厂;

  4. 简单请求用 retrieve(),下载流/自定义异常用 exchange()

  5. 大量微服务接口推荐 HTTP Interface + RestClient,简化代码;

  6. 所有远程调用捕获 RestClientException,区分网络超时、4xx参数、5xx服务异常;

  7. 存量项目逐步替换RestTemplate,新项目禁止使用RestTemplate。

相关推荐
大模型码小白2 小时前
Spring AI 框架实战:Java 后端集成大模型的架构设计与工程落地
java·人工智能·python·spring
霸道流氓气质5 小时前
基于 Spring 事务同步机制的事务后置动作收集器 Starter 实践
java·后端·spring
刘小八6 小时前
Spring AI Tool Calling 生产化:参数校验、权限控制与超时隔离
java·人工智能·spring
gaolei_eit20 小时前
Java+Ai+vue
java·spring·maven
玉&心20 小时前
关于配置mcp服务端sse-message-endpoint和sse-endpoint的注意点
spring·springai·mcp
蚰蜒螟1 天前
一次 Spring AOP 与定时任务引发的死锁排查实录
java·spring·firefox
Java爱好狂.1 天前
Java就业需要学习哪些内容?
spring·程序员·springboot·架构师·java面试·java面试题·java八股文
listening7771 天前
HarmonyOS 6.1 元服务深度优化:从“秒开”到“常驻”的极致体验
java·开发语言·spring