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")
.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 模板变量。以下示例配置了对 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 | 将响应转换为没有主体的响应实体。 |
错误处理
默认情况下,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
模块包含 HttpMessageConverter
接口,用于通过 InputStream
和 OutputStream
读取和写入 HTTP 请求和响应的主体。HttpMessageConverter
实例在客户端(例如,在 RestClient
中)和服务器端(例如,在 Spring MVC REST 控制器中)使用。
框架中提供了主要媒体(MIME)类型的具体实现,默认情况下,它们在客户端与 RestClient
和 RestTemplate
注册,在服务器端与 RequestMappingHandlerAdapter
注册(请参阅 配置消息转换器)。
下面描述了 HttpMessageConverter
的几种实现。有关完整列表,请参阅 HttpMessageConverter
Javadoc。对于所有转换器,都使用默认媒体类型,但您可以通过设置 supportedMediaTypes
属性来覆盖它。
MessageConverter | 描述 |
---|---|
|
一个 |
|
一个 |
|
一个 |
|
一个 |
|
一个 |
|
一个 |
|
一个 |
默认情况下,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));
// 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
。最后,它将使用简单的默认值。
请注意,SimpleClientHttpRequestFactory 在访问表示错误(例如 401)的响应状态时可能会引发异常。如果出现此问题,请使用任何其他请求工厂。
|
WebClient
WebClient
是一个非阻塞的、反应式的客户端,用于执行 HTTP 请求。它是在 5.0 中引入的,并提供了一种替代 RestTemplate
的方法,支持同步、异步和流式场景。
WebClient
支持以下内容
-
非阻塞 I/O
-
反应式流背压
-
使用更少的硬件资源实现高并发
-
利用 Java 8 lambda 的函数式、流畅的 API
-
同步和异步交互
-
从服务器向上或向下流式传输
有关更多详细信息,请参阅 WebClient。
RestTemplate
RestTemplate
在 HTTP 客户端库之上提供了一个高级 API,以经典的 Spring 模板类形式提供。它公开了以下组重载方法
RestClient 为同步 HTTP 访问提供了一个更现代的 API。对于异步和流式场景,请考虑反应式 WebClient。
|
方法组 | 描述 |
---|---|
|
通过 GET 检索表示形式。 |
|
通过 GET 检索 |
|
通过 HEAD 检索资源的所有标头。 |
|
使用 POST 创建新资源,并返回响应中的 |
|
使用 POST 创建新资源,并返回响应中的表示。 |
|
使用 POST 创建新资源,并返回响应中的表示。 |
|
使用 PUT 创建或更新资源。 |
|
使用 PATCH 更新资源,并返回响应中的表示。请注意,JDK |
|
使用 DELETE 删除指定 URI 的资源。 |
|
使用 ALLOW 获取资源允许的 HTTP 方法。 |
|
前面方法的更通用(更不固执己见)版本,在需要时提供额外的灵活性。它接受一个 这些方法允许使用 |
|
执行请求的最通用方式,通过回调接口完全控制请求准备和响应提取。 |
初始化
RestTemplate
使用与 RestClient
相同的 HTTP 库抽象。默认情况下,它使用 SimpleClientHttpRequestFactory
,但这可以通过构造函数更改。请参阅 客户端请求工厂。
RestTemplate 可以被检测以实现可观察性,以便生成指标和跟踪。请参阅 RestTemplate 可观察性支持 部分。
|
主体
传递到 RestTemplate
方法中和从 RestTemplate
方法返回的对象在 HttpMessageConverter
的帮助下转换为 HTTP 消息,反之亦然,请参阅 HTTP 消息转换。
从 RestTemplate
迁移到 RestClient
下表显示了 RestTemplate
方法的 RestClient
等效项。它可以用于从后者迁移到前者。
RestTemplate 方法 |
RestClient 等效项 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 接口
Spring 框架允许您使用带 @HttpExchange
方法的 Java 接口来定义 HTTP 服务。您可以将此类接口传递给 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 |
|
添加请求参数或多个参数。参数可以是 当 |
|
添加请求部分,可以是字符串(表单字段)、 |
|
从 |
|
添加一个或多个 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
以及 ResponseErrorHandler
层次结构的 Javadoc。