目录
- 场景背景
- 错误用法:FeignClientFactory
- 正确方式:自定义动态 Feign 客户端工厂
- 核心思路:
- 一、配置 Feign.Builder
- 二、自定义动态客户端工厂
- 三、使用方式
- 原始写法(错误):
- 正确写法:
- 补充说明
- 1. DynamicFeignClientFactory 类
- 2. FeignBuilderConfig 类
- 总结
场景背景
在微服务架构中,我们经常需要根据动态传入的服务名来远程调用其他服务。例如,你的业务中可能有多个子服务:service-1、service-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)其它相关文章!
加载中,请稍侯......
精彩评论