REST 客户端
Spring Framework 提供了以下选项来调用 REST 端点:
-
RestClient
- 具有流畅 API 的同步客户端。 -
WebClient
- 具有流畅 API 的非阻塞、响应式客户端。 -
RestTemplate
- 具有模板方法 API 的同步客户端。 -
HTTP 接口 - 带有生成的动态代理实现的注解接口。
RestClient
RestClient
是一个提供现代、流畅 API 的同步 HTTP 客户端。它提供了对 HTTP 库的抽象,可以方便地将 Java 对象转换为 HTTP 请求,以及从 HTTP 响应创建对象。
创建 RestClient
RestClient
使用静态 create
方法之一创建。你也可以使用 builder()
来获取一个具有更多选项的构建器,例如指定要使用的 HTTP 库(参见 客户端请求工厂)和要使用的消息转换器(参见 HTTP 消息转换),设置默认 URI、默认路径变量、默认请求头或 uriBuilderFactory
,或者注册拦截器和初始化器。
一旦创建(或构建),RestClient
可以安全地被多个线程使用。
以下示例展示了如何创建默认的 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 模板变量。以下示例将 GET 请求配置为 https://example.com/orders/42
:
-
Java
-
Kotlin
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
函数也可以用于更多控制,例如指定 请求参数。
字符串 URL 默认会进行编码,但可以通过使用自定义 uriBuilderFactory
构建客户端来更改此行为。URL 也可以通过函数或 java.net.URI
提供,这两种方式都不会进行编码。有关使用和编码 URI 的更多详细信息,请参阅 URI 链接。
请求头和请求体
如有必要,可以通过 header(String, String)
、headers(Consumer<HttpHeaders>)
或便捷方法 accept(MediaType…)
、acceptCharset(Charset…)
等添加请求头来操作 HTTP 请求。对于可以包含请求体(POST
、PUT
和 PATCH
)的 HTTP 请求,还有其他可用方法:contentType(MediaType)
和 contentLength(long)
。
请求体本身可以通过 body(Object)
设置,它内部使用 HTTP 消息转换。或者,请求体可以使用 ParameterizedTypeReference
设置,允许你使用泛型。最后,请求体可以设置为写入 OutputStream
的回调函数。
检索响应
一旦请求设置完毕,可以通过在 retrieve()
之后链接方法调用来发送请求。例如,可以通过使用 retrieve().body(Class)
或 retrieve().body(ParameterizedTypeReference)
(对于列表等参数化类型)来访问响应体。body
方法将响应内容转换为各种类型——例如,字节可以转换为 String
,JSON 可以使用 Jackson 转换为对象,等等(参见 HTTP 消息转换)。
响应也可以转换为 ResponseEntity
,通过 retrieve().toEntity(Class)
访问响应头和响应体。
单独调用 |
此示例展示了如何使用 RestClient
执行一个简单的 GET
请求。
- Java
-
String result = restClient.get() [id="CO1-1"]1 .uri("https://example.com") [id="CO1-2"]2 .retrieve() [id="CO1-3"]3 .body(String.class); [id="CO1-4"]4 System.out.println(result); [id="CO1-5"]5
<1> 设置 GET 请求 <1> 指定要连接的 URL <1> 检索响应 <1> 将响应转换为字符串 <1> 打印结果
- Kotlin
-
val result= restClient.get() [id="CO2-1"]1 .uri("https://example.com") [id="CO2-2"]2 .retrieve() [id="CO2-3"]3 .body<String>() [id="CO2-4"]4 println(result) [id="CO2-5"]5
<1> 设置 GET 请求 <1> 指定要连接的 URL <1> 检索响应 <1> 将响应转换为字符串 <1> 打印结果
通过 ResponseEntity
可以访问响应状态码和响应头:
- Java
-
ResponseEntity<String> result = restClient.get() [id="CO3-1"]1 .uri("https://example.com") [id="CO3-2"]1 .retrieve() .toEntity(String.class); [id="CO3-3"]2 System.out.println("Response status: " + result.getStatusCode()); [id="CO3-4"]3 System.out.println("Response headers: " + result.getHeaders()); [id="CO3-5"]3 System.out.println("Contents: " + result.getBody()); [id="CO3-6"]3
<1> 为指定的 URL 设置 GET 请求 <1> 将响应转换为 `ResponseEntity` <1> 打印结果
- Kotlin
-
val result = restClient.get() [id="CO4-1"]1 .uri("https://example.com") [id="CO4-2"]1 .retrieve() .toEntity<String>() [id="CO4-3"]2 println("Response status: " + result.statusCode) [id="CO4-4"]3 println("Response headers: " + result.headers) [id="CO4-5"]3 println("Contents: " + result.body) [id="CO4-6"]3
<1> 为指定的 URL 设置 GET 请求 <1> 将响应转换为 `ResponseEntity` <1> 打印结果
RestClient
可以使用 Jackson 库将 JSON 转换为对象。请注意此示例中 URI 变量的使用,以及 Accept
头设置为 JSON。
- Java
-
int id = ...; Pet pet = restClient.get() .uri("https://petclinic.example.com/pets/{id}", id) [id="CO5-1"]1 .accept(APPLICATION_JSON) [id="CO5-2"]2 .retrieve() .body(Pet.class); [id="CO5-3"]3
<1> 使用 URI 变量 <1> 将 `Accept` 头设置为 `application/json` <1> 将 JSON 响应转换为 `Pet` 领域对象
- Kotlin
-
val id = ... val pet = restClient.get() .uri("https://petclinic.example.com/pets/{id}", id) [id="CO6-1"]1 .accept(APPLICATION_JSON) [id="CO6-2"]2 .retrieve() .body<Pet>() [id="CO6-3"]3
<1> 使用 URI 变量 <1> 将 `Accept` 头设置为 `application/json` <1> 将 JSON 响应转换为 `Pet` 领域对象
在下一个示例中,RestClient
用于执行包含 JSON 的 POST 请求,该请求同样使用 Jackson 进行转换。
- Java
-
Pet pet = ... [id="CO7-1"]1 ResponseEntity<Void> response = restClient.post() [id="CO7-2"]2 .uri("https://petclinic.example.com/pets/new") [id="CO7-3"]2 .contentType(APPLICATION_JSON) [id="CO7-4"]3 .body(pet) [id="CO7-5"]4 .retrieve() .toBodilessEntity(); [id="CO7-6"]5
<1> 创建 `Pet` 领域对象 <1> 设置 POST 请求和要连接的 URL <1> 将 `Content-Type` 头设置为 `application/json` <1> 使用 `pet` 作为请求体 <1> 将响应转换为没有响应体的响应实体。
- Kotlin
-
val pet: Pet = ... [id="CO8-1"]1 val response = restClient.post() [id="CO8-2"]2 .uri("https://petclinic.example.com/pets/new") [id="CO8-3"]2 .contentType(APPLICATION_JSON) [id="CO8-4"]3 .body(pet) [id="CO8-5"]4 .retrieve() .toBodilessEntity() [id="CO8-6"]5
<1> 创建 `Pet` 领域对象 <1> 设置 POST 请求和要连接的 URL <1> 将 `Content-Type` 头设置为 `application/json` <1> 使用 `pet` 作为请求体 <1> 将响应转换为没有响应体的响应实体。
错误处理
默认情况下,当检索到 4xx 或 5xx 状态码的响应时,RestClient
会抛出 RestClientException
的子类。此行为可以使用 onStatus
覆盖。
- Java
-
String result = restClient.get() [id="CO9-1"]1 .uri("https://example.com/this-url-does-not-exist") [id="CO9-2"]1 .retrieve() .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { [id="CO9-3"]2 throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); [id="CO9-4"]3 }) .body(String.class);
<1> 为返回 404 状态码的 URL 创建 GET 请求 <1> 为所有 4xx 状态码设置状态处理器 <1> 抛出自定义异常
- Kotlin
-
val result = restClient.get() [id="CO10-1"]1 .uri("https://example.com/this-url-does-not-exist") [id="CO10-2"]1 .retrieve() .onStatus(HttpStatusCode::is4xxClientError) { _, response -> [id="CO10-3"]2 throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } [id="CO10-4"]3 .body<String>()
<1> 为返回 404 状态码的 URL 创建 GET 请求 <1> 为所有 4xx 状态码设置状态处理器 <1> 抛出自定义异常
交换
对于更高级的场景,RestClient
通过 exchange()
方法提供对底层 HTTP 请求和响应的访问,该方法可以替代 retrieve()
使用。使用 exchange()
时不会应用状态处理器,因为 exchange 函数已经提供了对完整响应的访问,允许你执行任何必要的错误处理。
- Java
-
Pet result = restClient.get() .uri("https://petclinic.example.com/pets/{id}", id) .accept(APPLICATION_JSON) .exchange((request, response) -> { [id="CO11-1"]1 if (response.getStatusCode().is4xxClientError()) { [id="CO11-2"]2 throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); [id="CO11-3"]2 } else { Pet pet = convertResponse(response); [id="CO11-4"]3 return pet; } });
<1> `exchange` 提供请求和响应 <1> 当响应具有 4xx 状态码时抛出异常 <1> 将响应转换为 Pet 领域对象
- Kotlin
-
val result = restClient.get() .uri("https://petclinic.example.com/pets/{id}", id) .accept(MediaType.APPLICATION_JSON) .exchange { request, response -> [id="CO12-1"]1 if (response.getStatusCode().is4xxClientError()) { [id="CO12-2"]2 throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) [id="CO12-3"]2 } else { val pet: Pet = convertResponse(response) [id="CO12-4"]3 pet } }
<1> `exchange` 提供请求和响应 <1> 当响应具有 4xx 状态码时抛出异常 <1> 将响应转换为 Pet 领域对象
HTTP 消息转换
Jackson JSON 视图
要仅序列化对象属性的子集,你可以指定一个 Jackson JSON 视图,如以下示例所示:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
多部分
要发送多部分数据,你需要提供一个 MultiValueMap<String, Object>
,其值可以是用于部分内容的 Object
、用于文件部分的 Resource
或用于带头的 HttpEntity
。例如:
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
// send using RestClient.post or RestTemplate.postForEntity
在大多数情况下,你无需为每个部分指定 Content-Type
。内容类型会根据选择用于序列化的 HttpMessageConverter
自动确定,或者在 Resource
的情况下,根据文件扩展名确定。如有必要,你可以使用 HttpEntity
包装器显式提供 MediaType
。
一旦 MultiValueMap
准备好,你可以将其用作 POST
请求的请求体,使用 RestClient.post().body(parts)
(或 RestTemplate.postForObject
)。
如果 MultiValueMap
包含至少一个非 String
值,则 Content-Type
会由 FormHttpMessageConverter
设置为 multipart/form-data
。如果 MultiValueMap
具有 String
值,则 Content-Type
默认为 application/x-www-form-urlencoded
。如有必要,也可以显式设置 Content-Type
。
客户端请求工厂
为了执行 HTTP 请求,RestClient
使用客户端 HTTP 库。这些库通过 ClientRequestFactory
接口进行适配。有多种实现可用:
-
JdkClientHttpRequestFactory
用于 Java 的HttpClient
-
HttpComponentsClientHttpRequestFactory
用于 Apache HTTP ComponentsHttpClient
-
JettyClientHttpRequestFactory
用于 Jetty 的HttpClient
-
ReactorNettyClientRequestFactory
用于 Reactor Netty 的HttpClient
-
SimpleClientHttpRequestFactory
作为简单的默认实现
如果在构建 RestClient
时没有指定请求工厂,它将使用 Apache 或 Jetty HttpClient
(如果它们在类路径上可用)。否则,如果加载了 java.net.http
模块,它将使用 Java 的 HttpClient
。最后,它将退回到简单的默认实现。
请注意, |
WebClient
WebClient
是一个非阻塞的响应式客户端,用于执行 HTTP 请求。它在 5.0 中引入,提供了 RestTemplate
的替代方案,支持同步、异步和流式传输场景。
WebClient
支持以下功能:
-
非阻塞 I/O
-
响应式流背压
-
以更少的硬件资源实现高并发
-
利用 Java 8 Lambda 的函数式、流畅 API
-
同步和异步交互
-
向服务器进行流式上传或从服务器进行流式下载
有关更多详细信息,请参阅 WebClient。
RestTemplate
RestTemplate
以经典的 Spring Template 类的形式提供了 HTTP 客户端库的高级 API。它公开了以下几组重载方法:
|
方法组 | 描述 |
---|---|
|
通过 GET 检索表示。 |
|
通过 GET 检索 |
|
通过 HEAD 检索资源的所有头。 |
|
通过 POST 创建新资源并返回响应中的 |
|
通过 POST 创建新资源并返回响应中的表示。 |
|
通过 POST 创建新资源并返回响应中的表示。 |
|
通过 PUT 创建或更新资源。 |
|
通过 PATCH 更新资源并返回响应中的表示。
请注意,JDK |
|
通过 DELETE 删除指定 URI 处的资源。 |
|
通过 ALLOW 检索资源允许的 HTTP 方法。 |
|
前述方法的更通用(和更少主观)版本,在需要时提供额外的灵活性。
它接受 这些方法允许使用 |
|
执行请求的最通用方式,通过回调接口完全控制请求 准备和响应提取。 |
初始化
RestTemplate
使用与 RestClient
相同的 HTTP 库抽象。默认情况下,它使用 SimpleClientHttpRequestFactory
,但可以通过构造函数更改。参见 客户端请求工厂。
|
请求体
传入和传出 RestTemplate
方法的对象在 HttpMessageConverter
的帮助下转换为 HTTP 消息,参见 HTTP 消息转换。
从 RestTemplate
迁移到 RestClient
下表显示了 RestTemplate
方法的 RestClient
等效项。它可用于从前者迁移到后者。
HTTP 接口
Spring Framework 允许你使用 @HttpExchange
方法将 HTTP 服务定义为 Java 接口。你可以将此类接口传递给 HttpServiceProxyFactory
以创建代理,该代理通过 HTTP 客户端(例如 RestClient
或 WebClient
)执行请求。你还可以从 @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 交换方法支持灵活的方法签名,具有以下方法参数:
方法参数 | 描述 |
---|---|
|
动态设置请求的 URL,覆盖注解的 |
|
提供 |
|
动态设置请求的 HTTP 方法,覆盖注解的 |
|
添加一个或多个请求头。参数可以是单个值、
|
|
添加一个变量以扩展请求 URL 中的占位符。参数可以是
包含多个变量的 |
|
提供一个 |
|
提供请求体,可以是要序列化的对象,也可以是
响应式流 |
|
添加一个或多个请求参数。参数可以是包含多个参数的 当 |
|
添加请求部分,可以是 String(表单字段)、 |
|
从 |
|
添加一个或多个 Cookie。参数可以是包含多个 Cookie 的 |
方法参数不能为 null
,除非 required
属性(如果参数注解上有)设置为 false
,或者参数被标记为可选,由 MethodParameter#isOptional
确定。
自定义参数解析器
对于更复杂的用例,HTTP 接口不支持 RequestEntity
类型作为方法参数。
这将接管整个 HTTP 请求,并且不会改善接口的语义。
与其添加许多方法参数,开发人员可以将其组合成自定义类型
并配置专用的 HttpServiceArgumentResolver
实现。
在以下 HTTP 接口中,我们使用自定义 Search
类型作为参数:
我们可以实现自己的 HttpServiceArgumentResolver
,它支持我们的自定义 Search
类型,
并将其数据写入传出的 HTTP 请求。
最后,我们可以在设置和使用 HTTP 接口时使用此参数解析器。
返回值
支持的返回值取决于底层客户端。
适配到 HttpExchangeAdapter
的客户端,例如 RestClient
和 RestTemplate
,
支持同步返回值:
方法返回值 | 描述 |
---|---|
|
执行给定的请求。 |
|
执行给定的请求并返回响应头。 |
|
执行给定的请求并将响应内容解码为声明的返回类型。 |
|
执行给定的请求并返回带有状态和头的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并
返回带有状态、头和解码正文的 |
适配到 ReactorHttpExchangeAdapter
的客户端,例如 WebClient
,支持上述所有以及响应式变体。下表显示了 Reactor 类型,但你也可以使用通过 ReactiveAdapterRegistry
支持的其他响应式类型:
方法返回值 | 描述 |
---|---|
|
执行给定的请求,并释放响应内容(如果有)。 |
|
执行给定的请求,释放响应内容(如果有),并返回 响应头。 |
|
执行给定的请求并将响应内容解码为声明的返回类型。 |
|
执行给定的请求并将响应内容解码为声明的 元素类型的流。 |
|
执行给定的请求,并释放响应内容(如果有),并返回一个
带有状态和头的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并
返回带有状态、头和解码正文的 |
|
执行给定的请求,将响应内容解码为声明的
元素类型的流,并返回带有状态、头和解码的
响应体流的 |
默认情况下,使用 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.Builder
中 defaultStatusHandler
的 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.Builder
中 defaultStatusHandler
的 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();
有关更多详细信息和选项,请参阅 RestTemplate
中 setErrorHandler
的 Javadoc 和 ResponseErrorHandler
层次结构。