REST 客户端
Spring 框架提供了以下选项来调用 REST 端点
-
RestClient— 具有流式 API 的同步客户端 -
WebClient— 具有流式 API 的非阻塞式、响应式客户端 -
RestTemplate— 具有模板方法 API 的同步客户端,现已弃用,转而使用RestClient -
HTTP 服务客户端 — 由生成的代理支持的带注解接口
RestClient
RestClient 是一个同步 HTTP 客户端,它提供流式 API 来执行请求。它作为 HTTP 库的抽象,并处理 HTTP 请求和响应内容与高级 Java 对象之间的转换。
创建 RestClient
RestClient 具有静态的 create 快捷方法。它还公开了一个 builder(),其中包含更多选项
一旦创建,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")
.defaultVersion("1.2")
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
.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")
.defaultVersion("1.2")
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
使用 RestClient
要执行 HTTP 请求,首先指定要使用的 HTTP 方法。使用便捷方法,如 get()、head()、post() 等,或 method(HttpMethod)。
请求 URL
接下来,使用 uri 方法指定请求 URI。这是可选的,如果您通过构建器配置了 baseUrl,则可以跳过此步骤。URL 通常指定为 String,带有可选的 URI 模板变量。以下显示了如何执行请求
-
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)。如果客户端配置了 ApiVersionInserter,您可以为请求设置 API 版本。
请求体本身可以通过 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 请求,该请求同样使用 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 Template 类的形式提供了对 HTTP 客户端库的高级 API。它公开了以下几组重载方法
从 Spring Framework 7.0 开始,RestTemplate 已被弃用,转而使用 RestClient,并将在未来版本中移除。请使用 “迁移到 RestClient” 指南。对于异步和流式传输场景,请考虑响应式 WebClient。 |
| 方法组 | 描述 |
|---|---|
|
通过 GET 检索表示。 |
|
通过 GET 检索 |
|
通过 HEAD 检索资源的所有头部。 |
|
通过 POST 创建新资源并返回响应中的 |
|
通过 POST 创建新资源并返回响应中的表示。 |
|
通过 POST 创建新资源并返回响应中的表示。 |
|
通过 PUT 创建或更新资源。 |
|
通过 PATCH 更新资源并返回响应中的表示。请注意,JDK |
|
通过 DELETE 删除指定 URI 处的资源。 |
|
通过 ALLOW 检索资源允许的 HTTP 方法。 |
|
是上述方法的更通用(且不那么主观)的版本,在需要时提供额外的灵活性。它接受 这些方法允许使用 |
|
执行请求的最通用方式,通过回调接口完全控制请求准备和响应提取。 |
初始化
RestTemplate 使用与 RestClient 相同的 HTTP 库抽象。默认情况下,它使用 SimpleClientHttpRequestFactory,但这可以通过构造函数更改。请参阅 客户端请求工厂。
RestTemplate 可以进行可观测性检测,以生成指标和跟踪。请参阅 RestTemplate 可观测性支持 部分。 |
正文
传入和传出 RestTemplate 方法的对象在 HttpMessageConverter 的帮助下转换为 HTTP 消息,请参阅 HTTP 消息转换。
迁移到 RestClient
应用程序可以逐步采用 RestClient,首先关注 API 使用,然后关注基础设施设置。您可以考虑以下步骤
-
从现有的
RestTemplate实例创建一个或多个RestClient,例如:RestClient restClient = RestClient.create(restTemplate)。逐步替换应用程序中组件的RestTemplate用法,首先关注发出请求。请参阅下表中的 API 等效项。 -
一旦所有客户端请求都通过
RestClient实例,您现在可以使用RestClient.Builder来复制您现有的RestTemplate实例创建。由于RestTemplate和RestClient共享相同的基础设施,您可以在设置中重用自定义的ClientHttpRequestFactory或ClientHttpRequestInterceptor。请参阅RestClient构建器 API。
如果在类路径上没有其他库可用,RestClient 将选择由现代 JDK HttpClient 支持的 JdkClientHttpRequestFactory,而 RestTemplate 将选择使用 HttpURLConnection 的 SimpleClientHttpRequestFactory。这可以解释 HTTP 级别运行时微妙的行为差异。
下表显示了 RestTemplate 方法的 RestClient 等效项。
RestTemplate 方法 |
RestClient 等效项 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RestClient 和 RestTemplate 实例在抛出异常(RestClientException 类型位于层次结构顶部)时具有相同的行为。当 RestTemplate 始终针对“4xx”响应状态抛出 HttpClientErrorException 时,RestClient 允许使用自定义的 “状态处理程序” 提供更大的灵活性。
HTTP 服务客户端
您可以将 HTTP 服务定义为具有 @HttpExchange 方法的 Java 接口,并使用 HttpServiceProxyFactory 从中创建一个客户端代理,通过 RestClient、WebClient 或 RestTemplate 进行远程 HTTP 访问。在服务器端,@Controller 类可以实现相同的接口,使用 @HttpExchange 控制器方法来处理请求。
首先,创建 Java 接口
public interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
(可选)在类型级别使用 @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);
}
接下来,配置客户端并创建 HttpServiceProxyFactory
// Using RestClient...
RestClient restClient = RestClient.create("...");
RestClientAdapter adapter = RestClientAdapter.create(restClient);
// or WebClient...
WebClient webClient = WebClient.create("...");
WebClientAdapter adapter = WebClientAdapter.create(webClient);
// or RestTemplate...
RestTemplate restTemplate = new RestTemplate();
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
现在,您可以创建客户端代理了
RepositoryService service = factory.createClient(RepositoryService.class);
// Use service methods for remote calls...
HTTP 服务客户端是一种强大而富有表现力的 HTTP 远程访问选择。它允许一个团队掌握 REST API 的工作方式、哪些部分与客户端应用程序相关、创建哪些输入和输出类型、需要哪些端点方法签名、拥有哪些 Javadoc 等知识。生成的 Java API 易于理解且可直接使用。
方法参数
@HttpExchange 方法支持具有以下输入的灵活方法签名
| 方法参数 | 描述 |
|---|---|
|
动态设置请求的 URL,覆盖注解的 |
|
提供 |
|
动态设置请求的 HTTP 方法,覆盖注解的 |
|
添加一个请求头或多个请求头。参数可以是单个值、 |
|
添加一个变量以扩展请求 URL 中的占位符。参数可以是包含多个变量的 |
|
提供一个 |
|
提供请求体,可以是一个待序列化的对象,也可以是响应式流 |
|
添加一个或多个请求参数。参数可以是包含多个参数的 当 |
|
添加请求部分,可以是字符串(表单字段)、 |
|
从 |
|
添加一个或多个 Cookie。参数可以是包含多个 Cookie 的 |
方法参数不能为 null,除非 required 属性(如果参数注解可用)设置为 false,或者参数被标记为可选,这由 MethodParameter#isOptional 确定。
RestClientAdapter 额外支持 StreamingHttpOutputMessage.Body 类型的方法参数,允许通过写入 OutputStream 发送请求体。
自定义参数
您可以配置自定义的 HttpServiceArgumentResolver。下面的示例接口使用自定义的 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>
}
自定义参数解析器可以这样实现
-
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
}
}
配置自定义参数解析器
-
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)
默认情况下,RequestEntity 不支持作为方法参数,而是鼓励对请求的各个部分使用更细粒度的方法参数。 |
返回值
支持的返回值取决于底层客户端。
适配到 HttpExchangeAdapter 的客户端(例如 RestClient 和 RestTemplate)支持同步返回值
| 方法返回值 | 描述 |
|---|---|
|
执行给定的请求。 |
|
执行给定的请求并返回响应头。 |
|
执行给定的请求并将响应内容解码为声明的返回类型。 |
|
执行给定的请求并返回包含状态和头部的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回包含状态、头部和解码后的正文的 |
适配到 ReactorHttpExchangeAdapter 的客户端(例如 WebClient)支持上述所有功能以及响应式变体。下表显示了 Reactor 类型,但您也可以使用通过 ReactiveAdapterRegistry 支持的其他响应式类型
| 方法返回值 | 描述 |
|---|---|
|
执行给定请求,并释放响应内容(如果有)。 |
|
执行给定请求,释放响应内容(如果有),并返回响应头。 |
|
执行给定的请求并将响应内容解码为声明的返回类型。 |
|
执行给定的请求并将响应内容解码为声明元素类型的流。 |
|
执行给定请求,并释放响应内容(如果有),并返回包含状态和头部的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回包含状态、头部和解码后的正文的 |
|
执行给定请求,将响应内容解码为声明元素类型的流,并返回包含状态、头部和解码后的响应体流的 |
默认情况下,使用 ReactorHttpExchangeAdapter 的同步返回值的超时时间取决于底层 HTTP 客户端的配置方式。您也可以在适配器级别设置 blockTimeout 值,但我们建议依赖底层 HTTP 客户端的超时设置,该客户端在较低级别操作并提供更多控制。
RestClientAdapter 额外支持 InputStream 或 ResponseEntity<InputStream> 类型的返回值,提供对原始响应体内容的访问。
错误处理
要自定义 HTTP 服务客户端代理的错误处理,您可以根据需要配置底层客户端。默认情况下,客户端会对 4xx 和 5xx HTTP 状态码抛出异常。要自定义此行为,请注册一个适用于通过客户端执行的所有响应的响应状态处理程序,如下所示
// For RestClient
RestClient restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
// or for WebClient...
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
// or for RestTemplate...
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
有关更多详细信息和选项(例如抑制错误状态码),请参阅每个客户端的参考文档,以及 RestClient.Builder 或 WebClient.Builder 中的 defaultStatusHandler 的 Javadoc,以及 RestTemplate 的 setErrorHandler。
装饰适配器
HttpExchangeAdapter 和 ReactorHttpExchangeAdapter 是将 HTTP 接口客户端基础设施与调用底层客户端的细节解耦的契约。有针对 RestClient、WebClient 和 RestTemplate 的适配器实现。
有时,通过在 HttpServiceProxyFactory.Builder 中配置装饰器来拦截客户端调用可能很有用。例如,您可以应用内置装饰器来抑制 404 异常并返回带有 NOT_FOUND 和 null 正文的 ResponseEntity
// For RestClient
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restCqlientAdapter)
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
.build();
// or for WebClient...
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builderFor(webClientAdapter)
.exchangeAdapterDecorator(NotFoundWebClientAdapterDecorator::new)
.build();
HTTP 服务组
使用 HttpServiceProxyFactory 创建客户端代理是微不足道的,但将它们声明为 bean 会导致重复配置。您可能还有多个目标主机,因此需要配置多个客户端,甚至需要创建更多的客户端代理 bean。
为了更轻松地大规模使用接口客户端,Spring Framework 提供了专门的配置支持。它允许应用程序专注于按组识别 HTTP 服务,并为每个组自定义客户端,而框架透明地创建客户端代理注册表,并将每个代理声明为 bean。
HTTP 服务组简单来说就是一组共享相同客户端设置和 HttpServiceProxyFactory 实例来创建代理的接口。通常,这意味着每个主机一个组,但如果底层客户端需要以不同方式配置,则同一目标主机可以有多个组。
声明 HTTP 服务组的一种方式是通过 @Configuration 类中的 @ImportHttpServices 注解,如下所示
@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class}) (1)
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) (2)
public class ClientConfig {
}
| 1 | 手动列出“echo”组的接口 |
| 2 | 在一个基础包下检测“greeting”组的接口 |
也可以通过创建 HTTP 服务注册器然后导入它来以编程方式声明组
public class MyHttpServiceRegistrar extends AbstractHttpServiceRegistrar { (1)
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
registry.forGroup("echo").register(EchoServiceA.class, EchoServiceB.class); (2)
registry.forGroup("greeting").detectInBasePackages(GreetServiceA.class); (3)
}
}
@Configuration
@Import(MyHttpServiceRegistrar.class) (4)
public class ClientConfig {
}
| 1 | 创建 AbstractHttpServiceRegistrar 的扩展类 |
| 2 | 手动列出“echo”组的接口 |
| 3 | 在一个基础包下检测“greeting”组的接口 |
| 4 | 导入注册器 |
您可以混合搭配 @ImportHttpService 注解和编程注册器,并且可以将导入分散到多个配置类中。所有导入都协同作用于相同的共享 HttpServiceProxyRegistry 实例。 |
声明 HTTP 服务组后,添加 HttpServiceGroupConfigurer bean 以自定义每个组的客户端。例如
@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class})
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class)
public class ClientConfig {
@Bean
public RestClientHttpServiceGroupConfigurer groupConfigurer() {
return groups -> {
// configure client for group "echo"
groups.filterByName("echo").forEachClient((group, clientBuilder) -> ...);
// configure the clients for all groups
groups.forEachClient((group, clientBuilder) -> ...);
// configure client and proxy factory for each group
groups.forEachGroup((group, clientBuilder, factoryBuilder) -> ...);
};
}
}
Spring Boot 使用 HttpServiceGroupConfigurer 添加对 HTTP 服务组的客户端属性支持,Spring Security 添加 OAuth 支持,Spring Cloud 添加负载均衡。 |
因此,每个客户端代理都可作为 bean 使用,您可以方便地按类型自动装配
@RestController
public class EchoController {
private final EchoService echoService;
public EchoController(EchoService echoService) {
this.echoService = echoService;
}
// ...
}
但是,如果存在多个相同类型的客户端代理,例如在多个组中的相同接口,则不存在该类型的唯一 bean,您不能仅按类型自动装配。对于这种情况,您可以直接使用包含所有代理的 HttpServiceProxyRegistry,并按组获取您需要的代理
@RestController
public class EchoController {
private final EchoService echoService1;
private final EchoService echoService2;
public EchoController(HttpServiceProxyRegistry registry) {
this.echoService1 = registry.getClient("echo1", EchoService.class); (1)
this.echoService2 = registry.getClient("echo2", EchoService.class); (2)
}
// ...
}
| 1 | 访问“echo1”组的 EchoService 客户端代理 |
| 2 | 访问“echo2”组的 EchoService 客户端代理 |