开发者

Spring Cloud OpenFeign实现动态服务名调用的示例代码

目录
  • 场景背景
  • 错误用法:FeignClientFactory
  • 正确方式:自定义动态 Feign 客户端工厂
    • 核心思路:
  • 一、配置 Feign.Builder
    • 二、自定义动态客户端工厂
      • 三、使用方式
        • 原始写法(错误):
        • 正确写法:
      • 补充说明
        • 1. DynamicFeignClientFactory 类
        • 2. FeignBuilderConfig 类
        • 总结

      场景背景

      在微服务架构中,我们经常需要根据动态传入的服务名来远程调用其他服务。例如,你的业务中可能有多个子服务:service-1service-2……需要动态决定调用哪个。

      通常我们使用如下方式注入 Feign 客户端:

       @FeignClient(name = "service")
       public interface FeignClient {
           @PostMapping("/api/push")
           void pushMessage(@RequestBody PushMessageRequest request);
       }
      

      但这种写法服务名是静态写死的,不能根据运行时的参数进行动态选择。

      错误用法:FeignClientFactory

      很多开发者会尝试用 Spring 内部的 FeignClientFactory

       @Resource
       private FeignClientFactory feignClientFactory;
       ​
       FeignClient FeignClient = feignClientFactory.getInstance(serviceName, FeignClient.class);
      

      这种方式只能获取 @FeignClient(name="xxx") 注册的静态实例,而不能真正实现动态服务调用。

      • 适用场景:获取已经 @FeignClient 声明过的 bean。
      • 不适用:动态服务名(如从数据库或配置中传入)+ 动态构建 Feign 实例。

      正确方式:自定义动php态 Feign 客户端工厂

      要想实现真正的动态服务名 + 负载均衡 + 支持配置和拦截器的 Feign 客户端,我们需要手动构造并注入 Feign 客户端

      核心思路:

      • 使用 Spring Cloud 提供的 Feign.Builder(必须是 Spring 注入的)
      • 配合 LoadBalancerClient 实现服务发现与负载均衡
      • 手动构建 Feign 接口实例

      一、配置 Feign.Builder

       @Configuration
      javascript public class FeignBuilderConfig {
       ​
           @Bean
           @Scope("prototype")
           public Feign.Builder feignBuilder(ObjectFactory<HttpMessageConverters> messageConverters) {
               return Feign.builder()
                       .contract(new SpringMvcContract())
                       .encoder(new SpringEncoder(messageConverters))
                       .decoder(new SpringDecoder(messageConverters))
                       .retryer(new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3))
                       .options(new Request.Options(3000, 5000))
                       .logger(new Logger.ErrorLogger())
                       .logLevel(Logger.Level.BASIC);
           }
       }
      

      二、自定义动态客户端工厂

       @Component
       @Slf4j
       public class DynamicFeignClientFactory {
       ​
           private final Feign.Builder feignBuilder;
           private final LoadBalancerClient loadBalancerClient;
       ​
      编程客栈     public DynamicFeignClientFactory(Feign.Builder feignBuilder,
                                            LoadBalancerClient loadBalancerClient) {
               this.feignBuilder = feignBuilder;
               this.loadBalancerClient = loadBalancerClient;
           }
       ​
           public <T> T getClient(String serviceName, Class<T> clazz) {
               int maxRetry = 3;
               int retryCount = 0;
               Exception lastException = null;
       ​
               while (retryCount < maxRetry) {
                   try {
                       ServiceInstance instance = loadBalancerClient.choose(serviceName);
                       if (instance == null) {
                           throw new RuntimeException("未找到可用的服务实例:" + serviceName);
                       }
       ​
                       String url = instance.getUri().toString();
                       log.info("选择的 Feign 客户端目标地址为:{}", url);
                       return feignBuilder.target(clazz, url);
       ​
                   } catch (Exception e) {
                       lastException = e;
                       log.warn("第 {} 次尝试获取 Feign 客户端失败,服务名:{},错误信息:{}", retryCount + 1, serviceName, e.getMessage());
                       retryCount++;
                       try {
                           Thread.sleep(500L);
                       } catch (InterruptedException ignored) {}
                   }
               }
       ​
               throw new RuntimeException("创建 Feign 客户端失败,服务名:" + serviceName, lastException);
           }
       }
      

      三、使用方式

      原始写法(错误):

       @Resource
       private FeignClientFactory feignClientFactory;
       ​
       FeignClient FeignClient = feignClientFactory.getInstance(serviceName, FeignClient.class); 
      

      正确写法:

      @Resource
      private DynamicFeignClientFactory feignClientFactory;
      
      FeignClient FeignClient = feignClientFactory.getClient(ServerName, FeignClient.class);
      FeignClient.pushMessage(new PushMessageRequest(Ids, senderEventMessage));
      

      补充说明

      • Spring 注入的 Feign.Builder 会自动继承全局配置(超时、日志、拦截器等)。
      • 支持服务名动态路由,自动走 Spring Cloud LoadBalancer。
      • 每次调用可绑定到不同的服务实例(支持轮询/自定义负载策略)。
      • 避免直接 new Feign.Builder(),否则会失去 Spring 集成能力。

      1. DynamicFeignClientFactory 类

       @Component
       @Slf4j
       public class DynamicFeignClientFactory {
       ​
           private final Feign.Builder feignBuilder;
           private final LoadBalancerClient loadBalancerClient;
       ​
           public DynamicFeignClientFactory(Feign.Builder feignBuilder,
                                            LoadBalancerClient loadBalancerClient) {
          编程客栈     this.feignBuilder = feignBuilder;
               this.loadBalancerClient = loadBalancerClient;
           }
       ​
           public <T> T getClient(String serviceName, Class<T> clazz) {
               ...
           }
       }
      

      功能说明:

      这是 动态创建 Feign 客户端 的核心工厂类,解决了 Spring Cloud @FeignClient 无法支持运行时动态服务名的问题。

      核心逻辑:

      • 使用 Spring 提供的 LoadBalancerClient 动态选择某个服务的实例(支持 Eureka/Nacos 等注册中心)。
      • 使用 Spring 注入的 Feign.Builder 构建 Feign 客户端实例,绑定目标实例地址
      • 加了简单的重试逻辑(最多3次),提升服务不稳定时的容错性。

      为什么不能直接用http://www.devze.com FeignClientFactory

      • FeignClientFactory#getInstance 是静态注册的,依赖启动时的 @FeignClient(name="xxx")不能做到动态服务名运行时创建实例
      • 而本类是自己构造目标地址,可通过服务名运行时切换服务。

      2. FeignBuilderConfig 类

       @Configuration
       public class FeignBuilderConfig {
       ​
           @Bean
           @Scope("prototype")
           public Feign.Builder feignBuilder(ObjectFactory<HttpMessageConverters> messageConverters) {
               return Feign.builder()
                       .contract(new SpringMvcContract())
                       .encoder(new SpringEncoder(messageConverters))
                       .decoder(new SpringDecoder(messageConverters))
                       .retryer(new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3))
                       .options(new Request.Options(3000, 5000))
                       .logger(new Logger.ErrorLogger())
                       .logLevel(Logger.Level.BASIC);
           }
       }
      

      功能说明:

      这是自定义的 Feign 构造器配置,确保动态创建的 Feign 实例拥有 Spring 的 HTTP 编解码器、契约协议、超时、重试等设置

      关键配置解读:

      配置项作用说明
      SpringMvcContract让 Feign 支持 @RequestMapping、@GetMapping 等 Spring MVC 风格注解
      SpringEncoder/Decoder使用 Spring Boot 的 HttpMessageConverter 做 jsON 编解码(默认支持 Jackson、Gson 等)
      Retryer.Default(...)设置重试机制:初始延迟100ms,最大延迟1s,最多重试3次
      Request.Options(...)设置连接超时为3秒,请求响应超时为5秒
      Logger.ErrorLogger + BASIC开启日志,仅记录错误请求的基本信息(节省性能)
      @Scope("prototype")每次注入都创建一个新的 Feign.Builder(防止多实例干扰)

      为什么不能直接用 Feign.builder()

      如果你直接用 Feign.builder()

      • 不具备 Spring 编解码器能力;
      • 没有 Spring 的日志、重试、超时等配置支持;
      • 无法识别 @RequestMapping 等注解;
      • 无法使用负载均衡(因为没注入 LoadBalancerClient);

      你必须用 Spring 注入的 Feign.Builder,并设置好契约与编解码器,才能让它具备 @FeignClient 的能力。

      总结

      配置类作用是否必须
      DynamicFeignClientFactory实现动态服务名绑定并构建 Feign 客户端
      FeignBuilderConfig注入支持 Spring 编解码、契约协议、重试、超时等功能的构造器

      这两个配置类结合起来,实现了 “动态服务发现 + 动态客户端构建 + Spring 完整能力支持” ,是 Spring Cloud Feign 动态服务名调用的标准做法之一。

      以上就是Spring Cloud OpenFeign实现动态服务名调用的示例代码的详细内容,更多关于Spring Cloud OpenFeign服务名调用的资料请关注编程客栈(www.devze.com)其它相关文章!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜