Skip to main content

Spring 7.0 新特性!彻底告别HTTP客户端配置地狱

引言

自 Spring Framework 6 引入 @HttpExchange 注解以来,通过 Java 接口定义 HTTP 服务变得前所未有的简单。在AI时代,这一特性显得尤为重要,特别是在调用各种第三方AI算力服务时。 如上图所示,PIG AI 充分利用了 HTTP Interface 功能的强大能力,实现了对非标准(非OpenAI)协议接口的完美兼容。通过这种方式,我们不仅满足了 Spring AI 和 LangChain4j 等框架对高级Service概念的要求,还能够复用现有的模型监控体系,让代码结构更加清晰和可维护。例如:

AI Services 的强大能力

由于 LLM 驱动的应用程序通常不仅需要单个组件,还需要多个组件协同工作(例如,提示模板、聊天记忆、LLM、输出解析器、RAG 组件:嵌入模型和存储)并且经常涉及多次交互,协调所有这些组件变得更加繁琐。 开放人员专注于业务逻辑,而不是低级实现细节。因此 Java AI 目前可以通过 HTTP Interface 的方式,我们可以优雅地将这些 AI 服务封装为标准的 Spring Bean,实现统一的配置和管理。
public interface MilestoneService {

    @GetExchange("/repos/{org}/{repo}/milestones")
    List<Milestone> getMilestones(@PathVariable String org, @PathVariable String repo);
}
在客户端,你可以这样生成代理来执行 HTTP 请求:
// 初始化 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<Milestone> milestones = client.getMilestones("spring-projects", "spring-framework");
在服务端,@Controller 类可以实现相同的接口来处理请求(如果 HTTP 服务是你自己的)。

配置开销的痛点

QSNgdr HTTP 服务客户端支持功能强大、表达力强且易于使用。但是,随着客户端代理数量的增长,配置变得重复且繁琐,特别是客户端代理通常需要声明为 Spring beans 时。如上图所示,当需要接入非标准模型(如智谱AI、通义千问等)时,往往需要为每个服务提供商创建单独的 service factory,这进一步加剧了配置的复杂性和重复性。 考虑以下示例:
@Bean
MilestoneService milestoneService(HttpServiceProxyFactory factory) {
    return factory.createClient(MilestoneService.class);
}

@Bean
ReleaseService releaseService(HttpServiceProxyFactory factory) {
    return factory.createClient(ReleaseService.class);
}

// 更多客户端 beans...

@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 beans
  • 统一访问:通过 HttpServiceProxyRegistry 访问所有客户端代理
在配置模型中,HTTP 服务按组组织,其中一个组只是一组共享相同 HTTP 客户端配置和生成的客户端实例的 HTTP 服务。

声明式注册

使用 @ImportHttpServices 注解(Spring Framework 7 新增)可以声明 HTTP 服务组。

手动列出 HTTP 服务

@ImportHttpServices(group = "github", types = {MilestoneService.class, ...})
@ImportHttpServices(group = "stackoverflow", types = {QuestionService.class, ...})
@Configuration
public class DemoConfig {
}

包扫描检测

@ImportHttpServices(group = "github", basePackages = "client.github")
@ImportHttpServices(group = "stackoverflow", basePackages = "client.stackoverflow")
@Configuration
public class DemoConfig {
}
HTTP 服务组默认配置为 RestClient,但你可以通过注解的 clientType 属性切换到 WebClient

程序化注册

如果你需要对过滤或其他注册逻辑有更多控制,也可以通过两个步骤以编程方式声明 HTTP 服务。

步骤 1:创建注册器

public class CustomHttpServiceRegistrar extends AbstractHttpServiceRegistrar { 

    @Override
    protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
        registry.forGroup("github").detectInBasePackages("client.github");
        // 更多注册...
    }
}

步骤 2:导入注册器

@Configuration
@Import(CustomHttpServiceRegistrar.class) 
public class ClientConfig {
}

HTTP 客户端初始化

声明了 HTTP 服务组后,剩下的就是为每个组配置 HTTP 客户端。你可以使用 HttpServiceGroupConfigurer bean 方法:
@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"));
    };
}

生态系统集成

Spring Boot 4.0 集成

Spring Boot 4.0 透明地通过其 RestClientWebClient 自动配置为每个组应用 HTTP 客户端构建器的初始化。还提供了按组的 HTTP 客户端属性支持:
# 全局配置,适用于所有组
spring.http.client.service.read-timeout=2s

# GitHub 组配置
spring.http.client.service.group.github.base-url=https://api.github.com

# Stackoverflow 组配置
spring.http.client.service.group.stackoverflow.base-url=https://api.stackexchange.com

其他集成

  • Spring Cloud 2025.1:为 HTTP 服务组提供透明的负载均衡和熔断支持
  • Spring Security 7.0:为检测 @HttpExchange 方法上的 @ClientRegistrationId 注解的 HTTP 服务组提供 OAuth 支持

总结

新的 HTTP 服务注册表让应用程序可以声明 HTTP 服务并配置底层客户端基础设施,而框架完成其余工作。这也是一个可扩展的机制,用于强大的、开箱即用的 HTTP 客户端初始化功能。

主要优势

  • 简化配置:告别重复的 bean 声明
  • 统一管理:按组组织 HTTP 服务
  • 生态集成:与 Spring Boot、Spring Cloud、Spring Security 无缝集成
  • 灵活配置:支持声明式和程序化两种方式
Spring 团队知道在这个主题上有很多场景和观点,也有使用 OpenFeign 的长期经验。同时,当我们将功能引入 Spring Framework 时,这是一个以全新视角重新审视它的机会,并提供一些更简单和更广泛有用的东西。 参考资料:Spring 官方博客 - HTTP Service Client Enhancements