REST 用戶端
Spring Framework 提供以下選項,用於呼叫 REST 端點
-
RestClient
- 具有流暢 API 的同步用戶端。 -
WebClient
- 具有流暢 API 的非阻塞、反應式用戶端。 -
RestTemplate
- 具有範本方法 API 的同步用戶端。 -
HTTP 介面 - 具有產生的動態 Proxy 實作的註解介面。
RestClient
RestClient
是一種同步 HTTP 用戶端,提供現代化的流暢 API。它提供 HTTP 程式庫的抽象化,可方便地從 Java 物件轉換為 HTTP 請求,並從 HTTP 回應建立物件。
建立 RestClient
RestClient
是使用靜態 create
方法之一建立的。您也可以使用 builder()
取得具有更多選項的 builder,例如指定要使用哪個 HTTP 程式庫 (請參閱用戶端請求工廠) 以及要使用哪些訊息轉換器 (請參閱HTTP 訊息轉換)、設定預設 URI、預設路徑變數、預設請求標頭或 uriBuilderFactory
,或註冊攔截器和初始化器。
一旦建立 (或建置) 完成,RestClient
即可由多個執行緒安全地使用。
以下範例顯示如何建立預設 RestClient
,以及如何建置自訂的 RestClient
。
-
Java
-
Kotlin
RestClient defaultClient = RestClient.create();
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
val defaultClient = RestClient.create()
val customClient = RestClient.builder()
.requestFactory(HttpComponentsClientHttpRequestFactory())
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
使用 RestClient
使用 RestClient
發出 HTTP 請求時,首先要指定的是要使用的 HTTP 方法。可以使用 method(HttpMethod)
或便利方法 get()
、head()
、post()
等來完成。
請求 URL
接下來,可以使用 uri
方法指定請求 URI。此步驟是選用的,如果 RestClient
已組態預設 URI,則可以跳過此步驟。URL 通常指定為 String
,帶有選用的 URI 範本變數。以下範例組態對 example.com/orders/42
的 GET 請求
-
Java
-
Kotlin
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
....
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
...
函數也可以用於更多控制,例如指定請求參數。
字串 URL 預設會編碼,但可以透過使用自訂 uriBuilderFactory
建置用戶端來變更此設定。URL 也可以使用函數或 java.net.URI
提供,這兩者都不會編碼。如需使用和編碼 URI 的更多詳細資訊,請參閱URI 連結。
請求標頭和 Body
如有必要,可以使用 header(String, String)
、headers(Consumer<HttpHeaders>
或便利方法 accept(MediaType…)
、acceptCharset(Charset…)
等新增請求標頭來操作 HTTP 請求。對於可以包含 Body 的 HTTP 請求 (POST
、PUT
和 PATCH
),還有其他方法可用:contentType(MediaType)
和 contentLength(long)
。
請求 Body 本身可以使用 body(Object)
設定,這會在內部使用HTTP 訊息轉換。或者,可以使用 ParameterizedTypeReference
設定請求 Body,讓您可以使用泛型。最後,Body 可以設定為寫入 OutputStream
的回呼函數。
擷取回應
設定請求後,透過叫用 retrieve()
存取 HTTP 回應。可以使用 body(Class)
或 body(ParameterizedTypeReference)
存取回應 Body,以用於列表等參數化類型。body
方法將回應內容轉換為各種類型 – 例如,位元組可以轉換為 String
,JSON 可以使用 Jackson 轉換為物件,依此類推 (請參閱HTTP 訊息轉換)。
回應也可以轉換為 ResponseEntity
,以存取回應標頭以及 Body。
此範例顯示如何使用 RestClient
執行簡單的 GET
請求。
-
Java
-
Kotlin
String result = restClient.get() (1)
.uri("https://example.com") (2)
.retrieve() (3)
.body(String.class); (4)
System.out.println(result); (5)
1 | 設定 GET 請求 |
2 | 指定要連線的 URL |
3 | 擷取回應 |
4 | 將回應轉換為字串 |
5 | 列印結果 |
val result= restClient.get() (1)
.uri("https://example.com") (2)
.retrieve() (3)
.body<String>() (4)
println(result) (5)
1 | 設定 GET 請求 |
2 | 指定要連線的 URL |
3 | 擷取回應 |
4 | 將回應轉換為字串 |
5 | 列印結果 |
透過 ResponseEntity
提供對回應狀態代碼和標頭的存取
-
Java
-
Kotlin
ResponseEntity<String> result = restClient.get() (1)
.uri("https://example.com") (1)
.retrieve()
.toEntity(String.class); (2)
System.out.println("Response status: " + result.getStatusCode()); (3)
System.out.println("Response headers: " + result.getHeaders()); (3)
System.out.println("Contents: " + result.getBody()); (3)
1 | 設定指定 URL 的 GET 請求 |
2 | 將回應轉換為 ResponseEntity |
3 | 列印結果 |
val result = restClient.get() (1)
.uri("https://example.com") (1)
.retrieve()
.toEntity<String>() (2)
println("Response status: " + result.statusCode) (3)
println("Response headers: " + result.headers) (3)
println("Contents: " + result.body) (3)
1 | 設定指定 URL 的 GET 請求 |
2 | 將回應轉換為 ResponseEntity |
3 | 列印結果 |
RestClient
可以使用 Jackson 程式庫將 JSON 轉換為物件。請注意此範例中 URI 變數的使用,以及 Accept
標頭設定為 JSON。
-
Java
-
Kotlin
int id = ...;
Pet pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) (1)
.accept(APPLICATION_JSON) (2)
.retrieve()
.body(Pet.class); (3)
1 | 使用 URI 變數 |
2 | 將 Accept 標頭設定為 application/json |
3 | 將 JSON 回應轉換為 Pet 網域物件 |
val id = ...
val pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) (1)
.accept(APPLICATION_JSON) (2)
.retrieve()
.body<Pet>() (3)
1 | 使用 URI 變數 |
2 | 將 Accept 標頭設定為 application/json |
3 | 將 JSON 回應轉換為 Pet 網域物件 |
在下一個範例中,RestClient
用於執行包含 JSON 的 POST 請求,JSON 再次使用 Jackson 轉換。
-
Java
-
Kotlin
Pet pet = ... (1)
ResponseEntity<Void> response = restClient.post() (2)
.uri("https://petclinic.example.com/pets/new") (2)
.contentType(APPLICATION_JSON) (3)
.body(pet) (4)
.retrieve()
.toBodilessEntity(); (5)
1 | 建立 Pet 網域物件 |
2 | 設定 POST 請求,以及要連線的 URL |
3 | 將 Content-Type 標頭設定為 application/json |
4 | 使用 pet 作為請求 Body |
5 | 將回應轉換為沒有 Body 的回應實體。 |
val pet: Pet = ... (1)
val response = restClient.post() (2)
.uri("https://petclinic.example.com/pets/new") (2)
.contentType(APPLICATION_JSON) (3)
.body(pet) (4)
.retrieve()
.toBodilessEntity() (5)
1 | 建立 Pet 網域物件 |
2 | 設定 POST 請求,以及要連線的 URL |
3 | 將 Content-Type 標頭設定為 application/json |
4 | 使用 pet 作為請求 Body |
5 | 將回應轉換為沒有 Body 的回應實體。 |
錯誤處理
預設情況下,當擷取具有 4xx 或 5xx 狀態代碼的回應時,RestClient
會擲回 RestClientException
的子類別。可以使用 onStatus
覆寫此行為。
-
Java
-
Kotlin
String result = restClient.get() (1)
.uri("https://example.com/this-url-does-not-exist") (1)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { (2)
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); (3)
})
.body(String.class);
1 | 為傳回 404 狀態代碼的 URL 建立 GET 請求 |
2 | 為所有 4xx 狀態代碼設定狀態處理常式 |
3 | 擲回自訂例外 |
val result = restClient.get() (1)
.uri("https://example.com/this-url-does-not-exist") (1)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> (2)
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } (3)
.body<String>()
1 | 為傳回 404 狀態代碼的 URL 建立 GET 請求 |
2 | 為所有 4xx 狀態代碼設定狀態處理常式 |
3 | 擲回自訂例外 |
Exchange
對於更進階的案例,RestClient
允許透過 exchange()
方法存取基礎 HTTP 請求和回應,該方法可以用於取代 retrieve()
。使用 exchange()
時,不會套用狀態處理常式,因為 exchange 函數已提供對完整回應的存取,讓您可以執行任何必要的錯誤處理。
-
Java
-
Kotlin
Pet result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(APPLICATION_JSON)
.exchange((request, response) -> { (1)
if (response.getStatusCode().is4xxClientError()) { (2)
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); (2)
}
else {
Pet pet = convertResponse(response); (3)
return pet;
}
});
1 | exchange 提供請求和回應 |
2 | 當回應具有 4xx 狀態代碼時擲回例外 |
3 | 將回應轉換為 Pet 網域物件 |
val result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange { request, response -> (1)
if (response.getStatusCode().is4xxClientError()) { (2)
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) (2)
} else {
val pet: Pet = convertResponse(response) (3)
pet
}
}
1 | exchange 提供請求和回應 |
2 | 當回應具有 4xx 狀態代碼時擲回例外 |
3 | 將回應轉換為 Pet 網域物件 |
HTTP 訊息轉換
Jackson JSON 檢視
若要僅序列化物件屬性的子集,您可以指定Jackson JSON 檢視,如下列範例所示
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
Multipart
若要傳送 multipart 資料,您需要提供 MultiValueMap<String, Object>
,其值可以是 part 內容的 Object
、檔案 part 的 Resource
或帶有標頭的 part 內容的 HttpEntity
。例如
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
// send using RestClient.post or RestTemplate.postForEntity
在大多數情況下,您不必為每個 part 指定 Content-Type
。內容類型會根據選擇用於序列化它的 HttpMessageConverter
自動判斷,或者在 Resource
的情況下,根據檔案副檔名判斷。如有必要,您可以使用 HttpEntity
包裝器明確提供 MediaType
。
一旦 MultiValueMap
準備就緒,您就可以將其用作 POST
請求的 Body,使用 RestClient.post().body(parts)
(或 RestTemplate.postForObject
)。
如果 MultiValueMap
包含至少一個非 String
值,則 Content-Type
會由 FormHttpMessageConverter
設定為 multipart/form-data
。如果 MultiValueMap
具有 String
值,則 Content-Type
預設為 application/x-www-form-urlencoded
。如有必要,也可以明確設定 Content-Type
。
用戶端請求工廠
為了執行 HTTP 請求,RestClient
使用用戶端 HTTP 程式庫。這些程式庫透過 ClientRequestFactory
介面進行調整。提供各種實作
-
適用於 Java
HttpClient
的JdkClientHttpRequestFactory
-
適用於 Apache HTTP Components
HttpClient
的HttpComponentsClientHttpRequestFactory
-
適用於 Jetty
HttpClient
的JettyClientHttpRequestFactory
-
適用於 Reactor Netty
HttpClient
的ReactorNettyClientRequestFactory
-
作為簡單預設值的
SimpleClientHttpRequestFactory
如果在建置 RestClient
時未指定請求工廠,如果 Apache 或 Jetty HttpClient
在類別路徑上可用,則會使用它們。否則,如果載入 java.net.http
模組,則會使用 Java HttpClient
。最後,它會回復為簡單的預設值。
請注意,當存取表示錯誤 (例如 401) 的回應狀態時,SimpleClientHttpRequestFactory 可能會引發例外。如果這是問題,請使用任何替代請求工廠。 |
WebClient
WebClient
是一種非阻塞、反應式用戶端,用於執行 HTTP 請求。它在 5.0 中引入,並提供 RestTemplate
的替代方案,支援同步、非同步和串流案例。
WebClient
支援以下功能
-
非阻塞 I/O
-
反應式串流回壓
-
以較少的硬體資源實現高並行性
-
函數式、流暢的 API,充分利用 Java 8 Lambda
-
同步和非同步互動
-
從伺服器串流上傳或串流下載
如需更多詳細資訊,請參閱WebClient。
RestTemplate
RestTemplate
以經典 Spring 範本類別的形式,在 HTTP 用戶端程式庫之上提供高階 API。它公開以下幾組多載方法
RestClient 為同步 HTTP 存取提供更現代化的 API。對於非同步和串流案例,請考慮反應式WebClient。 |
方法群組 | 描述 |
---|---|
|
透過 GET 擷取表示法。 |
|
透過使用 GET 擷取 |
|
透過使用 HEAD 擷取資源的所有標頭。 |
|
透過使用 POST 建立新資源,並從回應傳回 |
|
透過使用 POST 建立新資源,並從回應傳回表示法。 |
|
透過使用 POST 建立新資源,並從回應傳回表示法。 |
|
透過使用 PUT 建立或更新資源。 |
|
透過使用 PATCH 更新資源,並從回應傳回表示法。請注意,JDK |
|
透過使用 DELETE 刪除指定 URI 的資源。 |
|
透過使用 ALLOW 擷取資源允許的 HTTP 方法。 |
|
先前方法的更通用化 (且較少預設設定) 版本,可在需要時提供額外的彈性。它接受 這些方法允許使用 |
|
執行請求的最通用方式,透過回呼介面完全控制請求準備和回應擷取。 |
初始化
RestTemplate
使用與 RestClient
相同的 HTTP 程式庫抽象化。預設情況下,它使用 SimpleClientHttpRequestFactory
,但可以透過建構子變更此設定。請參閱用戶端請求工廠。
可以檢測 RestTemplate 的可觀察性,以便產生指標和追蹤。請參閱RestTemplate 可觀察性支援章節。 |
Body
傳遞到 RestTemplate
方法和從方法傳回的物件會借助 HttpMessageConverter
轉換為 HTTP 訊息和從 HTTP 訊息轉換而來,請參閱HTTP 訊息轉換。
從 RestTemplate
移轉到 RestClient
下表顯示 RestTemplate
方法的 RestClient
對等項。它可以用於從後者移轉到前者。
RestTemplate 方法 |
RestClient 對等項 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 介面
Spring Framework 讓您能將 HTTP 服務定義為具有 @HttpExchange
方法的 Java 介面。您可以將此介面傳遞給 HttpServiceProxyFactory
以建立代理,該代理透過 HTTP 用戶端(例如 RestClient
或 WebClient
)執行請求。您也可以從 @Controller
實作介面,以進行伺服器請求處理。
首先建立具有 @HttpExchange
方法的介面
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
現在您可以建立一個代理,在呼叫方法時執行請求。
針對 RestClient
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
針對 WebClient
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
針對 RestTemplate
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/"));
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
@HttpExchange
在類型層級受到支援,它會套用至所有方法
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void updateRepository(@PathVariable String owner, @PathVariable String repo,
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
}
方法參數
加上註解的 HTTP exchange 方法支援彈性的方法簽章,並具有以下方法參數
方法引數 | 描述 |
---|---|
|
動態設定請求的 URL,覆寫註解的 |
|
提供 |
|
動態設定請求的 HTTP 方法,覆寫註解的 |
|
新增請求標頭或多個標頭。引數可以是具有多個標頭的 |
|
新增變數以擴展請求 URL 中的佔位符。引數可以是具有多個變數的 |
|
提供 |
|
提供請求主體,可以是物件以進行序列化,也可以是 Reactive Streams |
|
新增請求參數或多個參數。引數可以是具有多個參數的 當 |
|
新增請求部分,可以是字串(表單欄位)、 |
|
從 |
|
新增 Cookie 或多個 Cookie。引數可以是具有多個 Cookie 的 |
方法參數不能為 null
,除非 required
屬性(在參數註解上可用時)設定為 false
,或者參數標記為可選,如 MethodParameter#isOptional
所判斷。
回傳值
支援的回傳值取決於底層用戶端。
適用於 HttpExchangeAdapter
的用戶端,例如 RestClient
和 RestTemplate
支援同步回傳值
方法回傳值 | 描述 |
---|---|
|
執行給定的請求。 |
|
執行給定的請求並傳回回應標頭。 |
|
執行給定的請求並將回應內容解碼為宣告的回傳類型。 |
|
執行給定的請求並傳回具有狀態和標頭的 |
|
執行給定的請求,將回應內容解碼為宣告的回傳類型,並傳回具有狀態、標頭和已解碼主體的 |
適用於 ReactorHttpExchangeAdapter
的用戶端,例如 WebClient
,支援上述所有項目以及反應式變體。下表顯示 Reactor 類型,但您也可以使用透過 ReactiveAdapterRegistry
支援的其他反應式類型
方法回傳值 | 描述 |
---|---|
|
執行給定的請求,並釋放回應內容(如果有的話)。 |
|
執行給定的請求,釋放回應內容(如果有的話),並傳回回應標頭。 |
|
執行給定的請求並將回應內容解碼為宣告的回傳類型。 |
|
執行給定的請求,並將回應內容解碼為宣告元素類型的串流。 |
|
執行給定的請求,並釋放回應內容(如果有的話),並傳回具有狀態和標頭的 |
|
執行給定的請求,將回應內容解碼為宣告的回傳類型,並傳回具有狀態、標頭和已解碼主體的 |
|
執行給定的請求,將回應內容解碼為宣告元素類型的串流,並傳回具有狀態、標頭和已解碼回應主體串流的 |
預設情況下,使用 ReactorHttpExchangeAdapter
的同步回傳值的逾時時間取決於底層 HTTP 用戶端的設定方式。您也可以在配接器層級設定 blockTimeout
值,但我們建議依賴底層 HTTP 用戶端的逾時設定,這在較低層級運作並提供更多控制。
錯誤處理
若要自訂錯誤回應處理,您需要設定底層 HTTP 用戶端。
針對 RestClient
預設情況下,RestClient
會針對 4xx 和 5xx HTTP 狀態碼引發 RestClientException
。若要自訂此行為,請註冊適用於透過用戶端執行的所有回應的回應狀態處理常式
RestClient restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
如需更多詳細資訊和選項,例如抑制錯誤狀態碼,請參閱 RestClient.Builder
中 defaultStatusHandler
的 Javadoc。
針對 WebClient
預設情況下,WebClient
會針對 4xx 和 5xx HTTP 狀態碼引發 WebClientResponseException
。若要自訂此行為,請註冊適用於透過用戶端執行的所有回應的回應狀態處理常式
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
如需更多詳細資訊和選項,例如抑制錯誤狀態碼,請參閱 WebClient.Builder
中 defaultStatusHandler
的 Javadoc。
針對 RestTemplate
預設情況下,RestTemplate
會針對 4xx 和 5xx HTTP 狀態碼引發 RestClientException
。若要自訂此行為,請註冊適用於透過用戶端執行的所有回應的錯誤處理常式
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
如需更多詳細資訊和選項,請參閱 RestTemplate
中 setErrorHandler
的 Javadoc 和 ResponseErrorHandler
階層。