REST 客戶端
Spring 框架提供了以下選項來呼叫 REST 端點
-
RestClient— 具有流式 API 的同步客戶端 -
WebClient— 具有流式 API 的非阻塞式、響應式客戶端 -
RestTemplate— 具有模板方法 API 的同步客戶端,現已棄用,轉而使用RestClient -
HTTP 服務客戶端 — 由生成的代理支援的帶註解介面
RestClient
RestClient 是一個同步 HTTP 客戶端,它提供流式 API 來執行請求。它作為 HTTP 庫的抽象,並處理 HTTP 請求和響應內容與高階 Java 物件之間的轉換。
建立 RestClient
RestClient 具有靜態的 create 快捷方法。它還公開了一個 builder(),其中包含更多選項
一旦建立,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")
.defaultVersion("1.2")
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
.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")
.defaultVersion("1.2")
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
使用 RestClient
要執行 HTTP 請求,首先指定要使用的 HTTP 方法。使用便捷方法,如 get()、head()、post() 等,或 method(HttpMethod)。
請求 URL
接下來,使用 uri 方法指定請求 URI。這是可選的,如果您透過構建器配置了 baseUrl,則可以跳過此步驟。URL 通常指定為 String,帶有可選的 URI 模板變數。以下顯示瞭如何執行請求
-
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 連結。
請求頭和請求體
如有必要,可以透過 header(String, String)、headers(Consumer<HttpHeaders>) 或便捷方法 accept(MediaType…)、acceptCharset(Charset…) 等來新增請求頭來操作 HTTP 請求。對於可以包含請求體(POST、PUT 和 PATCH)的 HTTP 請求,還提供了其他方法:contentType(MediaType) 和 contentLength(long)。如果客戶端配置了 ApiVersionInserter,您可以為請求設定 API 版本。
請求體本身可以透過 body(Object) 設定,它內部使用 HTTP 訊息轉換。或者,請求體可以使用 ParameterizedTypeReference 設定,允許您使用泛型。最後,請求體可以設定為寫入 OutputStream 的回撥函式。
檢索響應
請求設定完成後,可以透過在 retrieve() 後鏈式呼叫方法來發送請求。例如,可以透過使用 retrieve().body(Class) 或 retrieve().body(ParameterizedTypeReference) 來訪問響應體,後者適用於列表等引數化型別。body 方法將響應內容轉換為各種型別——例如,位元組可以轉換為 String,JSON 可以使用 Jackson 轉換為物件,等等(參見 HTTP 訊息轉換)。
響應也可以轉換為 ResponseEntity,透過 retrieve().toEntity(Class) 可以訪問響應頭和響應體
單獨呼叫 retrieve() 是一個無操作,並返回一個 ResponseSpec。應用程式必須對 ResponseSpec 呼叫一個終止操作才能產生任何副作用。如果您的用例對消費響應不感興趣,您可以使用 retrieve().toBodilessEntity()。 |
此示例展示瞭如何使用 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 請求,該請求同樣使用 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 作為請求體 |
| 5 | 將響應轉換為不帶正文的響應實體。 |
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 作為請求體 |
| 5 | 將響應轉換為不帶正文的響應實體。 |
錯誤處理
預設情況下,當檢索到 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 | 丟擲自定義異常 |
交換
對於更高階的場景,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();
多部分
要傳送多部分資料,您需要提供一個 MultiValueMap<String, Object>,其值可以是用於部分內容的 Object、用於檔案部分的 Resource 或用於包含頭的部分內容的 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
在大多數情況下,您無需為每個部分指定 Content-Type。內容型別會根據所選的 HttpMessageConverter 自動確定以進行序列化,或者在 Resource 的情況下,根據副檔名確定。如有必要,您可以使用 HttpEntity 包裝器明確提供 MediaType。
一旦 MultiValueMap 準備就緒,您可以將其用作 POST 請求的主體,使用 RestClient.post().body(parts)(或 RestTemplate.postForObject)。
如果 MultiValueMap 包含至少一個非 String 值,則 FormHttpMessageConverter 會將 Content-Type 設定為 multipart/form-data。如果 MultiValueMap 具有 String 值,則 Content-Type 預設為 application/x-www-form-urlencoded。如有必要,Content-Type 也可以顯式設定。
客戶端請求工廠
為了執行 HTTP 請求,RestClient 使用客戶端 HTTP 庫。這些庫透過 ClientRequestFactory 介面進行適配。有多種實現可用
-
JdkClientHttpRequestFactory用於 Java 的HttpClient -
HttpComponentsClientHttpRequestFactory用於 Apache HTTP ComponentsHttpClient -
JettyClientHttpRequestFactory用於 Jetty 的HttpClient -
ReactorNettyClientRequestFactory用於 Reactor Netty 的HttpClient -
SimpleClientHttpRequestFactory作為簡單的預設值
如果在構建 RestClient 時未指定請求工廠,它將使用 Apache 或 Jetty HttpClient(如果它們在類路徑上可用)。否則,如果載入了 java.net.http 模組,它將使用 Java 的 HttpClient。最後,它將退回到簡單的預設值。
請注意,SimpleClientHttpRequestFactory 在訪問表示錯誤(例如 401)的響應狀態時可能會引發異常。如果這是一個問題,請使用任何替代的請求工廠。 |
WebClient
WebClient 是一個非阻塞的響應式客戶端,用於執行 HTTP 請求。它於 5.0 引入,提供 RestTemplate 的替代方案,支援同步、非同步和流式傳輸場景。
WebClient 支援以下功能
-
非阻塞 I/O
-
響應式流背壓
-
以更少的硬體資源實現高併發
-
利用 Java 8 Lambda 的函式式、流式 API
-
同步和非同步互動
-
向上遊或下游伺服器進行流式傳輸
有關更多詳細資訊,請參閱 WebClient。
RestTemplate
RestTemplate 以經典的 Spring Template 類的形式提供了對 HTTP 客戶端庫的高階 API。它公開了以下幾組過載方法
從 Spring Framework 7.0 開始,RestTemplate 已被棄用,轉而使用 RestClient,並將在未來版本中移除。請使用 “遷移到 RestClient” 指南。對於非同步和流式傳輸場景,請考慮響應式 WebClient。 |
| 方法組 | 描述 |
|---|---|
|
透過 GET 檢索表示。 |
|
透過 GET 檢索 |
|
透過 HEAD 檢索資源的所有頭部。 |
|
透過 POST 建立新資源並返回響應中的 |
|
透過 POST 建立新資源並返回響應中的表示。 |
|
透過 POST 建立新資源並返回響應中的表示。 |
|
透過 PUT 建立或更新資源。 |
|
透過 PATCH 更新資源並返回響應中的表示。請注意,JDK |
|
透過 DELETE 刪除指定 URI 處的資源。 |
|
透過 ALLOW 檢索資源允許的 HTTP 方法。 |
|
是上述方法的更通用(且不那麼主觀)的版本,在需要時提供額外的靈活性。它接受 這些方法允許使用 |
|
執行請求的最通用方式,透過回撥介面完全控制請求準備和響應提取。 |
初始化
RestTemplate 使用與 RestClient 相同的 HTTP 庫抽象。預設情況下,它使用 SimpleClientHttpRequestFactory,但這可以透過建構函式更改。請參閱 客戶端請求工廠。
RestTemplate 可以進行可觀測性檢測,以生成指標和跟蹤。請參閱 RestTemplate 可觀測性支援 部分。 |
正文
傳入和傳出 RestTemplate 方法的物件在 HttpMessageConverter 的幫助下轉換為 HTTP 訊息,請參閱 HTTP 訊息轉換。
遷移到 RestClient
應用程式可以逐步採用 RestClient,首先關注 API 使用,然後關注基礎設施設定。您可以考慮以下步驟
-
從現有的
RestTemplate例項建立一個或多個RestClient,例如:RestClient restClient = RestClient.create(restTemplate)。逐步替換應用程式中元件的RestTemplate用法,首先關注發出請求。請參閱下表中的 API 等效項。 -
一旦所有客戶端請求都透過
RestClient例項,您現在可以使用RestClient.Builder來複制您現有的RestTemplate例項建立。由於RestTemplate和RestClient共享相同的基礎設施,您可以在設定中重用自定義的ClientHttpRequestFactory或ClientHttpRequestInterceptor。請參閱RestClient構建器 API。
如果在類路徑上沒有其他庫可用,RestClient 將選擇由現代 JDK HttpClient 支援的 JdkClientHttpRequestFactory,而 RestTemplate 將選擇使用 HttpURLConnection 的 SimpleClientHttpRequestFactory。這可以解釋 HTTP 級別執行時微妙的行為差異。
下表顯示了 RestTemplate 方法的 RestClient 等效項。
RestTemplate 方法 |
RestClient 等效項 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RestClient 和 RestTemplate 例項在丟擲異常(RestClientException 型別位於層次結構頂部)時具有相同的行為。當 RestTemplate 始終針對“4xx”響應狀態丟擲 HttpClientErrorException 時,RestClient 允許使用自定義的 “狀態處理程式” 提供更大的靈活性。
HTTP 服務客戶端
您可以將 HTTP 服務定義為具有 @HttpExchange 方法的 Java 介面,並使用 HttpServiceProxyFactory 從中建立一個客戶端代理,透過 RestClient、WebClient 或 RestTemplate 進行遠端 HTTP 訪問。在伺服器端,@Controller 類可以實現相同的介面,使用 @HttpExchange 控制器方法來處理請求。
首先,建立 Java 介面
public interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
(可選)在型別級別使用 @HttpExchange 宣告所有方法的通用屬性
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
public 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);
}
接下來,配置客戶端並建立 HttpServiceProxyFactory
// Using RestClient...
RestClient restClient = RestClient.create("...");
RestClientAdapter adapter = RestClientAdapter.create(restClient);
// or WebClient...
WebClient webClient = WebClient.create("...");
WebClientAdapter adapter = WebClientAdapter.create(webClient);
// or RestTemplate...
RestTemplate restTemplate = new RestTemplate();
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
現在,您可以建立客戶端代理了
RepositoryService service = factory.createClient(RepositoryService.class);
// Use service methods for remote calls...
HTTP 服務客戶端是一種強大而富有表現力的 HTTP 遠端訪問選擇。它允許一個團隊掌握 REST API 的工作方式、哪些部分與客戶端應用程式相關、建立哪些輸入和輸出型別、需要哪些端點方法簽名、擁有哪些 Javadoc 等知識。生成的 Java API 易於理解且可直接使用。
方法引數
@HttpExchange 方法支援具有以下輸入的靈活方法簽名
| 方法引數 | 描述 |
|---|---|
|
動態設定請求的 URL,覆蓋註解的 |
|
提供 |
|
動態設定請求的 HTTP 方法,覆蓋註解的 |
|
新增一個請求頭或多個請求頭。引數可以是單個值、 |
|
新增一個變數以擴充套件請求 URL 中的佔位符。引數可以是包含多個變數的 |
|
提供一個 |
|
提供請求體,可以是一個待序列化的物件,也可以是響應式流 |
|
新增一個或多個請求引數。引數可以是包含多個引數的 當 |
|
新增請求部分,可以是字串(表單欄位)、 |
|
從 |
|
新增一個或多個 Cookie。引數可以是包含多個 Cookie 的 |
方法引數不能為 null,除非 required 屬性(如果引數註解可用)設定為 false,或者引數被標記為可選,這由 MethodParameter#isOptional 確定。
RestClientAdapter 額外支援 StreamingHttpOutputMessage.Body 型別的方法引數,允許透過寫入 OutputStream 傳送請求體。
自定義引數
您可以配置自定義的 HttpServiceArgumentResolver。下面的示例介面使用自定義的 Search 方法引數型別
-
Java
-
Kotlin
public interface RepositoryService {
@GetExchange("/repos/search")
List<Repository> searchRepository(Search search);
}
interface RepositoryService {
@GetExchange("/repos/search")
fun searchRepository(search: Search): List<Repository>
}
自定義引數解析器可以這樣實現
-
Java
-
Kotlin
static class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
@Override
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (parameter.getParameterType().equals(Search.class)) {
Search search = (Search) argument;
requestValues.addRequestParameter("owner", search.owner());
requestValues.addRequestParameter("language", search.language());
requestValues.addRequestParameter("query", search.query());
return true;
}
return false;
}
}
class SearchQueryArgumentResolver : HttpServiceArgumentResolver {
override fun resolve(
argument: Any?,
parameter: MethodParameter,
requestValues: HttpRequestValues.Builder
): Boolean {
if (parameter.getParameterType() == Search::class.java) {
val search = argument as Search
requestValues.addRequestParameter("owner", search.owner)
.addRequestParameter("language", search.language)
.addRequestParameter("query", search.query)
return true
}
return false
}
}
配置自定義引數解析器
-
Java
-
Kotlin
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(new SearchQueryArgumentResolver())
.build();
RepositoryService repositoryService = factory.createClient(RepositoryService.class);
Search search = Search.create()
.owner("spring-projects")
.language("java")
.query("rest")
.build();
List<Repository> repositories = repositoryService.searchRepository(search);
val restClient = RestClient.builder().baseUrl("https://api.github.com/").build()
val adapter = RestClientAdapter.create(restClient)
val factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(SearchQueryArgumentResolver())
.build()
val repositoryService = factory.createClient<RepositoryService>(RepositoryService::class.java)
val search = Search(owner = "spring-projects", language = "java", query = "rest")
val repositories = repositoryService.searchRepository(search)
預設情況下,RequestEntity 不支援作為方法引數,而是鼓勵對請求的各個部分使用更細粒度的方法引數。 |
返回值
支援的返回值取決於底層客戶端。
適配到 HttpExchangeAdapter 的客戶端(例如 RestClient 和 RestTemplate)支援同步返回值
| 方法返回值 | 描述 |
|---|---|
|
執行給定的請求。 |
|
執行給定的請求並返回響應頭。 |
|
執行給定的請求並將響應內容解碼為宣告的返回型別。 |
|
執行給定的請求並返回包含狀態和頭部的 |
|
執行給定的請求,將響應內容解碼為宣告的返回型別,並返回包含狀態、頭部和解碼後的正文的 |
適配到 ReactorHttpExchangeAdapter 的客戶端(例如 WebClient)支援上述所有功能以及響應式變體。下表顯示了 Reactor 型別,但您也可以使用透過 ReactiveAdapterRegistry 支援的其他響應式型別
| 方法返回值 | 描述 |
|---|---|
|
執行給定請求,並釋放響應內容(如果有)。 |
|
執行給定請求,釋放響應內容(如果有),並返回響應頭。 |
|
執行給定的請求並將響應內容解碼為宣告的返回型別。 |
|
執行給定的請求並將響應內容解碼為宣告元素型別的流。 |
|
執行給定請求,並釋放響應內容(如果有),並返回包含狀態和頭部的 |
|
執行給定的請求,將響應內容解碼為宣告的返回型別,並返回包含狀態、頭部和解碼後的正文的 |
|
執行給定請求,將響應內容解碼為宣告元素型別的流,並返回包含狀態、頭部和解碼後的響應體流的 |
預設情況下,使用 ReactorHttpExchangeAdapter 的同步返回值的超時時間取決於底層 HTTP 客戶端的配置方式。您也可以在介面卡級別設定 blockTimeout 值,但我們建議依賴底層 HTTP 客戶端的超時設定,該客戶端在較低級別操作並提供更多控制。
RestClientAdapter 額外支援 InputStream 或 ResponseEntity<InputStream> 型別的返回值,提供對原始響應體內容的訪問。
錯誤處理
要自定義 HTTP 服務客戶端代理的錯誤處理,您可以根據需要配置底層客戶端。預設情況下,客戶端會對 4xx 和 5xx HTTP 狀態碼丟擲異常。要自定義此行為,請註冊一個適用於透過客戶端執行的所有響應的響應狀態處理程式,如下所示
// For RestClient
RestClient restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
// or for WebClient...
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
// or for RestTemplate...
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
有關更多詳細資訊和選項(例如抑制錯誤狀態碼),請參閱每個客戶端的參考文件,以及 RestClient.Builder 或 WebClient.Builder 中的 defaultStatusHandler 的 Javadoc,以及 RestTemplate 的 setErrorHandler。
裝飾介面卡
HttpExchangeAdapter 和 ReactorHttpExchangeAdapter 是將 HTTP 介面客戶端基礎設施與呼叫底層客戶端的細節解耦的契約。有針對 RestClient、WebClient 和 RestTemplate 的介面卡實現。
有時,透過在 HttpServiceProxyFactory.Builder 中配置裝飾器來攔截客戶端呼叫可能很有用。例如,您可以應用內建裝飾器來抑制 404 異常並返回帶有 NOT_FOUND 和 null 正文的 ResponseEntity
// For RestClient
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restCqlientAdapter)
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
.build();
// or for WebClient...
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builderFor(webClientAdapter)
.exchangeAdapterDecorator(NotFoundWebClientAdapterDecorator::new)
.build();
HTTP 服務組
使用 HttpServiceProxyFactory 建立客戶端代理是微不足道的,但將它們宣告為 bean 會導致重複配置。您可能還有多個目標主機,因此需要配置多個客戶端,甚至需要建立更多的客戶端代理 bean。
為了更輕鬆地大規模使用介面客戶端,Spring Framework 提供了專門的配置支援。它允許應用程式專注於按組識別 HTTP 服務,併為每個組自定義客戶端,而框架透明地建立客戶端代理登錄檔,並將每個代理宣告為 bean。
HTTP 服務組簡單來說就是一組共享相同客戶端設定和 HttpServiceProxyFactory 例項來建立代理的介面。通常,這意味著每個主機一個組,但如果底層客戶端需要以不同方式配置,則同一目標主機可以有多個組。
宣告 HTTP 服務組的一種方式是透過 @Configuration 類中的 @ImportHttpServices 註解,如下所示
@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class}) (1)
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) (2)
public class ClientConfig {
}
| 1 | 手動列出“echo”組的介面 |
| 2 | 在一個基礎包下檢測“greeting”組的介面 |
也可以透過建立 HTTP 服務註冊器然後匯入它來以程式設計方式宣告組
public class MyHttpServiceRegistrar extends AbstractHttpServiceRegistrar { (1)
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
registry.forGroup("echo").register(EchoServiceA.class, EchoServiceB.class); (2)
registry.forGroup("greeting").detectInBasePackages(GreetServiceA.class); (3)
}
}
@Configuration
@Import(MyHttpServiceRegistrar.class) (4)
public class ClientConfig {
}
| 1 | 建立 AbstractHttpServiceRegistrar 的擴充套件類 |
| 2 | 手動列出“echo”組的介面 |
| 3 | 在一個基礎包下檢測“greeting”組的介面 |
| 4 | 匯入註冊器 |
您可以混合搭配 @ImportHttpService 註解和程式設計註冊器,並且可以將匯入分散到多個配置類中。所有匯入都協同作用於相同的共享 HttpServiceProxyRegistry 例項。 |
宣告 HTTP 服務組後,新增 HttpServiceGroupConfigurer bean 以自定義每個組的客戶端。例如
@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class})
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class)
public class ClientConfig {
@Bean
public RestClientHttpServiceGroupConfigurer groupConfigurer() {
return groups -> {
// configure client for group "echo"
groups.filterByName("echo").forEachClient((group, clientBuilder) -> ...);
// configure the clients for all groups
groups.forEachClient((group, clientBuilder) -> ...);
// configure client and proxy factory for each group
groups.forEachGroup((group, clientBuilder, factoryBuilder) -> ...);
};
}
}
Spring Boot 使用 HttpServiceGroupConfigurer 新增對 HTTP 服務組的客戶端屬性支援,Spring Security 新增 OAuth 支援,Spring Cloud 新增負載均衡。 |
因此,每個客戶端代理都可作為 bean 使用,您可以方便地按型別自動裝配
@RestController
public class EchoController {
private final EchoService echoService;
public EchoController(EchoService echoService) {
this.echoService = echoService;
}
// ...
}
但是,如果存在多個相同型別的客戶端代理,例如在多個組中的相同介面,則不存在該型別的唯一 bean,您不能僅按型別自動裝配。對於這種情況,您可以直接使用包含所有代理的 HttpServiceProxyRegistry,並按組獲取您需要的代理
@RestController
public class EchoController {
private final EchoService echoService1;
private final EchoService echoService2;
public EchoController(HttpServiceProxyRegistry registry) {
this.echoService1 = registry.getClient("echo1", EchoService.class); (1)
this.echoService2 = registry.getClient("echo2", EchoService.class); (2)
}
// ...
}
| 1 | 訪問“echo1”組的 EchoService 客戶端代理 |
| 2 | 訪問“echo2”組的 EchoService 客戶端代理 |