REST 客户端
Spring框架提供以下选项用于调用REST端点:
-
RestClient
- 具有流畅API的同步客户端。 -
WebClient
- 非阻塞、响应式客户端,具有流畅API。 -
RestTemplate
- 具有模板方法API的同步客户端。 -
HTTP接口 - 带有生成的动态代理实现的注解接口。
RestClient
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")
.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")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
使用 RestClient
使用 RestClient
发起 HTTP 请求时,首先要指定要使用的 HTTP 方法。可以使用 method(HttpMethod)
方法或使用便捷方法 get()
、head()
、post()
等来实现。
请求 URL
接下来,可以使用 uri
方法指定请求 URI。这一步是可选的,如果 RestClient
配置了默认 URI,则可以跳过此步骤。URL 通常被指定为一个 String
,可以包含可选的 URI 模板变量。String 类型的 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("响应状态: " + result.getStatusCode()); (3)
System.out.println("响应头: " + result.getHeaders()); (3)
System.out.println("内容: " + result.getBody()); (3)
1 | 为指定的URL设置一个GET请求 |
2 | 将响应转换为ResponseEntity |
3 | 打印结果 |
val result = restClient.get() (1)
.uri("https://example.com") (1)
.retrieve()
.toEntity<String>() (2)
println("响应状态: " + result.statusCode) (3)
println("响应头: " + result.headers) (3)
println("内容: " + 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 | 将响应转换为没有主体的响应实体。 |
错误处理
默认情况下,RestClient
在检索到带有4xx或5xx状态代码的响应时会抛出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()
时,不会应用状态处理程序,因为交换函数已经提供对完整响应的访问,允许您执行任何必要的错误处理。
-
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消息转换
spring-web
模块包含用于通过InputStream
和OutputStream
读取和写入HTTP请求和响应主体的HttpMessageConverter
接口。 HttpMessageConverter
实例在客户端(例如,在RestClient
中)和服务器端(例如,在Spring MVC REST控制器中)使用。
框架中提供了主要媒体(MIME)类型的具体实现,并且默认情况下已在客户端的RestClient
和RestTemplate
以及服务器端的RequestMappingHandlerAdapter
中注册(请参见配置消息转换器)。
下面描述了几种HttpMessageConverter
的实现。有关所有转换器,请参阅HttpMessageConverter
Javadoc以获取完整列表。对于所有转换器,都使用默认的媒体类型,但您可以通过设置supportedMediaTypes
属性来覆盖它。
消息转换器 | 描述 |
---|---|
|
一个 |
|
一个 |
|
一个 |
|
一个 |
|
一个 |
|
一个 |
|
一个 |
默认情况下,RestClient
和RestTemplate
根据类路径上底层库的可用性注册所有内置消息转换器。您还可以通过在RestClient
构建器上使用messageConverters()
方法或通过RestTemplate
的messageConverters
属性显式设置要使用的消息转换器。
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));
// 使用RestClient.post或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
。最后,它将退回到简单的默认值。
WebClient
WebClient
是一个非阻塞的、响应式的客户端,用于执行HTTP请求。它在5.0中被引入,提供了一个替代方案给RestTemplate
,支持同步、异步和流式场景。
WebClient
支持以下功能:
-
非阻塞I/O
-
响应式流背压
-
高并发性,使用更少的硬件资源
-
函数式风格,流畅的API,充分利用Java 8的lambda表达式
-
同步和异步交互
-
从服务器流式上或流式下
更多详情请参见WebClient。
RestTemplate
RestTemplate
RestTemplate
提供了一个高级API,以经典的Spring模板类的形式覆盖了HTTP客户端库。它公开了以下一组重载方法:
更现代的API是通过RestClient 实现的,用于同步HTTP访问。对于异步和流式处理场景,请考虑使用响应式的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
以下表格显示了RestClient
方法对应于RestTemplate
方法。可以用于从后者迁移到前者。
RestTemplate 方法 |
RestClient 对应方法 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTTP接口
Spring Framework允许您将HTTP服务定义为具有@HttpExchange
方法的Java接口。您可以将这样的接口传递给HttpServiceProxyFactory
,以创建一个代理,通过HTTP客户端(如RestClient
或WebClient
)执行请求。您还可以从@Controller
中实现接口,用于服务器请求处理。
首先创建具有@HttpExchange
方法的接口:
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// 更多HTTP交换方法...
}
现在您可以创建一个在调用方法时执行请求的代理。
对于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。参数可以是 |
返回值
支持的返回值取决于底层客户端。
适配到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
层次结构。