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