简介
在 Spring Cloud 微服务生态中,OpenFeign 一直是服务间调用的核心组件。它通过声明式接口与注解,极大地简化了远程 HTTP 调用,屏蔽了底层细节,并自动集成了负载均衡与服务发现功能。
然而,根据 Spring Cloud 官方在 2022.0.0 版本发布博客中的说明,OpenFeign 项目已被视为“功能完备”,未来仅会进行错误修复和小的社区功能合并。官方明确建议开发者迁移至 Spring HTTP Service Clients。
自 Spring Cloud 5.0.0 起,其负载均衡组件 Spring Cloud LoadBalancer 已通过 LoadBalancerRestClientHttpServiceGroupConfigurer 和 LoadBalancerWebClientHttpServiceGroupConfigurer 为 Spring HTTP 服务客户端提供了自动配置支持。本文将结合实战案例,详细介绍如何利用 @HttpExchange 等注解构建声明式 HTTP 客户端,并实现服务发现与负载均衡。
实战案例
1. 定义远程接口服务
首先,我们创建一个提供 REST API 的服务器应用。其 BookController 接口定义如下:
@RestController
@RequestMapping("/books")
public class BookController {
@Value("${server.port}")
private Integer port ;
@GetMapping("")
public ResponseEntity<Map<String, Object>> books() throws Exception {
TimeUnit.SECONDS.sleep(2) ;
List<Book> data = List.of(
new Book(1L, "Spring Boot3实战案例200讲", "pack_xg", BigDecimal.valueOf(70D)),
new Book(2L, "Spring全家桶实战案例", "pack", BigDecimal.ZERO)
);
return ResponseEntity.ok(Map.of("server", port, "data", data)) ;
}
}
为了模拟多个服务实例,我们通过启动参数指定不同端口来启动两个服务:
--server.port=8081
--server.port=8082
启动服务后,控制台日志显示两个实例分别在 8081 和 8082 端口成功启动。



2. 声明式服务定义
接下来,我们在客户端新建一个项目,并定义声明式服务接口。关键接口 BookService 如下:
@HttpExchange("http://book")
public interface BookService {
@GetExchange("/books")
Map<String, Object> query() ;
}
说明:@HttpExchange("http://book") 中的 book 将被用作服务名。
3. 配置声明式服务
定义好接口后,需要通过配置类将其注册到 Spring 容器中:
@ImportHttpServices(group = "book", types = {BookService.class})
@Configuration
public class ClientConfig{
}
这里使用了 @ImportHttpServices 注解。group 属性非常重要,它的值(本例中为 "book")将影响客户端的行为,具体规则如下:
- 基础URL为空的情况:如果未在配置文件中设置
spring.http.serviceclient.book.base-url,系统会自动以组名(book)作为服务ID,生成负载均衡URL。初始使用 http 方案,若负载均衡器选中的服务实例支持HTTPS,则会自动切换为 https。
- 基础URL方案为
lb 的情况:若 baseUrl 设置为 lb://[serviceId]/path 格式(例如 lb://book/path),系统会保留该URL。初始默认方案仍为 http,同样会在选中安全实例时切换为 https。此时,系统会自动为阻塞式(RestClient)或响应式(WebClient)客户端添加负载均衡拦截器,实现请求的负载均衡分发。
- 基础URL方案非
lb 的情况:若 baseUrl 设置为非 lb 方案的固定URL(如 http://example.com/path),则不会应用负载均衡功能,客户端将直接使用该固定地址发起请求。
4. 配置服务实例(静态注册)
在本示例中,我们没有使用 Nacos、Eureka 等服务注册中心,因此采用静态配置的方式注册服务实例。在 application.yml 中配置如下:
spring:
cloud:
discovery:
client:
simple:
instances:
book:
- instanceId: s_v1
serviceId: ss
host: localhost
port: 8081
metadata:
gray: s1
- instanceId: s_v2
serviceId: ss
host: localhost
port: 8082
完成上述配置后,Spring Cloud 将使用 SimpleDiscoveryClient,它直接从配置属性中获取服务和实例信息。

5. 关键配置项开启
有一个容易忽略但至关重要的步骤:必须手动启用 HttpServiceClientProperties 配置。否则,应用启动时会报 URI with undefined scheme 错误。
@SpringBootApplication
@EnableConfigurationProperties({HttpServiceClientProperties.class})
public class App{
}

6. 测试验证
最后,我们创建一个控制器来注入并使用上面定义的 BookService:
@RestController
@RequestMapping("/books")
public class BookController {
private final BookService bookService ;
public BookController(BookService bookService) {
this.bookService = bookService ;
}
@GetMapping("")
public ResponseEntity<?> query() {
return ResponseEntity.ok(this.bookService.query()) ;
}
}
连续访问该接口两次,观察返回结果中的 server 字段:
第一次访问返回来自 8081 端口的数据:

第二次访问返回来自 8082 端口的数据:

测试结果表明,基于 @HttpExchange 的声明式客户端成功实现了在 localhost:8081 和 localhost:8082 两个服务实例间的负载均衡调用。
总结
通过本案例,我们完整演示了在 Spring Boot 3.5.0 环境中,如何使用 Spring Framework 6 提供的 @HttpExchange 声明式 HTTP 服务客户端替代传统的 OpenFeign。整个过程涵盖了接口定义、服务组配置、静态服务发现注册以及负载均衡的集成。这种方式更贴近 Spring 框架的原生特性,简化了依赖,是未来微服务间通信的推荐实践。对于更深入的微服务架构讨论和更多实战技巧,欢迎关注 云栈社区 的更新。