Spring Cloud OpenFeign實現(xiàn)動態(tài)服務名調用的示例代碼
場景背景
在微服務架構中,我們經常需要根據(jù)動態(tài)傳入的服務名來遠程調用其他服務。例如,你的業(yè)務中可能有多個子服務:service-1
、service-2
……需要動態(tài)決定調用哪個。
通常我們使用如下方式注入 Feign 客戶端:
@FeignClient(name = "service") public interface FeignClient { @PostMapping("/api/push") void pushMessage(@RequestBody PushMessageRequest request); }
但這種寫法服務名是靜態(tài)寫死的,不能根據(jù)運行時的參數(shù)進行動態(tài)選擇。
錯誤用法:FeignClientFactory
很多開發(fā)者會嘗試用 Spring 內部的 FeignClientFactory
:
@Resource private FeignClientFactory feignClientFactory; ? FeignClient FeignClient = feignClientFactory.getInstance(serviceName, FeignClient.class);
這種方式只能獲取 @FeignClient(name="xxx")
注冊的靜態(tài)實例,而不能真正實現(xiàn)動態(tài)服務調用。
- 適用場景:獲取已經
@FeignClient
聲明過的 bean。 - 不適用:動態(tài)服務名(如從數(shù)據(jù)庫或配置中傳入)+ 動態(tài)構建 Feign 實例。
正確方式:自定義動態(tài) Feign 客戶端工廠
要想實現(xiàn)真正的動態(tài)服務名 + 負載均衡 + 支持配置和攔截器的 Feign 客戶端,我們需要手動構造并注入 Feign 客戶端。
核心思路:
- 使用 Spring Cloud 提供的
Feign.Builder
(必須是 Spring 注入的) - 配合
LoadBalancerClient
實現(xiàn)服務發(fā)現(xiàn)與負載均衡 - 手動構建 Feign 接口實例
一、配置 Feign.Builder
@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); } }
二、自定義動態(tài)客戶端工廠
@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("創(chuàng)建 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
會自動繼承全局配置(超時、日志、攔截器等)。 - 支持服務名動態(tài)路由,自動走 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) { ... } }
功能說明:
這是 動態(tài)創(chuàng)建 Feign 客戶端 的核心工廠類,解決了 Spring Cloud @FeignClient
無法支持運行時動態(tài)服務名的問題。
核心邏輯:
- 使用 Spring 提供的
LoadBalancerClient
動態(tài)選擇某個服務的實例(支持 Eureka/Nacos 等注冊中心)。 - 使用 Spring 注入的
Feign.Builder
構建 Feign 客戶端實例,綁定目標實例地址 - 加了簡單的重試邏輯(最多3次),提升服務不穩(wěn)定時的容錯性。
為什么不能直接用 FeignClientFactory
?
FeignClientFactory#getInstance
是靜態(tài)注冊的,依賴啟動時的@FeignClient(name="xxx")
,不能做到動態(tài)服務名運行時創(chuàng)建實例。- 而本類是自己構造目標地址,可通過服務名運行時切換服務。
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 構造器配置,確保動態(tài)創(chuàng)建的 Feign 實例擁有 Spring 的 HTTP 編解碼器、契約協(xié)議、超時、重試等設置。
關鍵配置解讀:
配置項 | 作用說明 |
---|---|
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 | 開啟日志,僅記錄錯誤請求的基本信息(節(jié)省性能) |
@Scope("prototype") | 每次注入都創(chuàng)建一個新的 Feign.Builder(防止多實例干擾) |
為什么不能直接用 Feign.builder()
?
如果你直接用 Feign.builder()
:
- 不具備 Spring 編解碼器能力;
- 沒有 Spring 的日志、重試、超時等配置支持;
- 無法識別
@RequestMapping
等注解; - 無法使用負載均衡(因為沒注入 LoadBalancerClient);
你必須用 Spring 注入的 Feign.Builder
,并設置好契約與編解碼器,才能讓它具備 @FeignClient
的能力。
總結
配置類 | 作用 | 是否必須 |
---|---|---|
DynamicFeignClientFactory | 實現(xiàn)動態(tài)服務名綁定并構建 Feign 客戶端 | 是 |
FeignBuilderConfig | 注入支持 Spring 編解碼、契約協(xié)議、重試、超時等功能的構造器 | 是 |
這兩個配置類結合起來,實現(xiàn)了 “動態(tài)服務發(fā)現(xiàn) + 動態(tài)客戶端構建 + Spring 完整能力支持” ,是 Spring Cloud Feign 動態(tài)服務名調用的標準做法之一。
以上就是Spring Cloud OpenFeign實現(xiàn)動態(tài)服務名調用的示例代碼的詳細內容,更多關于Spring Cloud OpenFeign服務名調用的資料請關注腳本之家其它相關文章!
相關文章
SpringMVC Mock測試實現(xiàn)原理及實現(xiàn)過程詳解
這篇文章主要介紹了SpringMVC Mock測試實現(xiàn)原理及實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-10-10基于注解的springboot+mybatis的多數(shù)據(jù)源組件的實現(xiàn)代碼
這篇文章主要介紹了基于注解的springboot+mybatis的多數(shù)據(jù)源組件的實現(xiàn),會使用到多個數(shù)據(jù)源,文中通過代碼講解的非常詳細,需要的朋友可以參考下2021-04-04