对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

REST 客户端

Spring 框架提供了以下几种方式用于调用 REST 端点:spring-doc.cadn.net.cn

RestClient

RestClient 是一个同步的 HTTP 客户端,提供现代化、流畅的 API。 它对 HTTP 库进行了抽象,便于将 Java 对象转换为 HTTP 请求,并能从 HTTP 响应中创建对象。spring-doc.cadn.net.cn

创建RestClient

RestClient 是通过调用某个静态的 create 方法创建的。 你也可以使用 builder() 获取一个构建器,以进一步配置选项,例如指定要使用的 HTTP 库(参见 客户端请求工厂)和要使用的消息转换器(参见 HTTP 消息转换),设置默认 URI、默认路径变量、默认请求头,或 uriBuilderFactory,以及注册拦截器和初始化器。spring-doc.cadn.net.cn

一旦创建(或构建)完成,RestClient 可被多个线程安全地使用。spring-doc.cadn.net.cn

以下示例展示了如何创建一个默认的RestClient,以及如何构建一个自定义的1spring-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")
	.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() 等。spring-doc.cadn.net.cn

请求 URL

接下来,可以使用 uri 方法指定请求的 URI。 此步骤是可选的,如果 RestClient 已配置了默认 URI,则可以跳过。 URL 通常以 String 形式指定,并可包含可选的 URI 模板变量。 以下示例配置了一个发送到 example.com/orders/42 的 GET 请求: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 链接spring-doc.cadn.net.cn

请求头和请求体

如有必要,可以通过使用 header(String, String)headers(Consumer<HttpHeaders> 或便捷方法 accept(MediaType…​)acceptCharset(Charset…​) 等添加请求头来操作 HTTP 请求。 对于可以包含请求体的 HTTP 请求(POSTPUTPATCH),还提供了其他方法:contentType(MediaType)contentLength(long)spring-doc.cadn.net.cn

请求体本身可以通过 body(Object) 方法设置,该方法内部使用了HTTP 消息转换。 或者,也可以使用 ParameterizedTypeReference 来设置请求体,从而支持泛型。 最后,还可以将请求体设置为一个回调函数,该函数直接向 OutputStream 写入数据。spring-doc.cadn.net.cn

正在检索响应

一旦请求设置完成,就可以在 retrieve() 方法之后链式调用其他方法来发送请求。 例如,可以通过使用 retrieve().body(Class) 或针对参数化类型(如列表)使用 retrieve().body(ParameterizedTypeReference) 来获取响应体。 body 方法会将响应内容转换为各种类型——例如,字节数组可以转换为 String,JSON 可以通过 Jackson 转换为对象,等等(参见 HTTP 消息转换)。spring-doc.cadn.net.cn

响应也可以转换为 ResponseEntity,通过 retrieve().toEntity(Class) 可以同时访问响应头和响应体。spring-doc.cadn.net.cn

单独调用 retrieve() 是一个空操作(no-op),并返回一个 ResponseSpec。 应用程序必须在 ResponseSpec 上调用一个终止操作(terminal operation)才能产生任何副作用。 如果你的使用场景对消费响应内容不感兴趣,可以使用 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 变量的用法,以及 Accept 请求头被设置为 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 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 进行转换。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-Type 请求头设置为 application/json
4 使用 pet 作为请求体
5 将响应转换为一个不含响应体的 ResponseEntity。
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 将响应转换为一个不含响应体的 ResponseEntity。

错误处理

默认情况下,当接收到状态码为 4xx 或 5xx 的响应时,RestClient 会抛出 RestClientException 的某个子类异常。 可以使用 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() 方法提供对底层 HTTP 请求和响应的访问,该方法可替代 retrieve() 使用。 使用 exchange() 时不会应用状态处理器(status handlers),因为 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();

文件上传

要发送 multipart 数据,您需要提供一个 MultiValueMap<String, Object>,其值可以是用于部分(part)内容的 Object、用于文件部分的 Resource,或带有头部信息的 HttpEntity 用于部分(part)内容。 例如: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 包装器显式提供 HttpEntityspring-doc.cadn.net.cn

一旦 MultiValueMap 准备就绪,你就可以将其用作 POST 请求的请求体,通过 RestClient.post().body(parts)(或 RestTemplate.postForObject)来发送。spring-doc.cadn.net.cn

如果 MultiValueMap 包含至少一个非 String 的值,则 Content-Type 将由 FormHttpMessageConverter 设置为 multipart/form-data。 如果 MultiValueMap 具有 String 个值,则 Content-Type 默认为 application/x-www-form-urlencoded。 如有必要,也可以显式设置 Content-Typespring-doc.cadn.net.cn

客户端请求工厂

为了执行 HTTP 请求,RestClient 使用了一个客户端 HTTP 库。 这些库通过 ClientRequestFactory 接口进行适配。 目前提供了多种实现:spring-doc.cadn.net.cn

如果在构建 RestClient 时未指定请求工厂,它将使用 classpath 中可用的 Apache 或 Jetty HttpClient。 否则,如果已加载 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 模板类形式,为 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

使用 GET 方法获取一个 ResponseEntity(即状态、头部和主体)。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 方法更新资源,并返回响应中的资源表示形式。 请注意,JDK 的 HttpURLConnection 不支持 PATCH 方法,但 Apache 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、请求头和请求体作为输入),并返回一个 ResponseEntityspring-doc.cadn.net.cn

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

executespring-doc.cadn.net.cn

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

初始化

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

RestTemplate 可以进行可观测性(observability)插桩,以生成指标(metrics)和追踪(traces)。 参见RestTemplate 可观测性支持一节。

身体

传入和从 RestTemplate 方法返回的对象,会借助 HttpMessageConverter 转换为 HTTP 消息或从 HTTP 消息转换而来,参见HTTP 消息转换spring-doc.cadn.net.cn

从...迁移RestTemplate to RestClient

下表展示了 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 接口

Spring 框架允许你将 HTTP 服务定义为一个包含 @HttpExchange 方法的 Java 接口。你可以将该接口传递给 HttpServiceProxyFactory,以创建一个代理,该代理通过诸如 RestClientWebClient 这样的 HTTP 客户端执行请求。你也可以在服务器端通过实现该接口并使用 @Controller 注解来处理请求。spring-doc.cadn.net.cn

首先,创建一个包含 @HttpExchange 方法的接口:spring-doc.cadn.net.cn

public interface RepositoryService {

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

	// more HTTP exchange methods...

}

现在你可以创建一个代理,当调用方法时执行请求。spring-doc.cadn.net.cn

对于 RestClientspring-doc.cadn.net.cn

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

对于 WebClientspring-doc.cadn.net.cn

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

对于 RestTemplatespring-doc.cadn.net.cn

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 在类型级别上受支持,适用于所有方法: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);

}

方法参数

带注解的 HTTP 交换方法支持灵活的方法签名,可使用以下方法参数: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

以待序列化的对象形式提供请求体,或者以响应式流(Reactive Streams)Publisher 的形式提供,例如 MonoFlux,或通过已配置的 ReactiveAdapterRegistry 所支持的任何其他异步类型。spring-doc.cadn.net.cn

@RequestParamspring-doc.cadn.net.cn

添加一个或多个请求参数。参数可以是包含多个参数的 Map<String, ?>MultiValueMap<String, ?>,也可以是值的 Collection<?>,或者单个值。对于非 String 类型的值,支持类型转换。spring-doc.cadn.net.cn

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

@RequestPartspring-doc.cadn.net.cn

添加一个请求部分,该部分可以是字符串(表单字段)、Resource(文件部分)、 Object(要编码的实体,例如 JSON 格式)、HttpEntity(部分的内容和头部)、 Spring 的 Part,或者上述任意类型的 Reactive Streams Publisherspring-doc.cadn.net.cn

MultipartFilespring-doc.cadn.net.cn

MultipartFile 添加一个请求部分,通常用于 Spring MVC 控制器中,表示一个上传的文件。spring-doc.cadn.net.cn

@CookieValuespring-doc.cadn.net.cn

添加一个或多个 Cookie。参数可以是一个包含多个 Cookie 的 Map<String, ?>MultiValueMap<String, ?>,也可以是一个值的 Collection<?>,或者单个值。 对于非 String 类型的值,支持类型转换。spring-doc.cadn.net.cn

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

自定义参数解析器

对于更复杂的情况,HTTP 接口不支持将 RequestEntity 类型作为方法参数。 这样做会接管整个 HTTP 请求,并不能提升接口的语义性。 与其添加大量方法参数,开发人员可以将它们组合到一个自定义类型中, 并配置一个专用的 HttpServiceArgumentResolver 实现。spring-doc.cadn.net.cn

在下面的 HTTP 接口中,我们使用了一个自定义的 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>

}

我们可以实现自己的 HttpServiceArgumentResolver,以支持我们自定义的 Search 类型,并将其数据写入传出的 HTTP 请求中。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
	}
}

最后,我们可以在设置过程中使用此参数解析器,并使用我们的 HTTP 接口。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)

返回值

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

适配了 HttpExchangeAdapter 的客户端(例如 RestClientRestTemplate) 支持同步返回值: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

执行给定的请求,并返回一个包含状态和头部信息的 ResponseEntityspring-doc.cadn.net.cn

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

执行给定的请求,将响应内容解码为声明的返回类型,并返回一个包含状态、头部信息和解码后响应体的 ResponseEntityspring-doc.cadn.net.cn

诸如 ReactorHttpExchangeAdapter 等适配了 WebClient 的客户端,不仅支持上述所有内容,还支持响应式变体。下表展示了 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

执行给定的请求,释放响应内容(如果有的话),并返回一个包含状态和头部信息的ResponseEntityspring-doc.cadn.net.cn

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

执行给定的请求,将响应内容解码为声明的返回类型,并返回一个包含状态、头部信息和解码后响应体的 ResponseEntityspring-doc.cadn.net.cn

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

执行给定的请求,将响应内容解码为声明元素类型的流,并返回一个包含状态、头部信息和已解码响应体流的 ResponseEntityspring-doc.cadn.net.cn

默认情况下,使用 ReactorHttpExchangeAdapter 处理同步返回值时的超时时间取决于底层 HTTP 客户端的配置方式。你也可以在适配器级别设置一个 blockTimeout 值,但我们建议依赖底层 HTTP 客户端的超时设置,因为它在更低的层级运行,并提供更精细的控制。spring-doc.cadn.net.cn

错误处理

要自定义错误响应处理,您需要配置底层的 HTTP 客户端。spring-doc.cadn.net.cn

对于 RestClientspring-doc.cadn.net.cn

默认情况下,RestClient 在遇到 4xx 和 5xx HTTP 状态码时会抛出 RestClientException 异常。 若要自定义此行为,请注册一个响应状态处理器,该处理器将应用于通过该客户端执行的所有响应:spring-doc.cadn.net.cn

RestClient restClient = RestClient.builder()
		.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
		.build();

RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

有关更多详细信息和选项(例如抑制错误状态码),请参见 defaultStatusHandlerRestClient.Builder 的 Javadoc。spring-doc.cadn.net.cn

对于 WebClientspring-doc.cadn.net.cn

默认情况下,WebClient 在遇到 4xx 和 5xx HTTP 状态码时会抛出 WebClientResponseException 异常。 若要自定义此行为,请注册一个响应状态处理器,该处理器将应用于通过该客户端执行的所有响应:spring-doc.cadn.net.cn

WebClient webClient = WebClient.builder()
		.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
		.build();

WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();

有关更多详细信息和选项(例如抑制错误状态码),请参见 defaultStatusHandlerWebClient.Builder 的 Javadoc。spring-doc.cadn.net.cn

对于 RestTemplatespring-doc.cadn.net.cn

默认情况下,RestTemplate 在遇到 4xx 和 5xx HTTP 状态码时会抛出 RestClientException。 要自定义此行为,请注册一个错误处理器,该处理器将应用于通过该客户端执行的所有响应:spring-doc.cadn.net.cn

RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);

RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();

有关更多详细信息和选项,请参阅 setErrorHandlerRestTemplate 方法的 Javadoc 以及 ResponseErrorHandler 类层次结构。spring-doc.cadn.net.cn


1. 必须通过 HttpEntityRestClientheaders(Consumer<HttpHeaders>) 提供 body(Object) 的头部和主体。
2. 必须通过 RequestEntityRestClientmethod(HttpMethod)uri(URI)headers(Consumer<HttpHeaders>) 提供 body(Object) 的方法、URI、请求头和请求体。