【一起學原始碼-微服務】Feign 原始碼一:原始碼初探,通過Demo Debug Feign原始碼
前言
前情回顧
上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList
,同時在DynamicServerListLoadBalancer
中會呼叫PollingServerListUpdater
進行定時更新Eureka登錄檔資訊到BaseLoadBalancer
中,預設30s排程一次。
本講目錄
這一講主要是講Fegin Demo以及通過入口註解@EnableFeignCliets和@FeignClient來進行原始碼初探。
目錄如下:
- Feign程式碼Demo
- Feign呼叫原理
- @EnableEurekaClient和@FeignClient註解掃描
說明
原創不易,如若轉載 請標明來源!
部落格地址:一枝花算不算浪漫
微信公眾號:壹枝花算不算浪漫
原始碼分析
Feign程式碼Demo
Fegin的Demo還是延續之前講解的Eureka的程式碼。地址為:
https://github.com/barrywangmeng/spring-cloud-learn
如上圖所示,ServiceB呼叫ServiceA的服務,定義了一個@FeignClient標註的ServiceAFeignClient介面,裡面定義了ServiceA中Controller提供的介面資訊。
Feign呼叫原理
接著我們可以啟動所有服務,呼叫ServiceBController,然後在serviceAFeignClient處打上斷點看一下:
我們發現這裡serviceAFeignClient顯示的是:ReflectiveFeign$FeignInvocationHandler@7642
,這裡其實是使用了動態代理,因為ServiceAFeignClient
是一個介面,所以這裡可以猜測到底層使用的是JDK動態代理。
接著可以簡單地梳理下Feign請求簡單原理圖:
@EnableEurekaClient和@FeignClient註解掃描
先看下@EnableFeignClients註解程式碼:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
//等價於basePackages屬性,更簡潔的方式
String[] value() default {};
//指定多個包名進行掃描
String[] basePackages() default {};
//指定多個類或介面的class,掃描時會在這些指定的類和介面所屬的包進行掃描
Class<?>[] basePackageClasses() default {};
//為所有的Feign Client設定預設配置類
Class<?>[] defaultConfiguration() default {};
//指定用@FeignClient註釋的類列表。如果該項配置不為空,則不會進行類路徑掃描
Class<?>[] clients() default {};
}
接著看下@FeignClient註解程式碼:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
//指定Feign Client的名稱,如果專案使用了 Ribbon,name屬性會作為微服務的名稱,用於服務發現
@AliasFor("name")
String value() default "";
//用serviceId做服務發現已經被廢棄,所以不推薦使用該配置
@Deprecated
String serviceId() default "";
//指定Feign Client的serviceId,如果專案使用了 Ribbon,將使用serviceId用於服務發現,但上面可以看到serviceId做服務發現已經被廢棄,所以也不推薦使用該配置
@AliasFor("value")
String name() default "";
//為Feign Client 新增註解@Qualifier
String qualifier() default "";
//請求地址的絕對URL,或者解析的主機名
String url() default "";
//呼叫該feign client發生了常見的404錯誤時,是否呼叫decoder進行解碼異常資訊返回,否則丟擲FeignException
boolean decode404() default false;
//Feign Client設定預設配置類
Class<?>[] configuration() default {};
//定義容錯的處理類,當呼叫遠端介面失敗或超時時,會呼叫對應介面的容錯邏輯,fallback 指定的類必須實現@FeignClient標記的介面。實現的法方法即對應介面的容錯處理邏輯
Class<?> fallback() default void.class;
//工廠類,用於生成fallback 類示例,通過這個屬性我們可以實現每個介面通用的容錯邏輯,減少重複的程式碼
Class<?> fallbackFactory() default void.class;
//定義當前FeignClient的所有方法對映加統一字首
String path() default "";
//是否將此Feign代理標記為一個Primary Bean,預設為ture
boolean primary() default true;
}
接著我們看下@EnableFeignClients中注入的
@Import(FeignClientsRegistrar.class) 原始碼:
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
public FeignClientsRegistrar() {
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
//在這個過載的方法裡面做了兩件事情:
//1.將EnableFeignClients註解對應的配置屬性注入
//2.將FeignClient註解對應的屬性注入
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注入EnableFeignClients註解對應的配置屬性
registerDefaultConfiguration(metadata, registry);
//注入FeignClient註解對應的屬性
registerFeignClients(metadata, registry);
}
/**
* 拿到 EnableFeignClients註解 defaultConfiguration 欄位的值
* 然後進行注入
*
**/
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 獲取ClassPath掃描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 為掃描器設定資源載入器
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
// 1. 從@EnableFeignClients註解中獲取到配置的各個屬性值
// 這裡可以獲取到配置的:basePackages=com.barrywang.service.feign
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 2. 註解型別過濾器,只過濾@FeignClient
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
// 3. 從1. 中的屬性值中獲取clients屬性的值
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 掃描器設定過濾器且獲取需要掃描的基礎包集合
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}else {
// clients屬性值不為null,則將其clazz路徑轉為包路徑
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
// 3. 掃描基礎包,且滿足過濾條件下的介面封裝成BeanDefinition
for (String basePackage : basePackages) {
// 找到basePackage下定義的@FeignClient介面列表
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
// 遍歷掃描到的bean定義
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 並校驗掃描到的bean定義類是一個介面
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 獲取@FeignClient註解上的各個屬性值
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 可以看到這裡也註冊了一個FeignClient的配置bean
// 這個方法是建立了一個FeignClientFactoryBean的工廠類,裡面儲存@FeignClient註解的所有屬性值
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 註冊bean定義到spring中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
/**
* 註冊bean
**/
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 1.獲取類名稱,也就是本例中的FeignService介面
String className = annotationMetadata.getClassName();
// 2.BeanDefinitionBuilder的主要作用就是構建一個AbstractBeanDefinition
// AbstractBeanDefinition類最終被構建成一個BeanDefinitionHolder
// 然後註冊到Spring中
// 注意:beanDefinition類為FeignClientFactoryBean,故在Spring獲取類的時候實際返回的是
// FeignClientFactoryBean類
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
// 3.新增FeignClientFactoryBean的屬性,
// 這些屬性也都是我們在@FeignClient中定義的屬性
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// 4.設定別名 name就是我們在@FeignClient中定義的name屬性
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
// 5.定義BeanDefinitionHolder
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
private void validate(Map<String, Object> attributes) {
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
// FIXME annotation.getAliasedString("name", FeignClient.class, null);
Assert.isTrue(
!annotation.getClass("fallback").isInterface(),
"Fallback class must implement the interface annotated by @FeignClient"
);
Assert.isTrue(
!annotation.getClass("fallbackFactory").isInterface(),
"Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"
);
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 建立一個使用FeignClientSpecification構建的BeanDefinitionBuilder的類
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
......
......
......
}
在這裡做了兩件事情:
- 將EnableFeignClients註解對應的配置屬性注入;
- 將FeignClient註解對應的屬性注入。
生成FeignClient對應的bean,注入到Spring 的IOC容器。
總結
在我們檢視處理@EnableFeignClients和@FeignClient註解的地方,最後呼叫registerFeignClient()
會構造一個:
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
所以我們後續會重點檢視FeignClientFactoryBean
這個類。
申明
本文章首發自本人部落格:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!
感興趣的小夥伴可關注個人公眾號:壹枝花算不算浪漫
相關推薦
【一起學原始碼-微服務】Feign 原始碼一:原始碼初探,通過Demo Debug Feign原始碼
前言 前情回顧 上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會呼叫PollingServerListUpdater 進
【一起學原始碼-微服務】Feign 原始碼二:Feign動態代理構造過程
前言 前情回顧 上一講主要看了@EnableFeignClients中的registerBeanDefinitions()方法,這裡面主要是 將EnableFeignClients註解對應的配置屬性注入,將FeignClient註解對應的屬性注入。 最後是生成FeignClient對應的bean,注入到Spr
【一起學原始碼-微服務】Feign 原始碼三:Feign結合Ribbon實現負載均衡的原理分析
前言 前情回顧 上一講我們已經知道了Feign的工作原理其實是在專案啟動的時候,通過JDK動態代理為每個FeignClinent生成一個動態代理。 動態代理的資料結構是:ReflectiveFeign.FeignInvocationHandler。其中包含target(裡面是serviceName等資訊)和d
【一起學原始碼-微服務】Nexflix Eureka 原始碼二:EurekaServer啟動之配置檔案載入以及面向介面的配置項讀取
前言 上篇文章已經介紹了 為何要讀netflix eureka原始碼了,這裡就不再概述,下面開始正式原始碼解讀的內容。 如若轉載 請標明來源:一枝花算不算浪漫 程式碼總覽 還記得上文中,我們通過web.xml找到了eureka server入口的類EurekaBootStrap,這裡我們就先來簡單地看下: /
【一起學原始碼-微服務】Nexflix Eureka 原始碼三:EurekaServer啟動之EurekaServer上下文EurekaClient建立
前言 上篇文章已經介紹了 Eureka Server 環境和上下文初始化的一些程式碼,其中重點講解了environment初始化使用的單例模式,以及EurekaServerConfigure基於介面對外暴露配置方法的設計方式。這一講就是講解Eureka Server上下文初始化剩下的內容:Eureka Cli
【一起學原始碼-微服務】Nexflix Eureka 原始碼六:在眼花繚亂的程式碼中,EurekaClient是如何註冊的?
前言 上一講已經講解了EurekaClient的啟動流程,到了這裡已經有6篇Eureka原始碼分析的文章了,看了下之前的文章,感覺程式碼成分太多,會影響閱讀,後面會只擷取主要的程式碼,加上註釋講解。 這一講看的是EurekaClient註冊的流程,當然也是一塊核心,標題為什麼會寫上眼花繚亂呢?關於Eureka
【一起學原始碼-微服務】Nexflix Eureka 原始碼七:通過單元測試來Debug Eureka註冊過程
前言 上一講eureka client是如何註冊的,一直跟到原始碼傳送http請求為止,當時看eureka client註冊時如此費盡,光是找一個regiter的地方就找了半天,那麼client端傳送了http請求給server端,server端是如何處理的呢? 帶著這麼一個疑問 就開始今天原始碼的解讀了。
【一起學原始碼-微服務】Nexflix Eureka 原始碼八:EurekaClient登錄檔抓取 精妙設計分析!
前言 前情回顧 上一講 我們通過單元測試 來梳理了EurekaClient是如何註冊到server端,以及server端接收到請求是如何處理的,這裡最重要的關注點是登錄檔的一個數據結構:ConcurrentHashMap<String, Map<String, Lease<InstanceI
【一起學原始碼-微服務】Nexflix Eureka 原始碼九:服務續約原始碼分析
前言 前情回顧 上一講 我們講解了服務發現的相關邏輯,所謂服務發現 其實就是登錄檔抓取,服務例項預設每隔30s去註冊中心抓取一下注冊表增量資料,然後合併本地登錄檔資料,最後有個hash對比的操作。 本講目錄 今天主要是看下服務續約的邏輯,服務續約就是client端給server端傳送心跳檢測,告訴對方我還活著
【一起學原始碼-微服務】Nexflix Eureka 原始碼十:服務下線及例項摘除,一個client下線到底多久才會被其他例項感知?
前言 前情回顧 上一講我們講了 client端向server端傳送心跳檢查,也是預設每30鍾傳送一次,server端接收後會更新登錄檔的一個時間戳屬性,然後一次心跳(續約)也就完成了。 本講目錄 這一篇有兩個知識點及一個疑問,這個疑問是在工作中真真實實遇到過的。 例如我有服務A、服務B,A、B都註冊在同一個註
【一起學原始碼-微服務】Nexflix Eureka 原始碼十一:EurekaServer自我保護機制竟然有這麼多Bug?
前言 前情回顧 上一講主要講了服務下線,已經註冊中心自動感知宕機的服務。 其實上一講已經包含了很多EurekaServer自我保護的程式碼,其中還發現了1.7.x(1.9.x)包含的一些bug,但這些問題在master分支都已修復了。 服務下線會將服務例項從登錄檔中刪除,然後放入到recentQueue中,下
【一起學原始碼-微服務】Nexflix Eureka 原始碼十二:EurekaServer叢集模式原始碼分析
前言 前情回顧 上一講看了Eureka 註冊中心的自我保護機制,以及裡面提到的bug問題。 哈哈 轉眼間都2020年了,這個系列的文章從12.17 一直寫到現在,也是不容易哈,每天持續不斷學習,輸出部落格,這一段時間確實收穫很多。 今天在公司給組內成員分享了Eureka原始碼剖析,反響效果還可以,也算是感覺收
【一起學原始碼-微服務】Nexflix Eureka 原始碼十三:Eureka原始碼解讀完結撒花篇~!
前言 想說的話 【一起學原始碼-微服務-Netflix Eureka】專欄到這裡就已經全部結束了。 實話實說,從最開始Eureka Server和Eureka Client初始化的流程還是一臉悶逼,到現在Eureka各種操作都瞭然於心了。 本專欄從12.17開始寫,一直到今天12.30(文章在平臺是延後釋出的
【一起學原始碼-微服務】Ribbon 原始碼一:Ribbon概念理解及Demo除錯
前言 前情回顧 前面文章已經梳理清楚了Eureka相關的概念及原始碼,接下來開始研究下Ribbon的實現原理。 我們都知道Ribbon在spring cloud中擔當負載均衡的角色, 當兩個Eureka Client互相呼叫的時候,Ribbon能夠做到呼叫時的負載,保證多節點的客戶端均勻接收請求。(這個有點類
【一起學原始碼-微服務】Ribbon 原始碼二:通過Debug找出Ribbon初始化流程及ILoadBalancer原理分析
前言 前情回顧 上一講講了Ribbon的基礎知識,通過一個簡單的demo看了下Ribbon的負載均衡,我們在RestTemplate上加了@LoadBalanced註解後,就能夠自動的負載均衡了。 本講目錄 這一講主要是繼續深入RibbonLoadBalancerClient和Ribbon+Eureka整合的
【一起學原始碼-微服務】Ribbon 原始碼三:Ribbon與Eureka整合原理分析
前言 前情回顧 上一篇講了Ribbon的初始化過程,從LoadBalancerAutoConfiguration 到RibbonAutoConfiguration 再到RibbonClientConfiguration,我們找到了ILoadBalancer預設初始化的物件等。 本講目錄 這一講我們會進一步往下
【一起學原始碼-微服務】Ribbon 原始碼四:進一步探究Ribbon的IRule和IPing
前言 前情回顧 上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合程式碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會呼叫PollingServerListUpdater 進
【一起學原始碼-微服務】Ribbon原始碼五:Ribbon原始碼解讀彙總篇~
前言 想說的話 【一起學原始碼-微服務-Ribbon】專欄到這裡就已經全部結束了,共更新四篇文章。 Ribbon比較小巧,這裡是直接 讀的spring cloud 內嵌封裝的版本,裡面的各種configuration確實有點繞,不過看看第三講Ribbon初始化的過程總結圖就會清晰很多。 緊接著會繼續整理學習F
【一起學原始碼-微服務】Hystrix 原始碼一:Hystrix基礎原理與Demo搭建
說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一個系列文章講解了Feign的原始碼,主要是Feign動態代理實現的原理,及配合Ribbon實現負載均衡的機制。 這裡我們講解一個新的元件Hystrix,也是和Fe
【一起學原始碼-微服務】Hystrix 原始碼二:Hystrix核心流程:Hystix非降級邏輯流程梳理
說明 原創不易,如若轉載 請標明來源! 歡迎關注本人微信公眾號:壹枝花算不算浪漫 更多內容也可檢視本人部落格:一枝花算不算浪漫 前言 前情回顧 上一講我們講了配置了feign.hystrix.enabled=true之後,預設的Targeter就會構建成HystrixTargter, 然後通過對應的Hystr