此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

REST 客户端

Spring Framework 提供了以下用于调用 REST 端点的选项:spring-doc.cadn.net.cn

RestClient

RestClient是一个同步 HTTP 客户端,它提供了一个流畅的 API 来执行请求。 它充当 HTTP 库的抽象,并处理 HTTP 请求和响应内容与更高级别 Java 对象之间的转换。spring-doc.cadn.net.cn

创建一个RestClient

RestClient具有静态create快捷方式。 它还公开了一个builder()还有更多选项:spring-doc.cadn.net.cn

创建后,一个RestClient在多个线程中可以安全使用。spring-doc.cadn.net.cn

下面显示了如何创建或构建RestClient:spring-doc.cadn.net.cn

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).spring-doc.cadn.net.cn

请求 URL

接下来,使用uri方法。 这是可选的,如果您通过构建器配置了 baseUrl,则可以跳过此步骤。 URL 通常指定为String,带有可选的 URI 模板变量。 执行请求的方法如下:spring-doc.cadn.net.cn

int id = 42;
restClient.get()
	.uri("https://example.com/orders/{id}", id)
	// ...
val id = 42
restClient.get()
	.uri("https://example.com/orders/{id}", id)
	// ...

函数还可用于更多控件,例如指定请求参数spring-doc.cadn.net.cn

默认情况下,字符串 URL 是编码的,但可以通过使用自定义uriBuilderFactory. URL 也可以与函数一起提供,也可以作为java.net.URI,两者都未编码。 有关使用 URI 和编码 URI 的更多详细信息,请参阅 URI 链接spring-doc.cadn.net.cn

请求标头和正文

如有必要,可以通过添加请求标头来作 HTTP 请求header(String, String),headers(Consumer<HttpHeaders>,或使用方便的方法accept(MediaType…​),acceptCharset(Charset…​)等等。 对于可以包含正文 (POST,PUTPATCH),可以使用其他方法:contentType(MediaType)contentLength(long). 如果客户端配置了ApiVersionInserter.spring-doc.cadn.net.cn

请求正文本身可以通过body(Object),它在内部使用 HTTP 消息转换。 或者,可以使用ParameterizedTypeReference,允许您使用泛型。 最后,可以将正文设置为一个回调函数,该函数写入OutputStream.spring-doc.cadn.net.cn

检索响应

设置请求后,可以通过链接方法调用来发送请求。retrieve(). 例如,可以使用retrieve().body(Class)retrieve().body(ParameterizedTypeReference)对于列表等参数化类型。 这body方法将响应内容转换为各种类型——例如,字节可以转换为String,可以使用 Jackson 将 JSON 转换为对象,依此类推(参见 HTTP 消息转换)。spring-doc.cadn.net.cn

响应也可以转换为ResponseEntity,允许访问响应标头和正文,使用retrieve().toEntity(Class)spring-doc.cadn.net.cn

retrieve()本身是一个无作,并返回一个ResponseSpec. 应用程序必须在ResponseSpec产生任何副作用。 如果使用响应对您的用例没有兴趣,您可以使用retrieve().toBodilessEntity().

此示例演示如何RestClient可用于执行简单的GET请求。spring-doc.cadn.net.cn

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:spring-doc.cadn.net.cn

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 变量的用法,以及Acceptheader 设置为 JSON。spring-doc.cadn.net.cn

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 Acceptheader 到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 Acceptheader 到application/json
3 将 JSON 响应转换为Pet域对象

在下一个示例中,RestClient用于执行包含 JSON 的 POST 请求,该请求再次使用 Jackson 进行转换。spring-doc.cadn.net.cn

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-Typeheader 到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-Typeheader 到application/json
4 pet作为请求正文
5 将响应转换为没有正文的响应实体。

错误处理

默认情况下,RestClient抛出一个RestClientException检索带有 4xx 或 5xx 状态代码的响应时。 可以使用onStatus.spring-doc.cadn.net.cn

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()方法,可以使用它来代替retrieve(). 使用exchange(),因为交换函数已经提供了对完整响应的访问,允许您执行任何必要的错误处理。spring-doc.cadn.net.cn

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 视图,如以下示例所示:spring-doc.cadn.net.cn

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用于带有标题的部件内容。 例如:spring-doc.cadn.net.cn

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,基于文件扩展名。 如有必要,可以显式提供MediaType使用HttpEntity包装纸。spring-doc.cadn.net.cn

一旦MultiValueMap准备就绪,可以将其用作POST请求, 使用RestClient.post().body(parts)(或RestTemplate.postForObject).spring-doc.cadn.net.cn

如果MultiValueMap包含至少一种非Stringvalue,则Content-Type设置为multipart/form-data通过FormHttpMessageConverter. 如果MultiValueMapString值,则Content-Type默认为application/x-www-form-urlencoded. 如有必要,Content-Type也可以显式设置。spring-doc.cadn.net.cn

客户端请求工厂

要执行 HTTP 请求,RestClient使用客户端 HTTP 库。这些库通过ClientRequestFactory接口。 有多种实现方式可供选择:spring-doc.cadn.net.cn

如果未指定请求工厂,则在RestClient已构建,它将使用 Apache 或 JettyHttpClient如果它们在类路径上可用。否则,如果java.net.http模块,它将使用 Java 的HttpClient. 最后,它将诉诸简单的默认值。spring-doc.cadn.net.cn

请注意,SimpleClientHttpRequestFactory在访问表示错误的响应状态时可能会引发异常(例如,401)。如果这是一个问题,请使用任何备用请求工厂。

WebClient

WebClient是一个非阻塞、响应式客户端,用于执行 HTTP 请求。它是在 5.0 中引入,并提供了RestTemplate,支持同步、异步和流式处理方案。spring-doc.cadn.net.cn

WebClient支持以下内容:spring-doc.cadn.net.cn

有关更多详细信息,请参阅 WebClientspring-doc.cadn.net.cn

RestTemplate

RestTemplate以经典 Spring Template 类的形式提供基于 HTTP 客户端库的高级 API。 它公开了以下重载方法组:spring-doc.cadn.net.cn

RestClient为同步 HTTP 访问提供了更现代的 API。 对于异步和流式处理方案,请考虑响应式 WebClient
表 1.RestTemplate 方法
方法组 描述

getForObjectspring-doc.cadn.net.cn

通过 GET 检索表示。spring-doc.cadn.net.cn

getForEntityspring-doc.cadn.net.cn

检索一个ResponseEntity(即状态、标头和正文)使用 GET。spring-doc.cadn.net.cn

headForHeadersspring-doc.cadn.net.cn

使用 HEAD 检索资源的所有标头。spring-doc.cadn.net.cn

postForLocationspring-doc.cadn.net.cn

使用 POST 创建新资源并返回Location标头。spring-doc.cadn.net.cn

postForObjectspring-doc.cadn.net.cn

使用 POST 创建新资源,并从响应中返回表示形式。spring-doc.cadn.net.cn

postForEntityspring-doc.cadn.net.cn

使用 POST 创建新资源,并从响应中返回表示形式。spring-doc.cadn.net.cn

putspring-doc.cadn.net.cn

使用 PUT 创建或更新资源。spring-doc.cadn.net.cn

patchForObjectspring-doc.cadn.net.cn

使用 PATCH 更新资源,并从响应中返回表示形式。 请注意,JDKHttpURLConnection不支持PATCH,但 Apache HttpComponents 和其他 HttpComponents 可以。spring-doc.cadn.net.cn

deletespring-doc.cadn.net.cn

使用 DELETE 删除指定 URI 处的资源。spring-doc.cadn.net.cn

optionsForAllowspring-doc.cadn.net.cn

使用 ALLOW 检索资源允许的 HTTP 方法。spring-doc.cadn.net.cn

exchangespring-doc.cadn.net.cn

上述方法的更通用(且不那么固执己见)版本,可在需要时提供额外的灵活性。 它接受一个RequestEntity(包括 HTTP 方法、URL、标头和正文作为输入)并返回一个ResponseEntity.spring-doc.cadn.net.cn

这些方法允许使用ParameterizedTypeReference而不是Class指定 具有泛型的响应类型。spring-doc.cadn.net.cn

executespring-doc.cadn.net.cn

执行请求的最通用方式,完全控制请求 通过回调接口进行准备和响应提取。spring-doc.cadn.net.cn

初始化

RestTemplate使用与RestClient. 默认情况下,它使用SimpleClientHttpRequestFactory,但这可以通过构造函数进行更改。 请参阅客户端请求工厂spring-doc.cadn.net.cn

RestTemplate可以检测以实现可观测性,以生成指标和跟踪。 请参阅 RestTemplate 可观测性支持部分。

身体

传入和返回的对象RestTemplate方法与 HTTP 消息进行转换和从 HTTP 消息转换 在HttpMessageConverter,请参阅 HTTP 消息转换spring-doc.cadn.net.cn

RestTemplateRestClient

下表显示RestClient等效项RestTemplate方法。 它可用于从后者迁移到前者。spring-doc.cadn.net.cn

表 2.RestTemplate 方法的 RestClient 等效项
RestTemplate方法 RestClient等效

getForObject(String, Class, Object…​)spring-doc.cadn.net.cn

get() .uri(String, Object…​) .retrieve() .body(Class)spring-doc.cadn.net.cn

getForObject(String, Class, Map)spring-doc.cadn.net.cn

get() .uri(String, Map) .retrieve() .body(Class)spring-doc.cadn.net.cn

getForObject(URI, Class)spring-doc.cadn.net.cn

get() .uri(URI) .retrieve() .body(Class)spring-doc.cadn.net.cn

getForEntity(String, Class, Object…​)spring-doc.cadn.net.cn

get() .uri(String, Object…​) .retrieve() .toEntity(Class)spring-doc.cadn.net.cn

getForEntity(String, Class, Map)spring-doc.cadn.net.cn

get() .uri(String, Map) .retrieve() .toEntity(Class)spring-doc.cadn.net.cn

getForEntity(URI, Class)spring-doc.cadn.net.cn

get() .uri(URI) .retrieve() .toEntity(Class)spring-doc.cadn.net.cn

headForHeaders(String, Object…​)spring-doc.cadn.net.cn

head() .uri(String, Object…​) .retrieve() .toBodilessEntity() .getHeaders()spring-doc.cadn.net.cn

headForHeaders(String, Map)spring-doc.cadn.net.cn

head() .uri(String, Map) .retrieve() .toBodilessEntity() .getHeaders()spring-doc.cadn.net.cn

headForHeaders(URI)spring-doc.cadn.net.cn

head() .uri(URI) .retrieve() .toBodilessEntity() .getHeaders()spring-doc.cadn.net.cn

postForLocation(String, Object, Object…​)spring-doc.cadn.net.cn

post() .uri(String, Object…​) .body(Object).retrieve() .toBodilessEntity() .getLocation()spring-doc.cadn.net.cn

postForLocation(String, Object, Map)spring-doc.cadn.net.cn

post() .uri(String, Map) .body(Object) .retrieve() .toBodilessEntity() .getLocation()spring-doc.cadn.net.cn

postForLocation(URI, Object)spring-doc.cadn.net.cn

post() .uri(URI) .body(Object) .retrieve() .toBodilessEntity() .getLocation()spring-doc.cadn.net.cn

postForObject(String, Object, Class, Object…​)spring-doc.cadn.net.cn

post() .uri(String, Object…​) .body(Object) .retrieve() .body(Class)spring-doc.cadn.net.cn

postForObject(String, Object, Class, Map)spring-doc.cadn.net.cn

post() .uri(String, Map) .body(Object) .retrieve() .body(Class)spring-doc.cadn.net.cn

postForObject(URI, Object, Class)spring-doc.cadn.net.cn

post() .uri(URI) .body(Object) .retrieve() .body(Class)spring-doc.cadn.net.cn

postForEntity(String, Object, Class, Object…​)spring-doc.cadn.net.cn

post() .uri(String, Object…​) .body(Object) .retrieve() .toEntity(Class)spring-doc.cadn.net.cn

postForEntity(String, Object, Class, Map)spring-doc.cadn.net.cn

post() .uri(String, Map) .body(Object) .retrieve() .toEntity(Class)spring-doc.cadn.net.cn

postForEntity(URI, Object, Class)spring-doc.cadn.net.cn

post() .uri(URI) .body(Object) .retrieve() .toEntity(Class)spring-doc.cadn.net.cn

put(String, Object, Object…​)spring-doc.cadn.net.cn

put() .uri(String, Object…​) .body(Object) .retrieve() .toBodilessEntity()spring-doc.cadn.net.cn

put(String, Object, Map)spring-doc.cadn.net.cn

put() .uri(String, Map) .body(Object) .retrieve() .toBodilessEntity()spring-doc.cadn.net.cn

put(URI, Object)spring-doc.cadn.net.cn

put() .uri(URI) .body(Object) .retrieve() .toBodilessEntity()spring-doc.cadn.net.cn

patchForObject(String, Object, Class, Object…​)spring-doc.cadn.net.cn

patch() .uri(String, Object…​) .body(Object) .retrieve() .body(Class)spring-doc.cadn.net.cn

patchForObject(String, Object, Class, Map)spring-doc.cadn.net.cn

patch() .uri(String, Map) .body(Object) .retrieve() .body(Class)spring-doc.cadn.net.cn

patchForObject(URI, Object, Class)spring-doc.cadn.net.cn

patch() .uri(URI) .body(Object) .retrieve() .body(Class)spring-doc.cadn.net.cn

delete(String, Object…​)spring-doc.cadn.net.cn

delete() .uri(String, Object…​) .retrieve() .toBodilessEntity()spring-doc.cadn.net.cn

delete(String, Map)spring-doc.cadn.net.cn

delete() .uri(String, Map) .retrieve() .toBodilessEntity()spring-doc.cadn.net.cn

delete(URI)spring-doc.cadn.net.cn

delete() .uri(URI) .retrieve() .toBodilessEntity()spring-doc.cadn.net.cn

optionsForAllow(String, Object…​)spring-doc.cadn.net.cn

options() .uri(String, Object…​) .retrieve() .toBodilessEntity() .getAllow()spring-doc.cadn.net.cn

optionsForAllow(String, Map)spring-doc.cadn.net.cn

options() .uri(String, Map) .retrieve() .toBodilessEntity() .getAllow()spring-doc.cadn.net.cn

optionsForAllow(URI)spring-doc.cadn.net.cn

options() .uri(URI) .retrieve() .toBodilessEntity() .getAllow()spring-doc.cadn.net.cn

exchange(String, HttpMethod, HttpEntity, Class, Object…​)spring-doc.cadn.net.cn

method(HttpMethod) .uri(String, Object…​) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1]spring-doc.cadn.net.cn

exchange(String, HttpMethod, HttpEntity, Class, Map)spring-doc.cadn.net.cn

method(HttpMethod) .uri(String, Map) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1]spring-doc.cadn.net.cn

exchange(URI, HttpMethod, HttpEntity, Class)spring-doc.cadn.net.cn

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1]spring-doc.cadn.net.cn

exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Object…​)spring-doc.cadn.net.cn

method(HttpMethod) .uri(String, Object…​) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1]spring-doc.cadn.net.cn

exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Map)spring-doc.cadn.net.cn

method(HttpMethod) .uri(String, Map) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1]spring-doc.cadn.net.cn

exchange(URI, HttpMethod, HttpEntity, ParameterizedTypeReference)spring-doc.cadn.net.cn

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1]spring-doc.cadn.net.cn

exchange(RequestEntity, Class)spring-doc.cadn.net.cn

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [2]spring-doc.cadn.net.cn

exchange(RequestEntity, ParameterizedTypeReference)spring-doc.cadn.net.cn

method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [2]spring-doc.cadn.net.cn

execute(String, HttpMethod, RequestCallback, ResponseExtractor, Object…​)spring-doc.cadn.net.cn

method(HttpMethod) .uri(String, Object…​) .exchange(ExchangeFunction)spring-doc.cadn.net.cn

execute(String, HttpMethod, RequestCallback, ResponseExtractor, Map)spring-doc.cadn.net.cn

method(HttpMethod) .uri(String, Map) .exchange(ExchangeFunction)spring-doc.cadn.net.cn

execute(URI, HttpMethod, RequestCallback, ResponseExtractor)spring-doc.cadn.net.cn

method(HttpMethod) .uri(URI) .exchange(ExchangeFunction)spring-doc.cadn.net.cn

HTTP 接口客户端

您可以将 HTTP 服务定义为 Java 接口,并使用@HttpExchange方法,并使用HttpServiceProxyFactory从中创建一个客户端代理,以便通过 HTTP 进行远程访问RestClient,WebClientRestTemplate.在服务器端,一个@Controller类 可以实现相同的接口来处理具有@HttpExchange控制器方法的请求。spring-doc.cadn.net.cn

首先,创建 Java 接口:spring-doc.cadn.net.cn

public interface RepositoryService {

	@GetExchange("/repos/{owner}/{repo}")
	Repository getRepository(@PathVariable String owner, @PathVariable String repo);

	// more HTTP exchange methods...

}

(可选)使用@HttpExchange在类型级别为所有方法声明通用属性:spring-doc.cadn.net.cn

@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:spring-doc.cadn.net.cn

// 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();

现在,您已准备好创建客户端代理:spring-doc.cadn.net.cn

RepositoryService service = factory.createClient(RepositoryService.class);
// Use service methods for remote calls...

方法参数

@HttpExchange方法支持具有以下输入的灵活方法签名:spring-doc.cadn.net.cn

方法参数 描述

URIspring-doc.cadn.net.cn

动态设置请求的 URL,覆盖注释的url属性。spring-doc.cadn.net.cn

UriBuilderFactoryspring-doc.cadn.net.cn

提供一个UriBuilderFactory以展开 URI 模板和 URI 变量。 实际上,将UriBuilderFactory(及其基本 URL)。spring-doc.cadn.net.cn

HttpMethodspring-doc.cadn.net.cn

动态设置请求的 HTTP 方法,覆盖注释的method属性spring-doc.cadn.net.cn

@RequestHeaderspring-doc.cadn.net.cn

添加一个或多个请求标头。参数可以是单个值, 一个Collection<?>的价值观,Map<String, ?>,MultiValueMap<String, ?>. 非字符串值支持类型转换。添加标头值和 不要覆盖已添加的标头值。spring-doc.cadn.net.cn

@PathVariablespring-doc.cadn.net.cn

添加一个变量,用于在请求 URL 中展开占位符。参数可以是Map<String, ?>具有多个变量或单个值。类型转换 支持非 String 值。spring-doc.cadn.net.cn

@RequestAttributespring-doc.cadn.net.cn

提供Object添加为请求属性。仅支持RestClientWebClient.spring-doc.cadn.net.cn

@RequestBodyspring-doc.cadn.net.cn

将请求的正文作为要序列化的对象提供,或者将请求的正文作为 反应流PublisherMono,Flux,或支持的任何其他异步类型 通过配置的ReactiveAdapterRegistry.spring-doc.cadn.net.cn

@RequestParamspring-doc.cadn.net.cn

添加一个或多个请求参数。参数可以是Map<String, ?>MultiValueMap<String, ?>使用多个参数时,一个Collection<?>的值,或 单个值。非字符串值支持类型转换。spring-doc.cadn.net.cn

什么时候"content-type"设置为"application/x-www-form-urlencoded"请求 参数在请求正文中编码。否则,它们将添加为 URL 查询 参数。spring-doc.cadn.net.cn

@RequestPartspring-doc.cadn.net.cn

添加一个请求部分,可以是 String(表单字段),Resource(文件部分), 对象(要编码的实体,例如,作为 JSON),HttpEntity(部分内容和标题), a SpringPart或响应式流Publisher上述任何一项。spring-doc.cadn.net.cn

MultipartFilespring-doc.cadn.net.cn

MultipartFile,通常用于 Spring MVC 控制器 其中它表示上传的文件。spring-doc.cadn.net.cn

@CookieValuespring-doc.cadn.net.cn

添加一个或多个 Cookie。参数可以是Map<String, ?>MultiValueMap<String, ?>使用多个 cookie 时,一个Collection<?>的值,或 个人价值。非字符串值支持类型转换。spring-doc.cadn.net.cn

方法参数不能null除非required属性(如果在 参数注释)设置为false,或者该参数被标记为可选,由MethodParameter#isOptional.spring-doc.cadn.net.cn

RestClientAdapter为类型为StreamingHttpOutputMessage.Body允许通过写入OutputStream.spring-doc.cadn.net.cn

自定义参数

您可以配置自定义HttpServiceArgumentResolver.下面的示例界面 使用自定义Search方法参数类型:spring-doc.cadn.net.cn

public interface RepositoryService {

	@GetExchange("/repos/search")
	List<Repository> searchRepository(Search search);

}
interface RepositoryService {

	@GetExchange("/repos/search")
	fun searchRepository(search: Search): List<Repository>

}

自定义参数解析器可以这样实现:spring-doc.cadn.net.cn

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

要配置自定义参数解析器,请执行以下作:spring-doc.cadn.net.cn

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不支持作为方法参数,而是鼓励 对请求的各个部分使用更细粒度的方法参数。

返回值

支持的返回值取决于基础客户端。spring-doc.cadn.net.cn

客户适应HttpExchangeAdapterRestClientRestTemplate支持同步返回值:spring-doc.cadn.net.cn

方法返回值 描述

voidspring-doc.cadn.net.cn

执行给定的请求。spring-doc.cadn.net.cn

HttpHeadersspring-doc.cadn.net.cn

执行给定的请求并返回响应标头。spring-doc.cadn.net.cn

<T>spring-doc.cadn.net.cn

执行给定的请求并将响应内容解码为声明的返回类型。spring-doc.cadn.net.cn

ResponseEntity<Void>spring-doc.cadn.net.cn

执行给定的请求并返回一个ResponseEntity替换为状态和标头。spring-doc.cadn.net.cn

ResponseEntity<T>spring-doc.cadn.net.cn

执行给定的请求,将响应内容解码为声明的返回类型,然后 返回一个ResponseEntity替换为状态、标头和解码正文。spring-doc.cadn.net.cn

客户适应ReactorHttpExchangeAdapterWebClient,支持以上所有内容 以及反应性变体。下表显示了 Reactor 类型,但您也可以使用 通过ReactiveAdapterRegistry:spring-doc.cadn.net.cn

方法返回值 描述

Mono<Void>spring-doc.cadn.net.cn

执行给定的请求,并释放响应内容(如果有)。spring-doc.cadn.net.cn

Mono<HttpHeaders>spring-doc.cadn.net.cn

执行给定的请求,释放响应内容(如果有),并返回 响应标头。spring-doc.cadn.net.cn

Mono<T>spring-doc.cadn.net.cn

执行给定的请求并将响应内容解码为声明的返回类型。spring-doc.cadn.net.cn

Flux<T>spring-doc.cadn.net.cn

执行给定的请求并将响应内容解码为声明的流 元素类型。spring-doc.cadn.net.cn

Mono<ResponseEntity<Void>>spring-doc.cadn.net.cn

执行给定的请求,并释放响应内容(如果有),并返回ResponseEntity替换为状态和标头。spring-doc.cadn.net.cn

Mono<ResponseEntity<T>>spring-doc.cadn.net.cn

执行给定的请求,将响应内容解码为声明的返回类型,然后 返回一个ResponseEntity替换为状态、标头和解码正文。spring-doc.cadn.net.cn

Mono<ResponseEntity<Flux<T>>spring-doc.cadn.net.cn

执行给定的请求,将响应内容解码为声明的流 元素类型,并返回ResponseEntity使用状态、标头和解码的 响应正文流。spring-doc.cadn.net.cn

默认情况下,同步返回值的超时ReactorHttpExchangeAdapter取决于底层 HTTP 客户端的配置方式。您可以设置一个blockTimeout值,但我们建议依赖 底层 HTTP 客户端,它在较低级别运行并提供更多控制。spring-doc.cadn.net.cn

RestClientAdapter提供支持对类型InputStreamResponseEntity<InputStream>提供对原始响应的访问 身体内容。spring-doc.cadn.net.cn

错误处理

要自定义 HTTP 服务客户端代理的错误处理,您可以配置 根据需要进行基础客户端。默认情况下,客户端会引发 4xx 和 5xx HTTP 的异常 状态代码。要自定义此设置,请注册适用于所有 通过客户端执行的响应如下:spring-doc.cadn.net.cn

// 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();

有关更多详细信息和选项,例如禁止显示错误状态代码,请参阅参考 每个客户端的文档,以及defaultStatusHandlerRestClient.BuilderWebClient.BuildersetErrorHandlerRestTemplate.spring-doc.cadn.net.cn

装饰适配器

HttpExchangeAdapterReactorHttpExchangeAdapter是解耦 HTTP 的合约 从调用底层的详细信息接口客户端基础结构 客户。有适配器实现RestClient,WebClientRestTemplate.spring-doc.cadn.net.cn

有时,通过装饰器拦截客户端调用可能很有用 可在HttpServiceProxyFactory.Builder.例如,您可以申请 内置装饰器来抑制 404 异常并返回ResponseEntityNOT_FOUNDnull身体:spring-doc.cadn.net.cn

// 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-doc.cadn.net.cn

为了更轻松地大规模使用接口客户端,Spring Framework 提供了 专用配置支持。它使应用程序能够专注于识别 HTTP 服务 按组,并为每个组自定义客户端,而框架透明 创建客户端代理的注册表,并将每个代理声明为 Bean。spring-doc.cadn.net.cn

HTTP 服务组只是一组接口,它们共享相同的客户端设置和HttpServiceProxyFactory实例创建代理。通常,这意味着每个组 主机,但您可以为同一目标主机设置多个组,以防 底层客户端需要以不同的方式配置。spring-doc.cadn.net.cn

声明 HTTP 服务组的一种方法是通过@ImportHttpServices注释@Configuration类,如下所示:spring-doc.cadn.net.cn

@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class}) (1)
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) (2)
public class ClientConfig {
}
1 手动列出组“echo”的接口
2 检测基本包下组“问候语”的接口

上面允许您声明 HTTP 服务和组。作为替代方案,您还可以注释 HTTP 接口,如下所示:spring-doc.cadn.net.cn

@HttpServiceClient("echo")
public interface EchoServiceA {
	// ...
}

@HttpServiceClient("echo")
public interface EchoServiceB {
	// ...
}

上述需要专门的进口注册商,如下所示:spring-doc.cadn.net.cn

public class MyClientHttpServiceRegistrar implements AbstractClientHttpServiceRegistrar { (1)

	@Override
	protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
		findAndRegisterHttpServiceClients(groupRegistry, List.of("org.example.echo")); (2)
	}
}

@Configuration
@Import(MyClientHttpServiceRegistrar.class) (3)
public class ClientConfig {
}
1 扩展专用AbstractClientHttpServiceRegistrar
2 指定要查找客户端接口的基本包
3 导入注册商
@HttpServiceClient接口被排除在@ImportHttpServicesscans,因此当指向同一包时,与客户端接口的扫描没有重叠。

也可以通过创建 HTTP 服务registrar 然后导入它来以编程方式声明组:spring-doc.cadn.net.cn

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 检测基本包下组“问候语”的接口
4 导入注册商
您可以混合搭配@ImportHttpService使用程序化注册商进行注释, 并且您可以将导入分散到多个配置类中。所有进口 协作贡献相同,共享HttpServiceProxyRegistry实例。

声明 HTTP 服务组后,将HttpServiceGroupConfigurerbean 到 为每个组自定义客户端。例如:spring-doc.cadn.net.cn

@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 Service 组、Spring Security 添加 OAuth 支持和 Spring Cloud 添加负载 平衡。

由于上述原因,每个客户端代理都可以作为一个 Bean 使用,您可以 方便地按类型自动接线:spring-doc.cadn.net.cn

@RestController
public class EchoController {

	private final EchoService echoService;

	public EchoController(EchoService echoService) {
		this.echoService = echoService;
	}

	// ...
}

但是,如果有多个相同类型的客户端代理,例如同一接口 在多个组中,则没有该类型的唯一 bean,并且您无法通过 仅键入。对于这种情况,您可以直接使用HttpServiceProxyRegistry那 保存所有代理,并按组获取您需要的代理:spring-doc.cadn.net.cn

@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 访问EchoService组“echo1”的客户端代理
2 访问EchoService组“echo2”的客户端代理

1.HttpEntityheaders 和 body 必须提供给RestClient通过headers(Consumer<HttpHeaders>)body(Object).
2.RequestEntity方法、URI、标头和正文必须提供给RestClient通过method(HttpMethod),uri(URI),headers(Consumer<HttpHeaders>)body(Object).