REST 客戶端

Spring Framework 提供以下選項來呼叫 REST 端點:

  • RestClient - 帶有 fluent API 的同步客戶端。

  • WebClient - 帶有 fluent API 的非阻塞、響應式客戶端。

  • RestTemplate - 帶有 template method API 的同步客戶端。

  • HTTP Interface - 帶有生成的動態代理實現的註解介面。

RestClient

RestClient 是一個提供現代、fluent API 的同步 HTTP 客戶端。它提供了一個對 HTTP 庫的抽象,可以方便地將 Java 物件轉換為 HTTP 請求,並從 HTTP 響應建立物件。

建立 RestClient

RestClient 是使用靜態的 create 方法之一建立的。您也可以使用 builder() 獲取一個帶有更多選項的構建器,例如指定要使用的 HTTP 庫(參見客戶端請求工廠)和要使用的訊息轉換器(參見HTTP 訊息轉換),設定預設 URI、預設路徑變數、預設請求頭或 uriBuilderFactory,或註冊攔截器和初始化器。

一旦建立(或構建)完成,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)
	// ...

也可以使用函式來實現更多控制,例如指定請求引數

String URL 預設會進行編碼,但可以透過使用自定義 uriBuilderFactory 構建客戶端來更改此行為。URL 也可以透過函式或 java.net.URI 提供,這兩者都不會進行編碼。有關處理和編碼 URI 的更多詳細資訊,請參見URI 連結

請求頭和請求體

如有必要,可以透過 header(String, String)headers(Consumer<HttpHeaders> 或便捷方法 accept(MediaType…​)acceptCharset(Charset…​) 等新增請求頭來操作 HTTP 請求。對於可以包含請求體(POSTPUTPATCH)的 HTTP 請求,還有其他可用方法:contentType(MediaType)contentLength(long)

請求體本身可以透過 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 請求,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 作為請求體
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 丟擲自定義異常

Exchange

對於更高階的場景,RestClient 透過 exchange() 方法(可以替代 retrieve() 使用)提供了對底層 HTTP 請求和響應的訪問。使用 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 Views

為了僅序列化物件屬性的子集,您可以指定一個Jackson JSON View,如以下示例所示:

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>,其值可以是用於部分內容的 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 的值,則 FormHttpMessageConverterContent-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 Components 的 HttpClient

  • JettyClientHttpRequestFactory 用於 Jetty 的 HttpClient

  • ReactorNettyClientRequestFactory 用於 Reactor Netty 的 HttpClient

  • SimpleClientHttpRequestFactory 作為簡單的預設實現

如果在構建 RestClient 時未指定請求工廠,它將優先使用 classpath 上可用的 Apache 或 Jetty HttpClient。否則,如果載入了 java.net.http 模組,它將使用 Java 的 HttpClient。最後,它將回退到簡單的預設實現。

請注意,當訪問表示錯誤的響應狀態(例如 401)時,SimpleClientHttpRequestFactory 可能會丟擲異常。如果這是一個問題,請使用其他任何請求工廠。

WebClient

WebClient 是一個非阻塞、響應式的客戶端,用於執行 HTTP 請求。它在 5.0 中引入,提供了 RestTemplate 的替代方案,支援同步、非同步和流式場景。

WebClient 支援以下特性:

  • 非阻塞 I/O

  • 響應式流背壓

  • 以更少的硬體資源實現高併發

  • 利用 Java 8 lambda 的函式式風格、fluent API

  • 同步和非同步互動

  • 向上遊或向下遊流式傳輸

有關更多詳細資訊,請參閱WebClient

RestTemplate

RestTemplate 以經典的 Spring Template 類的形式提供了對 HTTP 客戶端庫的高階 API。它暴露了以下幾組過載方法:

RestClient 為同步 HTTP 訪問提供了更現代的 API。對於非同步和流式場景,請考慮響應式的WebClient
表 1. RestTemplate 方法
方法組 描述

getForObject

透過 GET 檢索表示。

getForEntity

透過 GET 檢索 ResponseEntity(即狀態、頭部和主體)。

headForHeaders

透過 HEAD 檢索資源的所有頭部。

postForLocation

透過 POST 建立新資源並返回響應中的 Location 頭。

postForObject

透過 POST 建立新資源並返回響應中的表示。

postForEntity

透過 POST 建立新資源並返回響應中的表示。

put

透過 PUT 建立或更新資源。

patchForObject

透過 PATCH 更新資源並返回響應中的表示。注意 JDK HttpURLConnection 不支援 PATCH,但 Apache HttpComponents 等支援。

delete

透過 DELETE 刪除指定 URI 的資源。

optionsForAllow

透過 ALLOW 檢索資源允許的 HTTP 方法。

exchange

比上述方法更通用(且更不固執)的版本,在需要時提供額外的靈活性。它接受一個 RequestEntity(包含 HTTP 方法、URL、頭部和主體作為輸入)並返回一個 ResponseEntity

這些方法允許使用 ParameterizedTypeReference 代替 Class 來指定帶泛型的響應型別。

execute

執行請求最通用的方式,透過回撥介面完全控制請求準備和響應提取。

初始化

RestTemplate 使用與 RestClient 相同的 HTTP 庫抽象。預設情況下,它使用 SimpleClientHttpRequestFactory,但這可以透過建構函式更改。請參見客戶端請求工廠

RestTemplate 可以進行觀測,以生成度量和追蹤。請參閱 RestTemplate 可觀測性支援 部分。

請求/響應體

傳入和傳出 RestTemplate 方法的物件,藉助 HttpMessageConverter 進行 HTTP 訊息的轉換,請參見 HTTP 訊息轉換

RestTemplate 遷移到 RestClient

下表顯示了 RestClient 對應於 RestTemplate 方法的等效用法。您可以使用它從後者遷移到前者。

表 2. RestClient 對應 RestTemplate 方法的等效用法
RestTemplate 方法 RestClient 等效用法

getForObject(String, Class, Object…​)

get() .uri(String, Object…​) .retrieve() .body(Class)

getForObject(String, Class, Map)

get() .uri(String, Map) .retrieve() .body(Class)

getForObject(URI, Class)

get() .uri(URI) .retrieve() .body(Class)

getForEntity(String, Class, Object…​)

get() .uri(String, Object…​) .retrieve() .toEntity(Class)

getForEntity(String, Class, Map)

get() .uri(String, Map) .retrieve() .toEntity(Class)

getForEntity(URI, Class)

get() .uri(URI) .retrieve() .toEntity(Class)

headForHeaders(String, Object…​)

head() .uri(String, Object…​) .retrieve() .toBodilessEntity() .getHeaders()

headForHeaders(String, Map)

head() .uri(String, Map) .retrieve() .toBodilessEntity() .getHeaders()

headForHeaders(URI)

head() .uri(URI) .retrieve() .toBodilessEntity() .getHeaders()

postForLocation(String, Object, Object…​)

post() .uri(String, Object…​) .body(Object).retrieve() .toBodilessEntity() .getLocation()

postForLocation(String, Object, Map)

post() .uri(String, Map) .body(Object) .retrieve() .toBodilessEntity() .getLocation()

postForLocation(URI, Object)

post() .uri(URI) .body(Object) .retrieve() .toBodilessEntity() .getLocation()

postForObject(String, Object, Class, Object…​)

post() .uri(String, Object…​) .body(Object) .retrieve() .body(Class)

postForObject(String, Object, Class, Map)

post() .uri(String, Map) .body(Object) .retrieve() .body(Class)

postForObject(URI, Object, Class)

post() .uri(URI) .body(Object) .retrieve() .body(Class)

postForEntity(String, Object, Class, Object…​)

post() .uri(String, Object…​) .body(Object) .retrieve() .toEntity(Class)

postForEntity(String, Object, Class, Map)

post() .uri(String, Map) .body(Object) .retrieve() .toEntity(Class)

postForEntity(URI, Object, Class)

post() .uri(URI) .body(Object) .retrieve() .toEntity(Class)

put(String, Object, Object…​)

put() .uri(String, Object…​) .body(Object) .retrieve() .toBodilessEntity()

put(String, Object, Map)

put() .uri(String, Map) .body(Object) .retrieve() .toBodilessEntity()

put(URI, Object)

put() .uri(URI) .body(Object) .retrieve() .toBodilessEntity()

patchForObject(String, Object, Class, Object…​)

patch() .uri(String, Object…​) .body(Object) .retrieve() .body(Class)

patchForObject(String, Object, Class, Map)

patch() .uri(String, Map) .body(Object) .retrieve() .body(Class)

patchForObject(URI, Object, Class)

patch() .uri(URI) .body(Object) .retrieve() .body(Class)

delete(String, Object…​)

delete() .uri(String, Object…​) .retrieve() .toBodilessEntity()

delete(String, Map)

delete() .uri(String, Map) .retrieve() .toBodilessEntity()

delete(URI)

delete() .uri(URI) .retrieve() .toBodilessEntity()

optionsForAllow(String, Object…​)

options() .uri(String, Object…​) .retrieve() .toBodilessEntity() .getAllow()

optionsForAllow(String, Map)

options() .uri(String, Map) .retrieve() .toBodilessEntity() .getAllow()

optionsForAllow(URI)

options() .uri(URI) .retrieve() .toBodilessEntity() .getAllow()

exchange(String, HttpMethod, HttpEntity, Class, Object…​)

method(HttpMethod) .uri(String, Object…​) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1]

exchange(String, HttpMethod, HttpEntity, Class, Map)

method(HttpMethod) .uri(String, Map) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1]

exchange(URI, HttpMethod, HttpEntity, Class)

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1]

exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Object…​)

method(HttpMethod) .uri(String, Object…​) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1]

exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Map)

method(HttpMethod) .uri(String, Map) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1]

exchange(URI, HttpMethod, HttpEntity, ParameterizedTypeReference)

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1]

exchange(RequestEntity, Class)

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [2]

exchange(RequestEntity, ParameterizedTypeReference)

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [2]

execute(String, HttpMethod, RequestCallback, ResponseExtractor, Object…​)

method(HttpMethod) .uri(String, Object…​) .exchange(ExchangeFunction)

execute(String, HttpMethod, RequestCallback, ResponseExtractor, Map)

method(HttpMethod) .uri(String, Map) .exchange(ExchangeFunction)

execute(URI, HttpMethod, RequestCallback, ResponseExtractor)

method(HttpMethod) .uri(URI) .exchange(ExchangeFunction)

HTTP 介面

Spring Framework 允許您使用 @HttpExchange 方法將 HTTP 服務定義為 Java 介面。您可以將此類介面傳遞給 HttpServiceProxyFactory 以建立一個代理,該代理透過 RestClientWebClient 等 HTTP 客戶端執行請求。您也可以從 @Controller 實現該介面以處理伺服器請求。

首先建立帶有 @HttpExchange 方法的介面

public 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")
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);

}

方法引數

註解的 HTTP 交換方法支援靈活的方法簽名,使用以下方法引數

方法引數 描述

URI

動態設定請求的 URL,覆蓋註解的 url 屬性。

UriBuilderFactory

提供一個 UriBuilderFactory 來擴充套件 URI 模板和 URI 變數。實際上,它會替換底層客戶端的 UriBuilderFactory(及其基本 URL)。

HttpMethod

動態設定請求的 HTTP 方法,覆蓋註解的 method 屬性

@RequestHeader

新增一個或多個請求頭。引數可以是一個單獨的值,一個 Collection<?> 集合的值,Map<String, ?>MultiValueMap<String, ?>。支援非 String 值的型別轉換。頭部值會被新增,不會覆蓋已經新增的頭部值。

@PathVariable

新增一個變數以擴充套件請求 URL 中的佔位符。引數可以是包含多個變數的 Map<String, ?>,或一個單獨的值。支援非 String 值的型別轉換。

@RequestAttribute

提供一個 Object 作為請求屬性新增。僅 RestClientWebClient 支援。

@RequestBody

提供請求體,可以是待序列化的 Object,或者 Reactive Streams Publisher,例如 MonoFlux,或透過配置的 ReactiveAdapterRegistry 支援的任何其他非同步型別。

@RequestParam

新增一個或多個請求引數。引數可以是包含多個引數的 Map<String, ?>MultiValueMap<String, ?>,一個 Collection<?> 集合的值,或一個單獨的值。支援非 String 值的型別轉換。

"content-type" 設定為 "application/x-www-form-urlencoded" 時,請求引數會編碼在請求體中。否則,它們會作為 URL 查詢引數新增。

@RequestPart

新增請求部分,它可以是 String(表單欄位)、Resource(檔案部分)、Object(待編碼的實體,例如 JSON)、HttpEntity(部分內容和頭部)、Spring 的 Part,或以上任何型別的 Reactive Streams Publisher

MultipartFile

MultipartFile 新增請求部分,這通常用於 Spring MVC 控制器中,代表一個上傳的檔案。

@CookieValue

新增一個或多個 cookie。引數可以是包含多個 cookie 的 Map<String, ?>MultiValueMap<String, ?>,一個 Collection<?> 集合的值,或一個單獨的值。支援非 String 值的型別轉換。

方法引數不能為 null,除非 required 屬性(如果引數註解提供了此屬性)設定為 false,或者該引數被標記為可選(由 MethodParameter#isOptional 決定)。

自定義引數解析器

對於更復雜的用例,HTTP 介面不支援 RequestEntity 型別作為方法引數。這將接管整個 HTTP 請求,並且不會改進介面的語義。開發者可以將多個方法引數組合到一個自定義型別中,並配置一個專用的 HttpServiceArgumentResolver 實現,而不是新增大量方法引數。

在以下 HTTP 介面中,我們使用自定義 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>

}

我們可以實現自己的 HttpServiceArgumentResolver 來支援自定義 Search 型別,並將其資料寫入 outgoing HTTP 請求中。

  • 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
	}
}

最後,我們可以在設定過程中使用這個引數解析器,並使用我們的 HTTP 介面。

  • 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)

返回值

支援的返回值取決於底層客戶端。

適配 HttpExchangeAdapter 的客戶端(例如 RestClientRestTemplate)支援同步返回值

方法返回值 描述

void

執行給定的請求。

HttpHeaders

執行給定的請求並返回響應頭部。

<T>

執行給定的請求並將響應內容解碼為宣告的返回型別。

ResponseEntity<Void>

執行給定的請求並返回一個包含狀態和頭部的 ResponseEntity

ResponseEntity<T>

執行給定的請求,將響應內容解碼為宣告的返回型別,並返回一個包含狀態、頭部和解碼後的請求體的 ResponseEntity

適配 ReactorHttpExchangeAdapter 的客戶端(例如 WebClient)支援上述所有內容以及響應式變體。下表顯示了 Reactor 型別,但您也可以使用透過 ReactiveAdapterRegistry 支援的其他響應式型別

方法返回值 描述

Mono<Void>

執行給定的請求,並釋放(如果存在)響應內容。

Mono<HttpHeaders>

執行給定的請求,釋放(如果存在)響應內容,並返回響應頭部。

Mono<T>

執行給定的請求並將響應內容解碼為宣告的返回型別。

Flux<T>

執行給定的請求,並將響應內容解碼為宣告元素型別的流。

Mono<ResponseEntity<Void>>

執行給定的請求,釋放(如果存在)響應內容,並返回一個包含狀態和頭部的 ResponseEntity

Mono<ResponseEntity<T>>

執行給定的請求,將響應內容解碼為宣告的返回型別,並返回一個包含狀態、頭部和解碼後的請求體的 ResponseEntity

Mono<ResponseEntity<Flux<T>>

執行給定的請求,將響應內容解碼為宣告元素型別的流,並返回一個包含狀態、頭部和解碼後的響應體流的 ResponseEntity

預設情況下,使用 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.BuilderdefaultStatusHandler 的 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.BuilderdefaultStatusHandler 的 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();

有關更多詳細資訊和選項,請參閱 RestTemplatesetErrorHandler 的 Javadoc 以及 ResponseErrorHandler 層次結構。


1. HttpEntity 的頭部和請求體必須透過 headers(Consumer<HttpHeaders>)body(Object) 提供給 RestClient
2. RequestEntity 的方法、URI、頭部和請求體必須透過 method(HttpMethod)uri(URI)headers(Consumer<HttpHeaders>)body(Object) 提供給 RestClient