REST 客户端
Spring 框架提供以下选项来调用 REST 端点:
-
RestClient
- 具有流畅 API 的同步客户端。 -
WebClient
- 具有流畅 API 的非阻塞式响应式客户端。 -
RestTemplate
- 具有模板方法 API 的同步客户端。 -
HTTP 接口 - 带有生成的动态代理实现的带注解的接口。
RestClient
RestClient
是一个同步 HTTP 客户端,它提供了一个现代化的流畅 API。它提供了一个 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 模板变量。以下示例配置了对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)
...
函数也可以用于更多控制,例如指定请求参数。
字符串 URL 默认情况下是编码的,但这可以通过使用自定义uriBuilderFactory
构建客户端来更改。URL 也可以使用函数或java.net.URI
提供,两者都不进行编码。有关使用和编码 URI 的更多详细信息,请参见URI 链接。
请求头和请求体
如有必要,可以通过使用header(String, String)
、headers(Consumer<HttpHeaders>
或便利方法accept(MediaType…)
、acceptCharset(Charset…)
等添加请求头来操作 HTTP 请求。对于可以包含请求体的 HTTP 请求(POST
、PUT
和PATCH
),可以使用其他方法:contentType(MediaType)
和contentLength(long)
。
请求体本身可以通过body(Object)
设置,它内部使用HTTP 消息转换。或者,可以使用ParameterizedTypeReference
设置请求体,允许您使用泛型。最后,可以将请求体设置为写入OutputStream
的回调函数。
检索响应
设置请求后,可以通过调用retrieve()
访问 HTTP 响应。响应体可以使用body(Class)
或body(ParameterizedTypeReference)
(用于参数化类型,如列表)访问。body
方法将响应内容转换为各种类型——例如,字节可以转换为String
,JSON 可以使用 Jackson 转换为对象,等等(参见HTTP 消息转换)。
响应还可以转换为ResponseEntity
,从而可以访问响应头和响应体。
此示例显示了如何使用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 请求,该请求再次使用 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 | 抛出一个自定义异常 |
交换
对于更高级的场景,RestClient
通过exchange()
方法访问底层的 HTTP 请求和响应,该方法可以替代retrieve()
使用。使用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 视图
要仅序列化对象的子集属性,您可以指定一个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
值,则FormHttpMessageConverter
会将Content-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 ComponentsHttpClient
-
JettyClientHttpRequestFactory
用于 Jetty 的HttpClient
-
ReactorNettyClientRequestFactory
用于 Reactor Netty 的HttpClient
-
SimpleClientHttpRequestFactory
作为简单的默认值
如果在构建RestClient
时未指定请求工厂,它将使用类路径中可用的 Apache 或 Jetty HttpClient
。否则,如果加载了java.net.http
模块,它将使用 Java 的HttpClient
。最后,它将采用简单的默认值。
请注意,SimpleClientHttpRequestFactory 在访问表示错误(例如,401)的响应的状态时可能会引发异常。如果这是一个问题,请使用任何替代请求工厂。 |
WebClient
WebClient
是一个非阻塞的反应式客户端,用于执行 HTTP 请求。它在 5.0 版本中引入,并提供了一种替代RestTemplate
的方法,支持同步、异步和流式场景。
WebClient
支持以下功能:
-
非阻塞 I/O
-
反应式流背压
-
使用较少的硬件资源实现高并发
-
利用 Java 8 lambda 表达式的函数式风格、流畅的 API
-
同步和异步交互
-
向上或向下流式传输到服务器
有关更多详细信息,请参阅WebClient。
RestTemplate
RestTemplate
以经典的 Spring 模板类的形式,提供对 HTTP 客户端库的高级 API。它公开了以下几组重载方法:
RestClient 为同步 HTTP 访问提供了更现代的 API。对于异步和流式场景,请考虑使用反应式WebClient。 |
方法组 | 描述 |
---|---|
|
通过 GET 检索表示。 |
|
通过使用 GET 检索 |
|
通过使用 HEAD 检索资源的所有标头。 |
|
通过使用 POST 创建新的资源,并返回响应中的 |
|
通过使用 POST 创建新的资源,并返回响应中的表示。 |
|
通过使用 POST 创建新的资源,并返回响应中的表示。 |
|
通过使用 PUT 创建或更新资源。 |
|
通过使用 PATCH 更新资源,并返回响应中的表示。请注意,JDK |
|
通过使用 DELETE 删除指定 URI 处的资源。 |
|
通过使用 ALLOW 检索资源允许的 HTTP 方法。 |
|
前面方法的更通用(且更少限定)版本,在需要时提供额外的灵活性。它接受 这些方法允许使用 |
|
执行请求最通用的方法,通过回调接口完全控制请求准备和响应提取。 |
初始化
RestTemplate
使用与RestClient
相同的 HTTP 库抽象。默认情况下,它使用SimpleClientHttpRequestFactory
,但这可以通过构造函数更改。请参阅客户端请求工厂。
RestTemplate 可以进行可观察性检测,以便生成指标和跟踪。请参阅RestTemplate 可观察性支持 部分。 |
主体
传入和传出RestTemplate
方法的对象在HttpMessageConverter
的帮助下转换为 HTTP 消息,请参阅HTTP 消息转换。
从RestTemplate
迁移到RestClient
下表显示了RestTemplate
方法的RestClient
等效项。它可以用于从后者迁移到前者。
RestTemplate 方法 |
RestClient 等效项 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 接口
Spring 框架允许您使用@HttpExchange
方法将HTTP服务定义为Java接口。您可以将此接口传递给HttpServiceProxyFactory
以创建一个代理,该代理通过HTTP客户端(例如RestClient
或WebClient
)执行请求。您也可以从@Controller
实现该接口以进行服务器请求处理。
首先使用@HttpExchange
方法创建接口
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")
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中的占位符。参数可以是具有多个变量的 |
|
提供一个 |
|
提供请求的主体,可以是待序列化的对象,也可以是Reactive Streams 的 |
|
添加一个或多个请求参数。参数可以是具有多个参数的 当 |
|
添加请求部分,可以是String(表单字段)、 |
|
从 |
|
添加一个或多个cookie。参数可以是具有多个cookie的 |
方法参数不能为null
,除非required
属性(如果参数注释中可用)设置为false
,或者根据MethodParameter#isOptional
确定参数为可选。
返回值
支持的返回值取决于底层客户端。
适用于HttpExchangeAdapter
的客户端(例如RestClient
和RestTemplate
)支持同步返回值
方法返回值 | 描述 |
---|---|
|
执行给定的请求。 |
|
执行给定的请求并返回响应头。 |
|
执行给定的请求并将响应内容解码为声明的返回类型。 |
|
执行给定的请求并返回带有状态和头的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回带有状态、头和已解码主体的 |
适用于ReactorHttpExchangeAdapter
的客户端(例如WebClient
)支持上述所有内容以及反应式变体。下表显示了Reactor类型,但您也可以使用通过ReactiveAdapterRegistry
支持的其他反应式类型
方法返回值 | 描述 |
---|---|
|
执行给定的请求,并释放响应内容(如有)。 |
|
执行给定的请求,释放响应内容(如有),并返回响应头。 |
|
执行给定的请求并将响应内容解码为声明的返回类型。 |
|
执行给定的请求并将响应内容解码为声明的元素类型的流。 |
|
执行给定的请求,释放响应内容(如有),并返回带有状态和头的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回带有状态、头和已解码主体的 |
|
执行给定的请求,将响应内容解码为声明的元素类型的流,并返回带有状态、头和已解码响应主体流的 |
默认情况下,使用ReactorHttpExchangeAdapter
的同步返回值的超时取决于底层HTTP客户端的配置方式。您也可以在适配器级别设置blockTimeout
值,但我们建议依赖底层HTTP客户端的超时设置,它在更低的级别运行并提供更多控制。
错误处理
要自定义错误响应处理,您需要配置底层HTTP客户端。
对于RestClient
默认情况下,对于4xx和5xx HTTP状态码,RestClient
会引发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
默认情况下,对于4xx和5xx HTTP状态码,WebClient
会引发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
默认情况下,对于4xx和5xx HTTP状态码,RestTemplate
会引发RestClientException
。要自定义此设置,请注册一个适用于通过客户端执行的所有响应的错误处理程序
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
有关更多详细信息和选项,请参阅RestTemplate
中的setErrorHandler
和ResponseErrorHandler
层次结构的Javadoc。