REST 客户端
Spring Framework 提供了以下选项来调用 REST 端点:
-
RestClient— 具有流畅 API 的同步客户端 -
WebClient— 具有流畅 API 的非阻塞、响应式客户端 -
RestTemplate— 具有模板方法 API 的同步客户端 -
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) 访问响应头和响应体
|
单独调用 |
此示例展示了如何使用 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 请求,该 JSON 再次使用 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() 时,状态处理器不会被应用,因为交换函数已经提供了对完整响应的访问,允许您执行任何必要的错误处理。
- 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();
Multipart
要发送 multipart 数据,您需要提供一个 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。最后,它将回退到简单的默认值。
|
请注意, |
WebClient
WebClient 是一个非阻塞的响应式客户端,用于执行 HTTP 请求。它在
5.0 中引入,提供 RestTemplate 的替代方案,支持
同步、异步和流式传输场景。
WebClient 支持以下功能:
-
非阻塞 I/O
-
响应式流背压
-
以更少的硬件资源实现高并发
-
利用 Java 8 Lambda 的函数式、流畅 API
-
同步和异步交互
-
向上游或向下游流式传输
有关更多详细信息,请参阅 WebClient。
RestTemplate
RestTemplate 以经典的 Spring 模板类的形式,为 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 接口客户端
您可以将 HTTP 服务定义为带有 @HttpExchange 方法的 Java 接口,并使用
HttpServiceProxyFactory 从中创建客户端代理,通过 HTTP 远程访问,使用
RestClient、WebClient 或 RestTemplate。在服务器端,@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...
方法参数
@HttpExchange 方法支持灵活的方法签名,具有以下输入:
| 方法参数 | 描述 |
|---|---|
|
动态设置请求的 URL,覆盖注解的 |
|
提供 |
|
动态设置请求的 HTTP 方法,覆盖注解的 |
|
添加一个或多个请求头。参数可以是单个值、
|
|
添加一个变量以扩展请求 URL 中的占位符。参数可以是
包含多个变量的 |
|
提供一个 |
|
提供请求体,可以是要序列化的 Object,也可以是
|
|
添加一个或多个请求参数。参数可以是包含多个参数的 当 |
|
添加请求部分,可以是 String(表单字段)、 |
|
从 |
|
添加一个或多个 cookie。参数可以是包含多个 cookie 的 |
方法参数不能为 null,除非 required 属性(在参数注解上可用)设置为 false,或者参数被标记为可选,由 MethodParameter#isOptional 确定。
RestClientAdapter 为 StreamingHttpOutputMessage.Body 类型的方法参数提供额外支持,允许通过写入 OutputStream 发送请求体。
自定义参数
您可以配置自定义 HttpServiceArgumentResolver。下面的示例接口使用自定义 Search 方法参数类型:
自定义参数解析器可以这样实现:
配置自定义参数解析器:
|
默认情况下, |
返回值
支持的返回值取决于底层客户端。
适配到 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}) [id="CO13-1"][id="CO1-1"][id="CO13-1"](1)
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) [id="CO13-2"][id="CO1-2"][id="CO13-2"](2)
public class ClientConfig {
}
<1> 手动列出“echo”组的接口 <1> 在基本包下检测“greeting”组的接口
也可以通过创建 HTTP 服务注册器然后导入它来以编程方式声明组:
public class MyHttpServiceRegistrar extends AbstractHttpServiceRegistrar { [id="CO14-1"][id="CO1-3"][id="CO14-1"](1)
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
registry.forGroup("echo").register(EchoServiceA.class, EchoServiceB.class); [id="CO14-2"][id="CO1-4"][id="CO14-2"](2)
registry.forGroup("greeting").detectInBasePackages(GreetServiceA.class); [id="CO14-3"][id="CO1-5"][id="CO14-3"](3)
}
}
@Configuration
@Import(MyHttpServiceRegistrar.class) [id="CO14-4"][id="CO1-6"][id="CO14-4"](4)
public class ClientConfig {
}
<1> 创建 `AbstractHttpServiceRegistrar` 的扩展类 <1> 手动列出“echo”组的接口 <1> 在基本包下检测“greeting”组的接口 <1> 导入注册器
|
您可以混合使用 |
一旦声明了 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 使用 |
上述操作的结果是,每个客户端代理都作为一个 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); [id="CO15-1"][id="CO1-7"][id="CO15-1"](1)
this.echoService2 = registry.getClient("echo2", EchoService.class); [id="CO15-2"][id="CO1-8"][id="CO15-2"](2)
}
// ...
}
<1> 访问“echo1”组的 `EchoService` 客户端代理 <1> 访问“echo2”组的 `EchoService` 客户端代理