旧得HTTP 服务注册表
HTTP 服务注册表社区里已经有非常成熟的解决方案(特指Spring Cloud OpenFeign),并且在不同的业务场景下积累了大量的使用模式和最佳实践。
为什么不像以前那样只是增强OpenFeign,而是选择在Spring Framework核心中内置这个功能:
- 摆脱历史包袱: OpenFeign是为了解决特定时期的问题而生的,其设计可能包含了为兼容旧版本或特定云环境而产生的复杂性。新建一个功能可以抛开这些约束,从零开始设计一个更简洁、更现代的API。
- 追求更优设计: 他们希望创造一个更精简(代码更少、配置更简单)、更通用(不依赖特定云组件,所有Spring应用都能无障碍使用)的解决方案。
大家用OpenFeign已经很久了,各种玩法都很熟。但我们这次想做个不一样的、更简单好用的基础版。虽然它可能没法一下子满足所有高级需求,但我们觉得这个新思路更好。
Spring Framework 6 引入了通过带有 @HttpExchange 注解方法的 Java 接口来定义 HTTP 服务的能力。例如:
java
public interface MilestoneService {
@GetExchange("/repos/{org}/{repo}/milestones")
List getMilestones(@PathVariable String org, @PathVariable String repo);
}
在客户端,你可以从接口生成一个代理来执行 HTTP 请求,如下所示:
java
// 初始化 HTTP 客户端
RestClient restClient = RestClient.create("https://api.github.com");
// 为客户端代理创建工厂
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder()
.exchangeAdapter(RestClientAdapter.create(restClient))
.build();
// 创建客户端代理
MilestoneService client = proxyFactory.createClient(MilestoneService.class);
// 使用代理进行 HTTP 请求
List milestones = client.getMilestones("spring-projects", "spring-framework");
在服务器端,如果一个 @Controller 类实现了相同的接口,它就可以处理请求(前提是这个 HTTP 服务是你自己拥有的)。
HTTP 服务客户端支持功能强大、表达力强且易于使用。它允许一个团队掌握 REST API 的工作方式、哪些部分与客户端应用相关、创建什么输入输出类型、需要什么样的端点方法签名、需要什么 javadoc 等等。最终产生的 Java API 可以指导开发者并直接使用。
这些模式长期以来在 Spring Cloud OpenFeign 中使用,现在所有 Spring Framework 6+ 应用程序都可以通过 RestClient、RestTemplate 或 WebClient 来使用。HTTP 服务客户端支持在 6.x 版本线中经历了大量基于反馈的演变,但一个主要的挑战仍然存在大量问题。
配置开销
创建一个 HttpServiceProxyFactory 并用它来生成一个、两个或三个客户端代理很简单,但随着数量增长,这就变得重复和繁琐,尤其是客户端代理通常被声明为 Spring bean。考虑以下例子:
java
@Bean
MilestoneService milestoneService(HttpServiceProxyFactory factory) {
return factory.createClient(MilestoneService.class);
}
@Bean
ReleaseService releaseService(HttpServiceProxyFactory factory) {
return factory.createClient(ReleaseService.class);
}
// ... 更多客户端bean ...
@Bean
HttpServiceProxyFactory proxyFactory(RestClient.Builder clientBuilder) {
RestClient client = clientBuilder.baseUrl("https://api.github.com").build();
return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(client)).build();
}
REST API 会暴露许多细粒度的端点。GitHub API 有几十个,甚至几百个,虽然你肯定不需要全部,但根据实际需求,最终很容易至少有十几个或更多。
此外,与多个 REST API 集成是很常见的,这意味着会有更多的接口以及更复杂的底层 HTTP 客户端配置。
HTTP 服务注册表
为了解决这个挑战,Spring Framework 7 在 HttpServiceProxyFactory 之上引入了一个额外的注册表层,它提供以下功能:
* 用于注册 HTTP 接口和初始化 HTTP 客户端基础设施的配置模型。
* 将客户端代理透明地创建并注册为 Spring bean。
* 通过 HttpServiceProxyRegistry 访问所有客户端代理。
在配置模型中,HTTP 服务按组进行组织,一个组就是一组共享相同 HTTP 客户端配置(以及由此产生的客户端实例)的 HTTP 服务。
目前,有两种声明 HTTP 服务的方式。
声明式注册
一种声明 HTTP 服务组的方式是通过 @ImportHttpServices 注解(Spring Framework 7 中的新功能)。
你可以用它来按组手动列出 HTTP 服务:
java
@ImportHttpServices(group = "github", types = {MilestoneService.class, … })
@ImportHttpServices(group = "stackoverflow", types = {QuestionService.class, … })
@Configuration
public class DemoConfig {
}
或者让它们在某个基础包下被自动检测到:
java
@ImportHttpServices(group = "github", basePackages = "client.github")
@ImportHttpServices(group = "stackoverflow", basePackages = "client.stackoverflow")
@Configuration
public class DemoConfig {
}
默认情况下,一个 HTTP 服务组使用 RestClient 进行配置,但你可以通过注解的 clientType 属性切换到 WebClient。
编程式注册
如果你需要对过滤或其他注册逻辑有更多控制,你也可以分两步以编程方式声明 HTTP 服务。
首先,创建一个注册器来声明 HTTP 服务组:
java
public class CustomHttpServiceRegistrar extends AbstractHttpServiceRegistrar {
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
registry.forGroup("github").detectInBasePackages("client.github");
// ... 更多注册 ...
}
}
然后导入这个注册器:
java
@Configuration
@Import(CustomHttpServiceRegistrar.class)
public class ClientConfig {
}
请注意,声明式和编程式注册都依赖于 ImportBeanDefinitionRegistrar,它在 Spring 配置生命周期的非常早期(bean 定义级别)就介入。这使得客户端代理 bean 可用于依赖注入,并有助于避免生命周期问题。
HTTP 客户端初始化
一旦 HTTP 服务组被声明,剩下的就是为每个组配置 HTTP 客户端。你可以使用一个 HttpServiceGroupConfigurer bean 方法来实现。例如:
java
@Bean
RestClientHttpServiceGroupConfigurer groupConfigurer() {
return groups -> {
groups.filterByName("github").forEachClient((_, builder) ->
builder.baseUrl("https://api.github.com"));
groups.filterByName("stackoverflow").forEachClient((_, builder) ->
builder.baseUrl("https://api.stackexchange.com?site=stackoverflow"));
};
}
你可以根据需要拥有任意多个 HTTP 服务组配置器,这些配置器可以由应用程序或框架提供。
例如,Spring Boot 4.0 通过其 RestClient 和 WebClient 自动配置,透明地对每个组应用 HTTP 客户端构建器的初始化。此外,它还提供了按组分组的 HTTP 客户端属性支持:
#全局设置,适用于所有组 |
Spring Cloud 2025.1 为 HTTP 服务组提供了透明的负载均衡和熔断支持。
Spring Security 7.0 为 HTTP 服务组提供了 OAuth 支持,可以检测 @HttpExchange 方法上的 @ClientRegistrationId 注解。其他认证支持正在考虑中(参见 spring-security#17940)。
总结
新的 HTTP 服务注册表允许应用程序声明 HTTP 服务并配置底层客户端基础设施,而框架则完成其余的工作。它也是一个可扩展的机制,用于实现强大的、开箱即用的 HTTP 客户端初始化功能。