1. 簡介
RestClient 是 Spring Framework 6.1 M2 中引入的同步 HTTP 客户端,取代了 RestTemplate。同步 HTTP 客户端以阻塞方式發送和接收 HTTP 請求和響應,這意味着它在每個請求完成之前不會繼續到下一個請求。
在本教程中,我們將探索 RestClient 的優勢,以及它與 RestTemplate 的比較。
2. RestClient 和 RestTemplate
RestTemplate,正如其名稱所示,基於模板設計模式構建。 這是一個行為設計模式,它在方法中定義算法的骨架,允許子類為某些步驟提供特定實現。 儘管它是一個強大的模式,但它會產生需要重載的需求,這可能會不便。
為了改進這一點,RestClient 具有流式 API。 流式 API 是一種設計模式,它允許通過在對象上順序調用方法來實現方法鏈,通常無需中間變量。
讓我們從創建基本的 RestClient 開始:
RestClient restClient = RestClient.create();
3. 簡單的 HTTP 請求方法 Fetching
類似於 RestTemplate 或任何其他 rest 客户端,RestClient 允許我們使用請求方法進行 HTTP 調用。讓我們逐步瞭解不同的 HTTP 方法,用於創建、檢索、修改和刪除資源。
我們將操作一個 elementary 的 Article 類:
public class Article {
Integer id;
String title;
// constructor and getters
}
3.1. 使用 GET 檢索資源
我們將使用 GET HTTP 方法來請求和檢索指定 Web 服務器上的數據,而無需修改它。 它主要用於 Web 應用程序的只讀操作。
為了開始,讓我們獲取一個簡單的 String 作為響應,而無需進行任何序列化到我們的自定義類:
String result = restClient.get()
.uri(uriBase + "/articles")
.retrieve()
.body(String.class);
3.2. 使用 POST 創建資源
我們將使用 POST HTTP 方法向 Web 服務器上的資源提交數據,通常用於在 Web 應用程序中創建新記錄或資源。 與 GET 方法不同,它檢索數據,POST 用於將數據發送到服務器進行處理,例如提交 Web 表單。
URI 應該定義我們想要處理的資源。
讓我們將一個 ID 等於 1 的簡單 Article 發送到我們的服務器:
Article article = new Article(1, "How to use RestClient");
ResponseEntity<Void> response = restClient.post()
.uri(uriBase + "/articles")
.contentType(APPLICATION_JSON)
.body(article)
.retrieve()
.toBodilessEntity();
由於我們指定了 APPLICATION_JSON 內容類型,因此 Article 類的實例將在底層由 Jackson 庫自動序列化為 JSON。在此示例中,我們使用 toBodilessEntity() 方法忽略響應主體。POST 端點通常不需要,並且通常不返回任何內容。
3.3. 使用 PUT 更新資源
接下來,我們將研究 PUT HTTP 方法,該方法用於使用提供的數據更新或替換現有資源。 它通常用於修改現有實體或其他 Web 應用程序中的資源。通常,我們需要指定要更新的資源,確保完全替換。
讓我們修改我們在上一段中創建的文章。我們提供的 URI 應該標識我們要更改的資源:
Article article = new Article(1, "How to use RestClient even better");
ResponseEntity<Void> response = restClient.put()
.uri(uriBase + "/articles/1")
.contentType(APPLICATION_JSON)
.body(article)
.retrieve()
.toBodilessEntity();
類似於上一段,我們將依賴 RestClient 來序列化我們的內容並忽略響應。
3.4. 使用 DELETE 刪除資源
我們將使用 DELETE HTTP 方法來請求從 Web 服務器刪除指定資源。 類似於 GET 端點,我們通常不為請求提供任何內容,而是依賴 URI 中編碼的參數:
ResponseEntity<Void> response = restClient.delete()
.uri(uriBase + "/articles/1")
.retrieve()
.toBodilessEntity();
4. 支持請求屬性
關於支持像 WebClient 那樣的請求屬性類型,重要的是要理解 WebClient 引入了屬性以解決一個限制,具體來説,針對反應式環境的限制。 這種限制是缺乏可靠的線程本地。 由於 RestTemplate 和 RestClient 可以使用線程本地,因此我們很少需要請求屬性。
使用 RestClient 的屬性的一些用例,例如將屬性傳遞給攔截器,確實存在。 為了促進這一點,Spring Framework 6.2 添加了 getAttributes 到 org.springframework.http.HttpRequest 接口。 它返回了用於 HttpRequest 請求的可變屬性映射。 我們可以創建一個攔截器來更新請求屬性:
@Test
void updateRequestAttribute() throws Exception {
String attrName = "attr1";
String attrValue = "value1";
assertDoesNotThrow(() -> {
ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
request.getAttributes().put(attrName, attrValue);
return execution.execute(request, body);
};
});
}
在大多數其他情況下,儘管如此,我們可以基於映射構建帶有查詢參數的請求 URI,而無需使用請求屬性 API,例如:
RestClient restClient = RestClient.builder()
.baseUrl("https://example.com/api")
.build();
String pathVariable = "pathVariable";
ResponseEntity response = restClient.get()
.uri(uriBuilder -> uriBuilder
.path("/" + pathVariable)
.queryParam("param1", "value1")
.queryParam("param2", "value2")
.build())
.header("Content-Type", "application/json")
.retrieve()
.toEntity(String.class)
.block();
5. 反序列化響應
我們經常希望序列化請求並反序列化響應到我們能夠高效操作的類。 RestClient 具備執行 JSON 到對象轉換的功能,該功能由 Jackson 庫提供支持。
此外,由於共享消息轉換器的使用,我們可以使用 RestTemplate 的所有數據類型。
讓我們通過其 ID 檢索一篇文章,並將其序列化到 Article 類實例:
Article article = restClient.get()
.uri(uriBase + "/articles/1")
.retrieve()
.body(Article.class);
當我們需要獲取一些泛型類的實例,例如 List 時,指定 body 的類會變得更加複雜。 例如,如果我們想獲取所有文章,我們將會得到 List<Article> 對象。 在這種情況下,我們可以使用抽象類 ParameterizedTypeReference 來告訴 RestClient 我們將獲得什麼對象。
我們甚至不需要指定泛型類型,Java 會自動為我們推斷類型:
List<Article> articles = restClient.get()
.uri(uriBase + "/articles")
.retrieve()
.body(new ParameterizedTypeReference<>() {});
6. 解析響應與 Exchange
RestClient 包含 exchange() 方法,用於處理更高級的情況,通過提供對底層 HTTP 請求和響應的訪問權限。因此,該庫不會應用默認的處理程序,並且我們必須自己處理狀態碼。
假設我們與通信的服務在數據庫中沒有文章時返回 204 狀態碼。由於這種略微非標準的行為,我們希望以特殊的方式處理它。當狀態碼等於 204 時,我們拋出 ArticleNotFoundException 異常,並且在狀態碼不等於 200 時,也拋出更通用的異常:
List<Article> article = restClient.get()
.uri(uriBase + "/articles")
.exchange((request, response) -> {
if (response.getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(204))) {
throw new ArticleNotFoundException();
} else if (response.getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(200))) {
return objectMapper.readValue(response.getBody(), new TypeReference<>() {});
} else {
throw new InvalidArticleResponseException();
}
});
由於我們正在處理原始響應,因此我們還需要自己使用 ObjectMapper 解析響應體。
7. 錯誤處理
默認情況下,當 RestClient 遇到 HTTP 響應中的 4xx 或 5xx 狀態碼時,它會引發一個繼承自 RestClientException 的異常。 我們可以通過實現自定義狀態處理器來覆蓋此行為。
下面是如何編寫一個在無法找到文章時拋出自定義異常的處理程序:
Article article = restClient.get()
.uri(uriBase + "/articles/1234")
.retrieve()
.onStatus(status -> status.value() == 404, (request, response) -> {
throw new ArticleNotFoundException(response);
})
.body(Article.class);
8. 從 RestClient 構建使用 RestTemplate 的客户端
RestClient 是 RestTemplate 的後繼者,在較舊的代碼庫中,我們很可能遇到使用 RestTemplate 的實現。
幸運的是,使用舊版本的 RestTemplate 的配置創建 RestClient 實例非常簡單。
RestTemplate oldRestTemplate;
RestClient restClient = RestClient.create(oldRestTemplate);
9. 結論
在本文中,我們重點介紹了 RestClient 類,它是 RestTemplate 的繼任者,作為同步 HTTP 客户端。我們學習瞭如何使用其流暢 API,用於簡單和複雜的使用案例。接下來,我們開始收集所有 HTTP 方法,然後轉向響應序列化和錯誤處理主題。