棄用RestTemplate!RestClient真香
環(huán)境:Spring Boot3.2.5
1. 簡(jiǎn)介
從Spring Framework 6.1和Spring Boot 3.2開始,我們可以使用Spring RestClient通過(guò)流暢且同步的API執(zhí)行HTTP請(qǐng)求。RestClient基于底層的HTTP客戶端庫(kù)工作,如JDK HttpClient、Apache HttpComponents等。
顧名思義,RestClient提供了WebClient的流暢API設(shè)計(jì)和RestTemplate的功能。RestClient在設(shè)計(jì)時(shí)就考慮了可測(cè)試性,使得在單元測(cè)試中模擬HTTP交互變得更加容易。
請(qǐng)注意,對(duì)于異步和流式處理場(chǎng)景,WebClient仍然是首選的API。
RestTemplate、RestClient 和 WebClient 如何選擇?
從Spring 6.1開始,與RestTemplate相比,RestClient為同步HTTP訪問(wèn)提供了更現(xiàn)代的API。RestTemplate是Spring 3中引入的,它是一個(gè)臃腫的類,以模板類的形式暴露了HTTP的所有功能,但擁有過(guò)多的重載方法。
WebClient也支持同步HTTP訪問(wèn),但它需要額外的依賴spring-boot-starter-webflux。而使用RestClient,我們可以避免在項(xiàng)目中添加新的依賴。
RestTemplate:是一個(gè)較舊的、用于發(fā)起HTTP請(qǐng)求的同步API。它缺乏新應(yīng)用程序可能需要的靈活性和現(xiàn)代特性。
WebClient:是Spring WebFlux的反應(yīng)式、非阻塞客戶端部分。盡管它可以用于同步交互,但對(duì)于簡(jiǎn)單的用例來(lái)說(shuō),它似乎有些過(guò)于復(fù)雜。
RestClient:是Spring框架的新增成員,旨在取代RestTemplate。它提供了一個(gè)像WebClient一樣更現(xiàn)代、流暢的API,但不需要反應(yīng)式堆棧,因此在RestTemplate和WebClient之間找到了一個(gè)平衡點(diǎn)。
以下是RestTemplate與WebClient對(duì)比:
功能 | WebClient | RestTemplate |
反應(yīng)式編程 | 基于反應(yīng)式原理構(gòu)建,支持反應(yīng)式編程 | 同步,不是為反應(yīng)式編程而設(shè)計(jì)的 |
技術(shù) | 基于反應(yīng)堆棧 | 基于 Servlet 棧 |
線程模型 | 使用非阻塞 I/O,適合處理大量并發(fā)請(qǐng)求 | 使用阻塞 I/O,在高并發(fā)情況下可能導(dǎo)致線程阻塞 |
Java版本 | 需要 Java 8 或更高版本。支持函數(shù)式編程 | 兼容 Java 6 或更高版本 |
錯(cuò)誤處理 | 使用 onErrorResume、onErrorReturn 等操作符提供強(qiáng)大的錯(cuò)誤處理功能 | 錯(cuò)誤處理通常使用 try-catch 塊進(jìn)行 |
流式 | 使用 Flux 和 Mono 支持流式數(shù)據(jù),適用于反應(yīng)式流式應(yīng)用場(chǎng)景 | 對(duì)流的支持有限,不適合反應(yīng)式流 |
使用類 | 最適合微服務(wù)、反應(yīng)式應(yīng)用程序和需要高并發(fā)性的應(yīng)用場(chǎng)景 | 適用于傳統(tǒng)的單片式應(yīng)用和簡(jiǎn)單用例 |
依賴 | 需要依賴 Spring WebFlux | 需要 Spring Web 依賴關(guān)系 |
功能支持 | 與反應(yīng)式編程模式相一致,并有可能得到持續(xù)發(fā)展和支持 | 可能會(huì)進(jìn)行維護(hù)更新,但今后可能不會(huì)受到那么多關(guān)注 |
接下來(lái),我將詳細(xì)的介紹RestClient的使用。
2. 實(shí)戰(zhàn)案例
2.1 創(chuàng)建RestClient
Spring 允許使用多種靈活的方法來(lái)初始化 RestClient Bean。例如,最簡(jiǎn)單的方法是使用 create() 方法。
@Value("${pack.remote.address:http://www.pack.com}")
private String baseURI;
@Bean
public RestClient restClient() {
return RestClient.create(baseURI) ;
}
我們還可以使用 builder() 方法來(lái)設(shè)置更復(fù)雜的選項(xiàng),如默認(rèn)頭、請(qǐng)求處理器、消息處理程序等。例如,下面的配置使用 HttpClient 作為 HTTP 連接管理的底層庫(kù)。
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory
= new HttpComponentsClientHttpRequestFactory() ;
clientHttpRequestFactory.setHttpClient(httpClient) ;
// 更多配置
return clientHttpRequestFactory ;
}
@Bean
public RestClient restClient(CloseableHttpClient httpClient) {
return RestClient.builder()
.baseUrl(baseURI)
// 更多配置
.requestFactory(clientHttpRequestFactory())
.build() ;
}
甚至,我們還可以直接通過(guò)RestTemplate來(lái)構(gòu)建RestClient對(duì)象
@Bean
public RestClient restClient(RestTemplate restTemplate) {
return RestClient.create(restTemplate);
}
2.2 HTTP Get請(qǐng)求
restClient.get() 用于向指定的 URL 創(chuàng)建 GET 請(qǐng)求。請(qǐng)注意,我們可以將動(dòng)態(tài)值傳遞給 URI 模板。
@Resource
private RestClient restClient ;
restClient.get()
.uri("/users")
//...
restClient.get()
.uri("/employees/{id}", id)
//...
最后,retrieve() 方法發(fā)送請(qǐng)求并返回包含 API 響應(yīng)的 ResponseSpec。下面的請(qǐng)求將獲取用戶列表,并將響應(yīng)體解析為User實(shí)例列表。
List<User> list = restClient.get()
.uri("/users")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(List.class) ;
我們還可以處理,如請(qǐng)求的狀態(tài)、請(qǐng)求header等數(shù)據(jù),可以按如下方式獲取響應(yīng)實(shí)體(ResponseEntity):
ResponseEntity<List> responseEntity = restClient.get()
.uri("/users")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(List.class) ;
2.3 HTTP Post請(qǐng)求
restClient.post() 用于處理 POST 請(qǐng)求。除了 POST API 通常不返回任何響應(yīng)外,其他大部分與 GET API 調(diào)用相同。toBodilessEntity() 方法具有完全相同的功能,可用于 POST API。
User user = new User(666L, "張三", 22) ;
ResponseEntity<Void> responseEntity = restClient.post()
.uri("/users")
.contentType(MediaType.APPLICATION_JSON)
.body(user)
.retrieve()
.toBodilessEntity() ;
2.4 HTTP Put請(qǐng)求
restClient.put() 用于處理 PUT 請(qǐng)求。PUT API 通常會(huì)發(fā)送一個(gè)請(qǐng)求正文并接收一個(gè)響應(yīng)正文,如下示例。
User user = new User(666L, "張三", 22) ;
ResponseEntity<User> responseEntity = restClient.put()
.uri("/users/666")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(user)
.retrieve()
.toEntity(User.class) ;
2.5 HTTP Delete請(qǐng)求
restClient.delete() 用于處理 DELETE 請(qǐng)求。一般來(lái)說(shuō),delete API 在服務(wù)器中接受,并且不會(huì)要求響應(yīng)體。
ResponseEntity<Employee> responseEntity = restClient.delete()
.uri("/users/666")
.retrieve()
.toBodilessEntity() ;
2.6 RestClient復(fù)雜應(yīng)用
如果我們需要完全控制響應(yīng)處理,可以使用 exchange() 方法。它提供了對(duì) HttpRequest 和 HttpResponse 對(duì)象的訪問(wèn)權(quán)限,然后我們就可以按自己的需要使用它們了。
List<Employee> list = restClient.get()
.uri("/users")
.accept(MediaType.APPLICATION_JSON)
.exchange((request, response) -> {
List response = null ;
if (response.getStatusCode().is4xxClientError()
|| response.getStatusCode().is5xxServerError()) {
System.err.println("請(qǐng)求錯(cuò)誤") ;
} else {
response = new ObjectMapper().readValue(response.getBody(), List.class) ;
}
return response ;
}) ;
2.7 異常處理
對(duì)于失敗的請(qǐng)求,RestClient 會(huì)拋出兩種異常:
HttpClientErrorException:帶 4xx 響應(yīng)代碼
HttpServerErrorException:響應(yīng)代碼為 5xx
try {
User user = restClient.get()
.uri("/users/666")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(User.class) ;
} catch(HttpClientErrorException e4xx) {
// ...
} catch(HttpServerErrorException e5xx) {
// ...
}
2.8 自定義攔截器
我們可以通過(guò)RestClient.Builder設(shè)置攔截器,通過(guò)攔截器我們可以進(jìn)行日志的記錄,認(rèn)證的配置等等,如下示例:
@Bean
public RestClient demoRestClient(LoggingRestClientInterceptor loggingInterceptor) {
return RestClient
.builder()
.requestInterceptor(loggingInterceptor)
.build() ;
}
自定義攔截器
@Component
public static class LoggingRestClientInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
System.err.println("logging...") ;
return execution.execute(request, body) ;
}
}
注:RestTemplate與RestClient使用的攔截器是相同的。所以你可以復(fù)用之前寫的攔截器。
2.9 基于服務(wù)發(fā)現(xiàn)負(fù)載均衡
從Spring Cloud 4.1.0開始RestClient支持服務(wù)發(fā)現(xiàn)的負(fù)載均衡。在RestTemplate時(shí)代,我們?cè)诙xRestTemplate bean對(duì)象時(shí),只要添加了@LoadBalanced注解,那么我們的RestTemplate就可以通過(guò)服務(wù)名的方式遠(yuǎn)程調(diào)用。
RestClient要通過(guò)服務(wù)發(fā)現(xiàn)機(jī)制調(diào)用,那么我們就要自定義RestClient.Builder對(duì)象。
@Bean
@LoadBalanced
public RestClient.Builder demoRestClientBuilder() {
Builder builder = RestClient.builder().baseUrl("http://demo") ;
return builder ;
}
接下來(lái),我們就可以通過(guò)上面自定義的RestClient.Builder來(lái)構(gòu)建RestClient。
@Bean
public RestClient lbcRestClient(RestClient.Builder builder) {
return builder
.baseUrl("http://demo")
.build() ;
}
這樣配置,我們的RestClient就具備服務(wù)發(fā)現(xiàn)的能力了。