RestTemplate这次真要退场了Spring Boot 4.2已经开始弃用,项目HTTP调用全部换成了RestClient
前几天升级 Spring Boot 依赖时,我顺手看了一眼 4.2 的 Release Notes。
看到一行熟悉的名字:
RestTemplate
这次不是增加功能,而是:
deprecated。
准确来说,Spring Framework 已经明确将 RestTemplate 标记为弃用,并推荐迁移到 RestClient;刚发布的 Spring Boot 4.2.0-M1 进一步把 RestTemplateAutoConfiguration、RestTemplateBuilder、TestRestTemplate 等配套基础设施一起放进了弃用名单。
我看完之后第一反应其实不是:
“Spring 又造了一个 HTTP 客户端。”
而是打开公司一个老项目搜了一下:
RestTemplate
结果:
47处。
支付、短信、用户中心、地图服务、物流查询……
基本哪儿都有。
于是花了点时间把其中一条调用链改成了 RestClient。
改完之后我的感觉是:
这次确实可以开始迁了。
RestTemplate的问题不是不能用,而是时代已经过去了
先说一句公道话。
RestTemplate 并不好用吗?
其实挺好用。
很多 Java 项目现在可能还是这种代码:
UserResponse response = restTemplate.getForObject( ”http://user-service/api/users/{id}”, UserResponse.class, userId );
POST:
ResponseEntity response = restTemplate.postForEntity( url, request, PayResponse.class );
干了这么多年 Java,基本不用查文档就能写出来。
所以这几年哪怕 WebClient 已经很成熟,我也一直没有为了“新”强行把同步项目全部改成 Reactor。
原因很简单:
我的业务本来就是:
Controller ↓Service ↓调用第三方HTTP ↓等待结果 ↓继续处理
非得为了发一个 HTTP 请求变成:
Mono
很多时候反而把简单事情复杂化了。
而 RestClient 比较讨巧的一点就在这里:
它还是同步 HTTP Client。
不需要改响应式编程。
Spring 官方现在对三者的定位也很直接:
RestClient同步、Fluent API WebClient非阻塞、Reactive RestTemplate旧的同步Template API,已弃用
所以对绝大多数普通 Spring MVC 项目来说,RestClient 才是最自然的接班人。
第一个接口改完,我发现代码确实顺眼了一些
以前项目里有一个物流查询:
public LogisticsResult query( String trackingNo) { String url = logisticsUrl + ”/api/tracking/” + trackingNo; ResponseEntity response = restTemplate.getForEntity( url, LogisticsResult.class ); return response.getBody();}
换成 RestClient:
public LogisticsResult query( String trackingNo) { return restClient .get() .uri( ”/api/tracking/{trackingNo}”, trackingNo ) .retrieve() .body(LogisticsResult.class);}
第一眼可能觉得:
好像也没少多少代码。
但真正舒服的是后面的组合。
比如 POST:
PayResponse response = restClient .post() .uri(”/api/pay”) .contentType( MediaType.APPLICATION_JSON ) .body(request) .retrieve() .body(PayResponse.class);
加 Header:
restClient .get() .uri(”/api/orders/{id}”, orderId) .header( ”Authorization”, ”Bearer ” + token ) .retrieve() .body(OrderResponse.class);
整个调用顺序基本就是 HTTP 请求真正发生的顺序:
GET↓URL↓Header↓Body↓发送↓Response
Spring 官方对 RestClient 的定位也是“同步、Fluent API 的 HTTP Client”,同时继续使用 Spring 的消息转换等基础设施。
没有什么学习成本。
真正的项目不要到处RestClient.create()
网上 Demo 很容易看到:
RestClient restClient = RestClient.create();
然后直接用了。
自己玩没问题。
项目里我不会这么干。
因为真实系统调用第三方服务,一定会逐渐出现:
统一地址 Authorization TraceId User-Agent 日志 超时 监控 错误处理
这些东西如果散落在几十个 Service 里面,两个月以后又会变成另一个 RestTemplate 项目。
我现在一般会按外部系统分别创建 Client:
@Configurationpublic class HttpClientConfig { @Bean RestClient logisticsRestClient( RestClient.Builder builder) { return builder .baseUrl( ”https://logistics.example.com” ) .defaultHeader( HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE ) .requestInterceptor( new TraceIdInterceptor() ) .build(); }}
业务代码里只注入:
private final RestClient logisticsRestClient;
这样 Logistics Service 根本不用知道:
域名是什么Header怎么加TraceId怎么传底层HTTP Client是谁
Spring Boot 本身会提供 RestClient.Builder 的自动配置能力,所以这种写法也更符合 Boot 的使用方式。
我现在基本遵循一个原则:
一个外部系统,一个明确的 HTTP Client 配置。
别搞一个万能:
restClient
全公司随便调用。
最后一定失控。
还有一个我以前经常写错的地方:4xx和5xx不能都当异常结束
比如支付服务返回:
HTTP/1.1 409 Conflict
Body:
{ ”code”: ”ORDER_ALREADY_PAID”, ”message”: ”订单已经支付”}
这在 HTTP 层是 409。
但业务上它未必意味着:
系统故障
可能只是:
用户重复点击了支付。
如果把所有非 2xx 都粗暴转换成:
RuntimeException
后面监控会非常难看。
我现在会把 HTTP 错误和业务错误分开处理。
例如:
PayResponse response = restClient .post() .uri(”/api/pay”) .body(request) .retrieve() .onStatus( status -> status.value() == 409, (req, res) -> { throw new OrderAlreadyPaidException(); } ) .body(PayResponse.class);
真正的:
500502503
则进入另外的异常处理链路。
这看起来只是 API 写法变化。
实际上顺手把以前项目里一个长期存在的问题也解决了:
HTTP失败≠业务失败
两个概念最好一开始就分开。
更让我感兴趣的是,下一步连Client实现类都可以不写
把 RestTemplate 改成 RestClient 后,我又顺着 Spring 文档看了一下 HTTP Service Client。
这个东西其实更有意思。
以前写远程用户服务:
@Componentpublic class UserClient { private final RestClient restClient; public UserResponse getUser( Long userId) { return restClient .get() .uri( ”/users/{id}”, userId ) .retrieve() .body(UserResponse.class); }}
现在可以直接定义:
@HttpExchange(”/users”)public interface UserClient { @GetExchange(”/{id}”) UserResponse getUser( @PathVariable Long id );}
就这一份接口。
Spring 可以通过代理生成真正的 HTTP Client 实现。
官方当前也把 HTTP Service Client 和 RestClient、WebClient 一起列为 REST 调用方案,它本质上就是通过带注解的 Java Interface 描述远程 HTTP API,再生成代理。
这套写法让我第一时间想到:
@FeignClient
但区别是:
这是 Spring Framework 自己的能力。
如果项目只是需要:
声明式REST Client
以后是不是还一定需要额外引入 OpenFeign,就值得重新评估了。
至少新项目我会优先试 Spring 自己这套。
迁移不用一次改完,这一点很重要
47 个 RestTemplate 调用,如果让我一个版本全部改掉,我肯定不会干。
这种基础组件最怕:
代码看着更漂亮↓上线以后第三方请求全出问题
Spring 官方其实已经给了一个很实际的渐进迁移方案。
可以直接从已有的 RestTemplate 创建:
RestClient restClient = RestClient.create( restTemplate );
这样原来已经配置好的:
RequestFactory Interceptor MessageConverter
等基础设施还可以继续利用,然后先逐步把调用 API 从:
restTemplate.getForObject(...)
替换成:
restClient .get() .retrieve()
等所有调用迁完,再重新整理 RestClient.Builder 配置。Spring 官方迁移指南就是建议分阶段做,而不是一次推倒重来。
我觉得这个方式比较符合真实公司项目。
先改:
低风险第三方接口
再改:
内部服务调用
支付、订单这类核心链路放最后。
别为了追版本搞“大扫除式重构”。
测试也开始换代了
还有一个信号挺明显。
Spring Boot 4.2.0-M1 不只弃用了:
RestTemplateBuilder
连:
TestRestTemplate
也一起进入弃用流程。
官方推荐的一个明显替代方案叫:
RestTestClient
例如:
RestTestClient client = RestTestClient .bindToController( new OrderController( orderService ) ) .build();
然后:
client.get() .uri(”/orders/10001”) .exchange() .expectStatus() .isOk() .expectBody(OrderResponse.class);
它比较实用的一点是:
既可以:
不启动真实Server测试Controller
也可以:
连接真实HTTP Server做端到端测试
Spring Framework 官方把它定义成建立在 RestClient 之上的测试客户端。
所以这次变化不像以前那种:
新增一个类,你愿意用就用。
从生产调用,到 Boot 自动配置,再到测试工具,Spring 整条链路都开始往:
RestClient
迁移了。
这个信号其实已经相当明确。
现在要不要马上改?
如果项目还在:
Spring Boot 2.xSpring Boot 3.x
而且 RestTemplate 跑得非常稳定,我不会建议今天看完文章,明天就开一个:
refactor: replace all RestTemplate
然后改 300 个文件。
没必要。
而且 Spring Boot 4.2.0-M1 本身还是Milestone 版本,不是让生产系统为了这件事马上升级的。
但如果现在正在做:
新项目 新的第三方接口 新的微服务调用 Spring Boot 4.x升级
我不会再新增:
new RestTemplate()
了。
直接用:
RestClient
如果接口比较多,再进一步考虑:
@HttpExchange
这会是我现在的选择。
Spring Framework 当前文档已经明确写着:RestTemplate 被弃用,并将在未来版本移除;Spring Boot 4.2 又开始清理它周围的自动配置和测试基础设施。
所以这次不是:
“RestTemplate马上不能运行了。”
真正的意思是:
Spring已经不希望新代码继续往这个方向长了。
这两个说法差别很大。
老项目不用慌。
新代码就别再往旧路上加了。
我已经把项目里的:
47处RestTemplate
列进了迁移清单。
不会一次全改。
但下一次再新增 HTTP 调用,代码大概率会从这一行开始:
restClient .get()
而不是那个陪了很多 Java 程序员十几年的:
restTemplate .getForObject(...)
一个类真正开始退场,往往不是它被删除的那一天。
而是:
我们写下一段新代码时,已经不再选择它。
