Spring Cloud OpenFeign實(shí)現(xiàn)動(dòng)態(tài)服務(wù)名調(diào)用的示例代碼
場(chǎng)景背景
在微服務(wù)架構(gòu)中,我們經(jīng)常需要根據(jù)動(dòng)態(tài)傳入的服務(wù)名來(lái)遠(yuǎn)程調(diào)用其他服務(wù)。例如,你的業(yè)務(wù)中可能有多個(gè)子服務(wù):service-1、service-2……需要?jiǎng)討B(tài)決定調(diào)用哪個(gè)。
通常我們使用如下方式注入 Feign 客戶端:
@FeignClient(name = "service")
public interface FeignClient {
@PostMapping("/api/push")
void pushMessage(@RequestBody PushMessageRequest request);
}
但這種寫(xiě)法服務(wù)名是靜態(tài)寫(xiě)死的,不能根據(jù)運(yùn)行時(shí)的參數(shù)進(jìn)行動(dòng)態(tài)選擇。
錯(cuò)誤用法:FeignClientFactory
很多開(kāi)發(fā)者會(huì)嘗試用 Spring 內(nèi)部的 FeignClientFactory:
@Resource private FeignClientFactory feignClientFactory; ? FeignClient FeignClient = feignClientFactory.getInstance(serviceName, FeignClient.class);
這種方式只能獲取 @FeignClient(name="xxx") 注冊(cè)的靜態(tài)實(shí)例,而不能真正實(shí)現(xiàn)動(dòng)態(tài)服務(wù)調(diào)用。
- 適用場(chǎng)景:獲取已經(jīng)
@FeignClient聲明過(guò)的 bean。 - 不適用:動(dòng)態(tài)服務(wù)名(如從數(shù)據(jù)庫(kù)或配置中傳入)+ 動(dòng)態(tài)構(gòu)建 Feign 實(shí)例。
正確方式:自定義動(dòng)態(tài) Feign 客戶端工廠
要想實(shí)現(xiàn)真正的動(dòng)態(tài)服務(wù)名 + 負(fù)載均衡 + 支持配置和攔截器的 Feign 客戶端,我們需要手動(dòng)構(gòu)造并注入 Feign 客戶端。
核心思路:
- 使用 Spring Cloud 提供的
Feign.Builder(必須是 Spring 注入的) - 配合
LoadBalancerClient實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)與負(fù)載均衡 - 手動(dòng)構(gòu)建 Feign 接口實(shí)例
一、配置 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);
}
}
二、自定義動(dòng)態(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("未找到可用的服務(wù)實(shí)例:" + serviceName);
}
?
String url = instance.getUri().toString();
log.info("選擇的 Feign 客戶端目標(biāo)地址為:{}", url);
return feignBuilder.target(clazz, url);
?
} catch (Exception e) {
lastException = e;
log.warn("第 {} 次嘗試獲取 Feign 客戶端失敗,服務(wù)名:{},錯(cuò)誤信息:{}", retryCount + 1, serviceName, e.getMessage());
retryCount++;
try {
Thread.sleep(500L);
} catch (InterruptedException ignored) {}
}
}
?
throw new RuntimeException("創(chuàng)建 Feign 客戶端失敗,服務(wù)名:" + serviceName, lastException);
}
}
三、使用方式
原始寫(xiě)法(錯(cuò)誤):
@Resource private FeignClientFactory feignClientFactory; ? FeignClient FeignClient = feignClientFactory.getInstance(serviceName, FeignClient.class);
正確寫(xiě)法:
@Resource private DynamicFeignClientFactory feignClientFactory; FeignClient FeignClient = feignClientFactory.getClient(ServerName, FeignClient.class); FeignClient.pushMessage(new PushMessageRequest(Ids, senderEventMessage));
補(bǔ)充說(shuō)明
- Spring 注入的
Feign.Builder會(huì)自動(dòng)繼承全局配置(超時(shí)、日志、攔截器等)。 - 支持服務(wù)名動(dòng)態(tài)路由,自動(dòng)走 Spring Cloud LoadBalancer。
- 每次調(diào)用可綁定到不同的服務(wù)實(shí)例(支持輪詢/自定義負(fù)載策略)。
- 避免直接
new Feign.Builder(),否則會(huì)失去 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) {
...
}
}
功能說(shuō)明:
這是 動(dòng)態(tài)創(chuàng)建 Feign 客戶端 的核心工廠類,解決了 Spring Cloud @FeignClient 無(wú)法支持運(yùn)行時(shí)動(dòng)態(tài)服務(wù)名的問(wèn)題。
核心邏輯:
- 使用 Spring 提供的
LoadBalancerClient動(dòng)態(tài)選擇某個(gè)服務(wù)的實(shí)例(支持 Eureka/Nacos 等注冊(cè)中心)。 - 使用 Spring 注入的
Feign.Builder構(gòu)建 Feign 客戶端實(shí)例,綁定目標(biāo)實(shí)例地址 - 加了簡(jiǎn)單的重試邏輯(最多3次),提升服務(wù)不穩(wěn)定時(shí)的容錯(cuò)性。
為什么不能直接用 FeignClientFactory?
FeignClientFactory#getInstance是靜態(tài)注冊(cè)的,依賴啟動(dòng)時(shí)的@FeignClient(name="xxx"),不能做到動(dòng)態(tài)服務(wù)名運(yùn)行時(shí)創(chuàng)建實(shí)例。- 而本類是自己構(gòu)造目標(biāo)地址,可通過(guò)服務(wù)名運(yùn)行時(shí)切換服務(wù)。
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);
}
}
功能說(shuō)明:
這是自定義的 Feign 構(gòu)造器配置,確保動(dòng)態(tài)創(chuàng)建的 Feign 實(shí)例擁有 Spring 的 HTTP 編解碼器、契約協(xié)議、超時(shí)、重試等設(shè)置。
關(guān)鍵配置解讀:
| 配置項(xiàng) | 作用說(shuō)明 |
|---|---|
| SpringMvcContract | 讓 Feign 支持 @RequestMapping、@GetMapping 等 Spring MVC 風(fēng)格注解 |
| SpringEncoder/Decoder | 使用 Spring Boot 的 HttpMessageConverter 做 JSON 編解碼(默認(rèn)支持 Jackson、Gson 等) |
| Retryer.Default(...) | 設(shè)置重試機(jī)制:初始延遲100ms,最大延遲1s,最多重試3次 |
| Request.Options(...) | 設(shè)置連接超時(shí)為3秒,請(qǐng)求響應(yīng)超時(shí)為5秒 |
| Logger.ErrorLogger + BASIC | 開(kāi)啟日志,僅記錄錯(cuò)誤請(qǐng)求的基本信息(節(jié)省性能) |
| @Scope("prototype") | 每次注入都創(chuàng)建一個(gè)新的 Feign.Builder(防止多實(shí)例干擾) |
為什么不能直接用 Feign.builder()?
如果你直接用 Feign.builder():
- 不具備 Spring 編解碼器能力;
- 沒(méi)有 Spring 的日志、重試、超時(shí)等配置支持;
- 無(wú)法識(shí)別
@RequestMapping等注解; - 無(wú)法使用負(fù)載均衡(因?yàn)闆](méi)注入 LoadBalancerClient);
你必須用 Spring 注入的 Feign.Builder,并設(shè)置好契約與編解碼器,才能讓它具備 @FeignClient 的能力。
總結(jié)
| 配置類 | 作用 | 是否必須 |
|---|---|---|
| DynamicFeignClientFactory | 實(shí)現(xiàn)動(dòng)態(tài)服務(wù)名綁定并構(gòu)建 Feign 客戶端 | 是 |
| FeignBuilderConfig | 注入支持 Spring 編解碼、契約協(xié)議、重試、超時(shí)等功能的構(gòu)造器 | 是 |
這兩個(gè)配置類結(jié)合起來(lái),實(shí)現(xiàn)了 “動(dòng)態(tài)服務(wù)發(fā)現(xiàn) + 動(dòng)態(tài)客戶端構(gòu)建 + Spring 完整能力支持” ,是 Spring Cloud Feign 動(dòng)態(tài)服務(wù)名調(diào)用的標(biāo)準(zhǔn)做法之一。
以上就是Spring Cloud OpenFeign實(shí)現(xiàn)動(dòng)態(tài)服務(wù)名調(diào)用的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Spring Cloud OpenFeign服務(wù)名調(diào)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JAVA實(shí)現(xiàn)二維碼生成加背景圖代碼實(shí)例
這篇文章主要介紹了JAVA實(shí)現(xiàn)二維碼生成加背景圖代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
SpringMVC Mock測(cè)試實(shí)現(xiàn)原理及實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了SpringMVC Mock測(cè)試實(shí)現(xiàn)原理及實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
基于注解的springboot+mybatis的多數(shù)據(jù)源組件的實(shí)現(xiàn)代碼
這篇文章主要介紹了基于注解的springboot+mybatis的多數(shù)據(jù)源組件的實(shí)現(xiàn),會(huì)使用到多個(gè)數(shù)據(jù)源,文中通過(guò)代碼講解的非常詳細(xì),需要的朋友可以參考下2021-04-04
Java測(cè)試題 實(shí)現(xiàn)一個(gè)注冊(cè)功能過(guò)程解析
這篇文章主要介紹了Java測(cè)試題 實(shí)現(xiàn)一個(gè)注冊(cè)功能過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10

