RestTemplate 棄用!Spring 6.x+ 遷移 RestClient/WebClient 實操指南
引言
Spring團隊已正式宣布,陪伴開發(fā)者15年的經(jīng)典HTTP客戶端RestTemplate,將在Spring Framework 7.0啟動棄用計劃,并在后續(xù)版本逐步移除。
版本 | 時間 | 狀態(tài) | 影響 |
Spring Framework 7.0 | 2025 年 11 月 | 宣布棄用計劃 | 無功能移除,僅官方標記棄用方向 |
Spring Framework 7.1 | 預(yù)計 2026 年 11 月 | 正式標記 @Deprecated | 代碼中會出現(xiàn)廢棄警告,IDE 提示替代方案 |
Spring Framework 8.0 | 待定 | 徹底移除 | 無法再引入 RestTemplate 類,必須遷移 |
棄用原因
RestTemplate誕生于2009年(Spring 3.0),其設(shè)計已無法適配現(xiàn)代Java生態(tài),核心痛點可通過代碼直觀感受:
- 超時配置失效:Spring 6.0后setReadTimeout方法標注@Deprecated,直接調(diào)用會觸發(fā)警告且無效果。
// Spring 6.0 前:通過 HttpRequestFactory 設(shè)置超時(已失效)
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(5000); // 標注 @Deprecated(since = "6.0", forRemoval = true)
RestTemplate restTemplate = new RestTemplate(factory);- API冗余:新增功能依賴方法重載,導(dǎo)致同類方法大量重復(fù),例如發(fā)送POST請求:
// RestTemplate 發(fā)送 POST 的多種重載,參數(shù)順序易混淆
restTemplate.postForObject("url", request, Response.class);
restTemplate.postForEntity("url", request, Response.class);
restTemplate.postForLocation("url", request);- 異步支持薄弱:AsyncRestTemplate已被廢棄,依賴ListenableFuture處理并發(fā)時,代碼嵌套繁瑣:
// AsyncRestTemplate 已廢棄,不推薦使用
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
asyncRestTemplate.postForObject("url", request, Response.class)
.addCallback(
response -> { /* 成功處理 */ },
ex -> { /* 異常處理 */ }
);替代方案:RestClient 與 WebClient
RestClient:同步阻塞場景首選(替代 RestTemplate)
RestClient自Spring Framework 6.1引入,采用Fluent API設(shè)計,支持鏈式調(diào)用,且擴展性更強。
官方:https://docs.spring.io/spring-framework/reference/integration/rest-clients.html
案例 1:基礎(chǔ) GET 請求(帶路徑參數(shù) + 響應(yīng)解析)
// 1. 創(chuàng)建 RestClient 實例(推薦全局單例,可通過 @Bean 注入)
RestClient restClient = RestClient.create("https://api.example.com");
// 2. 發(fā)送 GET 請求,解析響應(yīng)為 User 實體
User user = restClient.get()
.uri("/users/{id}", 1001) // 路徑參數(shù):{id} 對應(yīng)值 1001
.header("Authorization", "Bearer token123") // 設(shè)置請求頭
.accept(MediaType.APPLICATION_JSON) // 聲明接收格式
.retrieve() // 執(zhí)行請求并獲取響應(yīng)
.body(User.class); // 自動解析響應(yīng)體為 User 類(依賴 MappingJackson2HttpMessageConverter)
System.out.println("用戶姓名:" + user.getName());案例 2:POST 請求(帶請求體 + 自定義異常處理)
// 1. 定義請求體(POJO 類)
@Data
class UserCreateRequest {
private String name;
private Integer age;
}
// 2. 構(gòu)建請求體對象
UserCreateRequest request = new UserCreateRequest();
request.setName("張三");
request.setAge(25);
// 3. 發(fā)送 POST 請求,處理不同響應(yīng)碼(避免 RestTemplate 需捕獲 HttpClientErrorException 的問題)
try {
User response = restClient.post()
.uri("/users")
.contentType(MediaType.APPLICATION_JSON) // 設(shè)置請求體格式
.body(request) // 傳入請求體對象(自動序列化為 JSON)
.retrieve()
// 自定義 4xx 客戶端錯誤處理
.onStatus(HttpStatusCode::is4xxClientError, (req, resp) -> {
String errorMsg = resp.getBodyAsString().block(); // 讀取錯誤響應(yīng)體
throw new RuntimeException("客戶端錯誤[" + resp.getStatusCode() + "]:" + errorMsg);
})
// 自定義 5xx 服務(wù)端錯誤處理
.onStatus(HttpStatusCode::is5xxServerError, (req, resp) -> {
throw new RuntimeException("服務(wù)端錯誤:" + resp.getStatusCode());
})
.body(User.class); // 解析成功響應(yīng)為 User 實體
System.out.println("創(chuàng)建用戶 ID:" + response.getId());
} catch (RuntimeException e) {
e.printStackTrace(); // 統(tǒng)一捕獲自定義異常
}案例 3:超時配置(替代 RestTemplate 失效的 setReadTimeout)
// 1. 配置 Socket 超時(讀取超時 5 秒,連接超時 3 秒)
SocketConfig socketConfig = SocketConfig.custom()
.setSoTimeout(5000) // 讀取超時(對應(yīng) RestTemplate 的 setReadTimeout)
.build();
// 2. 配置連接管理器(支持連接池、連接超時等)
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setDefaultSocketConfig(socketConfig)
.setConnectionTimeToLive(3000, java.util.concurrent.TimeUnit.MILLISECONDS) // 連接超時
.build();
// 3. 構(gòu)建 Apache HttpClient 實例
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.build();
// 4. 基于 HttpClient 創(chuàng)建 RestClient(注入自定義請求工廠)
RestClient restClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)) // 關(guān)聯(lián) HttpClient 配置
.baseUrl("https://api.example.com")
.build();
// 后續(xù)使用 restClient 發(fā)送請求,已包含超時配置
try {
User user = restClient.get()
.uri("/users/1001")
.retrieve()
.body(User.class);
} catch (Exception e) {
// 超時會拋出 org.springframework.web.client.ResourceAccessException
System.err.println("請求超時:" + e.getMessage());
}WebClient:異步非阻塞 / 流式場景首選
WebClient基于Reactive Streams設(shè)計,適合高并發(fā)、異步或流式處理(如SSE)場景,與Spring WebFlux無縫整合。
官方:https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html
案例 1:異步 GET 請求(非阻塞處理)
// 1. 創(chuàng)建 WebClient 實例(推薦全局單例)
WebClient webClient = WebClient.create("https://api.example.com");
// 2. 異步發(fā)送 GET 請求(返回 Mono<User>,非阻塞,不阻塞主線程)
Mono<User> userMono = webClient.get()
.uri("/users/{id}", 1001)
.header("Authorization", "Bearer token123")
.retrieve()
.bodyToMono(User.class); // 響應(yīng)體轉(zhuǎn)為 Mono(單值響應(yīng),適合單個對象)
// 3. 訂閱結(jié)果(異步處理,主線程繼續(xù)執(zhí)行其他邏輯)
userMono.subscribe(
user -> System.out.println("異步獲取用戶:" + user.getName()), // 成功回調(diào)
ex -> System.err.println("異常:" + ex.getMessage()), // 異常回調(diào)
() -> System.out.println("請求完成(無返回值時觸發(fā))") // 完成回調(diào)
);
// 主線程無需等待,直接執(zhí)行后續(xù)代碼(非阻塞特性體現(xiàn))
System.out.println("主線程繼續(xù)執(zhí)行其他任務(wù)...");案例 2:流式處理 SSE(Server-Sent Events)
// 1. 定義 SSE 事件實體
@Data
class SseEvent {
private String eventType; // 事件類型(如 "log"、"notice")
private String content; // 事件內(nèi)容
private Long timestamp; // 時間戳
}
// 2. 發(fā)送 SSE 請求,獲取流式響應(yīng)(返回 Flux<SseEvent>,多值響應(yīng))
Flux<SseEvent> eventFlux = webClient.get()
.uri("/sse/real-time-logs")
.accept(MediaType.TEXT_EVENT_STREAM) // 必須聲明 SSE 格式
.retrieve()
.bodyToFlux(SseEvent.class); // 流式響應(yīng)轉(zhuǎn)為 Flux(多值響應(yīng),持續(xù)接收)
// 3. 訂閱流式數(shù)據(jù)(持續(xù)接收服務(wù)器推送的事件)
eventFlux.subscribe(
event -> {
// 處理每一條 SSE 事件
System.out.printf("[%d] %s: %s%n",
event.getTimestamp(), event.getEventType(), event.getContent());
},
ex -> System.err.println("SSE 連接異常:" + ex.getMessage()), // 連接異常處理
() -> System.out.println("SSE 連接被服務(wù)器關(guān)閉") // 連接關(guān)閉處理
);老項目平滑遷移實操
// 老項目已有的 RestTemplate 實例(可能包含自定義配置)
RestTemplate oldRestTemplate = new RestTemplate();
// 老項目原有配置:添加日志攔截器
oldRestTemplate.setInterceptors(Collections.singletonList(new LoggingInterceptor()));
// 老項目原有配置:自定義消息轉(zhuǎn)換器(如支持 XML 解析)
oldRestTemplate.getMessageConverters().add(new MappingJackson2XmlHttpMessageConverter());
// 1. 用 RestClient 包裝老 RestTemplate,復(fù)用所有配置(零侵入)
RestClient restClient = RestClient.create(oldRestTemplate);
// 2. 新開發(fā)功能使用 RestClient,老功能繼續(xù)用 oldRestTemplate
// 新功能:RestClient 調(diào)用(享受 Fluent API 優(yōu)勢)
User newUser = restClient.get()
.uri("/users/1002")
.retrieve()
.body(User.class);
// 老功能:繼續(xù)使用 oldRestTemplate(暫不修改,避免風險)
User oldUser = oldRestTemplate.getForObject("https://api.example.com/users/1001", User.class);






























