
1、前言
Feign的功能和使用方式 可以看這篇 :
可以看到 @FeignClient 是作用在接口上的, 并沒(méi)有實(shí)現(xiàn)類。這一點(diǎn)和mybatis的Mapper接口一樣,肯定是利用動(dòng)態(tài)代理 來(lái) 實(shí)現(xiàn)的。
還有Feign是集成了Ribbon和Hystrix組件的。
接下來(lái)我們到Feign源碼里一探究竟。
2、源碼入口?
除了導(dǎo)入依賴之外。一般我們會(huì)用 @EnableFeignClients 來(lái)啟動(dòng)Feign功能。
@SpringBootApplication
@EnableFeignClients(clients = OrderService.class)
public class MicroUserApplication {
public static void main(String[] args) {
SpringApplication.run(MicroUserApplication.class);
}
}
@EnableFeignClients注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 源碼入口
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
// 掃描的包下的含有@FeignClient的類,創(chuàng)建Feign客戶端
String[] basePackages() default {};
// 掃描類所處在的包下的含有@FeignClient的類,創(chuàng)建Feign客戶端
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
// 開啟Feign客戶端的接口類
Class<?>[] clients() default {};
}
里面有一些配置開啟Feign客戶端的接口類,以及要掃描的包的屬性。
最重要的是會(huì)用@Import 注解導(dǎo)入 FeignClientsRegistrar類。

FeignClientsRegistrar類,會(huì)實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口,那么就必定會(huì)實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry)方法。在Spring容器把FeignClientsRegistrar導(dǎo)入,實(shí)例化,之后調(diào)用。
AnnotationMetadata就是導(dǎo)入他的類的元數(shù)據(jù)對(duì)象。主要是用來(lái)獲取@EnableFeignClients注解里的具體配置。
然后在 registerBeanDefinitions方法中根據(jù)配置,注冊(cè)Feign客戶端。
3、Feign客戶端的注冊(cè)
看下FeignClientsRegistrar的registerBeanDefinitions方法邏輯。
會(huì)調(diào)用registerFeignClients方法,傳入的AnnotationMetadata對(duì)象就是類上有 @EnableFeignClients的啟動(dòng)類MicroUserApplication。可以看到里面是有注解對(duì)象的。

3.1、根據(jù)@EnableFeignClients注解的配置,確定類掃描器要掃的包路徑 以及過(guò)濾條件
進(jìn)入registerFeignClients。
創(chuàng)建一個(gè)掃描器。
并且從元數(shù)據(jù)獲取@EnableFeignClients注解的配置信息。

還會(huì)創(chuàng)建一個(gè)掃描器在掃描包的類的時(shí)候 過(guò)濾注解類型的過(guò)濾器。
指定有@FeignClient 注解的類才是有效的。

接下來(lái)獲取@EnableFeignClients注解里的clients屬性。
如果沒(méi)有配置,那就獲取basePackage屬性,掃描器用的過(guò)濾器 就是 過(guò)濾@FeignClient注解的過(guò)濾器。 也就是 掃描 basePackage下含有 @FeignClient注解的類

否則,就會(huì) 根據(jù)@EnableFeignClients注解里的clients屬性配置的類,去獲取配置的類所在的包, 新建的過(guò)濾器 只過(guò)濾 clients屬性配置的這些類。
也就是 只掃描 出clients屬性配置的這些類。
在spring源碼里@ComponentScan的原理里也是 這樣, 同樣有掃包用的掃描器,掃描器里有過(guò)濾器,用來(lái)掃指定包下含有@Component注解的類。

3.2、掃描包,過(guò)濾之后返回BeanDefinition對(duì)象集合
從@EnableFeignClients注解的配置信息,確定好要掃的包路徑以及過(guò)濾條件后。開始用掃描器掃包,掃描出來(lái)符合條件的類,返回BeanDefinition對(duì)象集合,并遍歷,調(diào)用registerFeignClient方法,傳入類上FeignClient注解的屬性信息。

3.3、注冊(cè)FeignClientFactoryBean的 BeanDefinition對(duì)象,并設(shè)置屬性值。
registerFeignClient方法會(huì) 創(chuàng)建一個(gè)FeignClientFactoryBean的BeanDefinition對(duì)象,并把類上FeignClient注解的屬性信息加到BeanDefinition的propertyValues容器。實(shí)例化FeignClientFactoryBean時(shí),會(huì)根據(jù) propertyValues容器對(duì)相應(yīng)的屬性進(jìn)行初始化。
最終 注冊(cè)FeignClientFactoryBean的BeanDefinition對(duì)象到Spring容器中。等待后續(xù)實(shí)例化

3.4、Feign客戶端對(duì)象的獲取
需要注意的是 FeignClientFactoryBean會(huì)實(shí)現(xiàn)FactoryBean接口,和mybatis注冊(cè)mapper的代理實(shí)例到spring容器里的原理一樣,會(huì)在實(shí)現(xiàn)的getObject()方法中返回真正的代理對(duì)象,最終注冊(cè)到spring容器中的也是 getObject()方法返回的對(duì)象。
點(diǎn)進(jìn)FeignClientFactoryBean的getObject(),
首先會(huì)先從Spring容器中獲取Feign上下文FeignContext對(duì)象。
如果 @Feign沒(méi)有指定 url的話,那么就是 根據(jù)服務(wù)名稱來(lái)調(diào)用,走這。調(diào)用loadBalance方法。

loadBalance方法里 會(huì)先從 Feign上下文對(duì)象里獲取服務(wù)名稱對(duì)應(yīng)的 Feign客戶端對(duì)象,


getInstance方法會(huì)根據(jù)服務(wù)名 ,獲取內(nèi)部緩存的Spring上下文對(duì)象 ,如果沒(méi)有,就新建Spring上下文對(duì)象,并緩存起來(lái)。這點(diǎn)和ribbon里是一樣的, 每個(gè)服務(wù)名稱都會(huì) 有 自己對(duì)應(yīng)的 Spring上下文對(duì)象, 是相互隔離的。這里就 不再贅述了。

最后獲取到Spring上下文對(duì)象,從中 獲取 Feign客戶端對(duì)象。

3.5、接口代理對(duì)象的生成
獲取到Feign客戶端對(duì)象對(duì)象之后,塞入構(gòu)造器中,最后 調(diào)用Targeter.target方法

3.5.1、FallbackFactory降級(jí)工廠類實(shí)例
進(jìn)入HystrixTargeter.target方法。
判斷@FeignClient注解 配置降級(jí)的方式 是 Fallback屬性還是FallbackFactory屬性。優(yōu)先Fallback屬性。
我們的實(shí)例代碼中配置的是FallbackFactory,調(diào)用targetWithFallbackFactory方法。

一開始會(huì)調(diào)用我們自己配置的FallbackFactory類的,create方法,返回Fallback對(duì)象。

進(jìn)入我們寫的FallbackFactory實(shí)現(xiàn)類,返回的是@FeignClient注解 的接口匿名實(shí)現(xiàn)類。里面重寫的方法 就是 對(duì)應(yīng) 遠(yuǎn)程調(diào)用方法的 降級(jí)方法。

返回出Fallback對(duì)象之后,進(jìn)行判空,判斷是否是 @FeignClient注解的接口 的實(shí)現(xiàn)類。
(這一步其實(shí)就是Feign組件在Spring容器啟動(dòng)時(shí) 對(duì) 我們配置的降級(jí)類 進(jìn)行校驗(yàn)。)
然后調(diào)用構(gòu)造器builder.target

設(shè)置FallbackFactory到構(gòu)造器中,返回調(diào)用構(gòu)造器的 build方法 構(gòu)建 ReflectiveFeign對(duì)象。

3.5.2、ReflectiveFeign對(duì)象 & InvocationHandlerFactory的匿名對(duì)象的創(chuàng)建
這里會(huì)創(chuàng)建一個(gè)InvocationHandlerFactory接口的匿名實(shí)現(xiàn)類對(duì)象,InvocationHandlerFactory是創(chuàng)建HystrixInvocationHandler的工廠。 create方法體會(huì)返回HystrixInvocationHandler對(duì)象。

一系列包裝最終返回ReflectiveFeign對(duì)象

3.5.3、創(chuàng)建代理對(duì)象
然后調(diào)用ReflectiveFeign對(duì)象的 newInstance方法,創(chuàng)建代理對(duì)象。

3.5.3.1、對(duì)象方法和MethodHandler映射的建立
3.5.3.1.1、方法名和MethodHandler映射的建立
一開會(huì)會(huì)調(diào)用targetToHandlersByName.apply方法,遍歷接口里的方法,建立方法名和MethodHandler映射關(guān)系,存在map中。


key是接口名#方法名(參數(shù)類型列表),
value則會(huì)調(diào)用SynchronousMethodHandler.Factory factory 對(duì)象的create方法,創(chuàng)建SynchronousMethodHandler對(duì)象。

最終返回這個(gè)方法名和MethodHandler映射關(guān)系的map。
3.5.3.1.2、方法和MethodHandler映射的建立
拿到方法名和MethodHandler映射關(guān)系的map之后, 然后遍歷接口的所有方法, 拼出 接口名#方法名(參數(shù)類型列表) 這種字符串 作為key, 從 map 獲取 value :SynchronousMethodHandler對(duì)象。
然后 用method對(duì)象作為key, SynchronousMethodHandler對(duì)象作為value, 建立 方法和MethodHandler映射關(guān)系。put到methodToHandler這個(gè)map中

3.5.3.2、創(chuàng)建InvocationHandler對(duì)象
調(diào)用factory.create方法 , 傳入接口,方法和MethodHandler映射關(guān)系map對(duì)象methodToHandler,創(chuàng)建JDK動(dòng)態(tài)代理中的InvocationHandler對(duì)象。

factory是 剛才build方法里設(shè)置進(jìn)去的InvocationHandlerFactory接口的匿名實(shí)現(xiàn)類對(duì)象,實(shí)現(xiàn)的create方法,會(huì)返回HystrixInvocationHandler對(duì)象。這里就會(huì)調(diào)到它,傳入接口target,和方法和MethodHandler映射關(guān)系,還有FallbackFactory。

復(fù)制給 HystrixInvocationHandler里成員變量

注意下,dispatch屬性就是 方法和MethodHandler映射關(guān)系。后面調(diào)用會(huì)用到。
最終new出 HystrixInvocationHandler對(duì)象,返回
3.5.3.3、生成動(dòng)態(tài)代理對(duì)象。
接著把 HystrixInvocationHandler對(duì)象,傳到Proxy.newProxyInstance方法中, 實(shí)現(xiàn)接口為@FeignClient注解的接口, invokeHandle對(duì)象是 HystrixInvocationHandler對(duì)象,

HystrixInvocationHandler實(shí)現(xiàn)InvocationHandler接口,那么 在調(diào)用 代理對(duì)象的方法時(shí),就會(huì)調(diào)用到 HystrixInvocationHandler.invoke()方法進(jìn)行 代理。
feign時(shí)調(diào)用的邏輯的就全在 HystrixInvocationHandler.invoke()方法里了。
最終這個(gè)getObject返回出 實(shí)現(xiàn) 有@FeignClient注解的接口 的 代理對(duì)象,注冊(cè)到Spring容器中。我們從其他地方注入這個(gè)接口的實(shí)例,實(shí)際上 注入的這就這個(gè)代理對(duì)象。
4、調(diào)用時(shí)的代理邏輯
4.1、調(diào)用 hystrix組件,進(jìn)行服務(wù)隔離,降級(jí),熔斷
點(diǎn)到 HystrixInvocationHandler.invoke方法
首先是方法判斷,equals,hashCode,toString方法不 走增強(qiáng),直接調(diào)原生方法

下面是具體的調(diào)用,會(huì)調(diào)用Hystrix的組件 : HystrixCommand,重寫run,getFallback方法
4.1.1、run()
- run方法 :這個(gè)方法在Hystrix發(fā)起遠(yuǎn)程調(diào)用的時(shí)候會(huì)調(diào)到, 在里面執(zhí)行業(yè)務(wù)方法。

會(huì)從HystrixInvocationHandler對(duì)象的dispatch屬性里根據(jù)method對(duì)出SynchronousMethodHandler對(duì)象,調(diào)它的invoke方法。
前面講了HystrixInvocationHandler對(duì)象的dispatch屬性 是個(gè)map,存的是 method對(duì)象和 SynchronousMethodHandler對(duì)象的映射關(guān)系
4.1.2、getFallback()
- getFallback() : 獲取降級(jí)的返回值,這個(gè)是Hystrix 遠(yuǎn)程調(diào)用降級(jí)時(shí)調(diào)用的,會(huì)執(zhí)行我們定義的降級(jí)方法,返回返回值。

調(diào)用fallbackFactory.create方法 ,這個(gè)會(huì)創(chuàng)建到我們自己寫的FallbackFactory實(shí)現(xiàn)類,然后返回 有@FeignClient接口的 實(shí)現(xiàn)類對(duì)象。
后面從映射關(guān)系中獲取返回到method對(duì)象,反射調(diào)用。
下面就會(huì)調(diào)用hystrixCommand的execute方法。

還是走的 hystrix 那一套,最后會(huì)走到上面hystrixCommand對(duì)象的 run() 鉤子方法中,如果需要降級(jí)的話,就走 getFallback()鉤子方法。
關(guān)于hystrix后續(xù)的執(zhí)行流程就不再贅述了,之前的SpringCloud之Hystrix源碼 里有詳細(xì)介紹。
4.2、調(diào)用ribbon組件 負(fù)載均衡
當(dāng) hystrix組件,判斷 是否熔斷, 線程池或者信號(hào)量是否已滿等條件之后,允許接受請(qǐng)求的話,那么就會(huì)執(zhí)行上面執(zhí)行的HystrixCommand的run方法,去發(fā)起遠(yuǎn)程調(diào)用。
遠(yuǎn)程調(diào)用的話肯定是ribbon完成的,在這個(gè)方法中 就會(huì)調(diào)用ribbon組件負(fù)載均衡之后 發(fā)起對(duì)某臺(tái)主機(jī)的 對(duì)應(yīng)的接口請(qǐng)求。

上面講過(guò)HystrixInvocationHandler的dispatch存放的是存的是 method對(duì)象和 SynchronousMethodHandler對(duì)象的映射關(guān)系,這里就會(huì)根據(jù)method對(duì)象get出 SynchronousMethodHandler對(duì)象,調(diào)用他的invoke方法。
點(diǎn)進(jìn)SynchronousMethodHandler的invoke方法。

SynchronousMethodHandler在實(shí)例化的時(shí)候會(huì)傳入Feign客戶端對(duì)象,存到成員變量client中。
這個(gè)工程由于啟用的鏈路追蹤,所以這里的feign類型是TraceLoadBalancerFeignClient。如果沒(méi)有鏈路追蹤,應(yīng)該就是TraceLoadBalancerFeignClient的父類:LoadBalancerFeignClient類型。TraceLoadBalancerFeignClient類相較于 LoadBalancerFeignClient類就是在ribbon調(diào)用的前后 多記錄了 本地調(diào)用的 鏈路信息而已。
調(diào)用feign客戶端的execute方法

在調(diào)用前 執(zhí)行了鏈路追蹤的相關(guān)邏輯后,走到父類LoadBalancerFeignClient的execute方法

4.2.1、獲取FeignLoadBalancer實(shí)例
接下來(lái)獲取調(diào)用lbClient(clientName) 方法獲取LoadBalancer對(duì)象。

調(diào)用CachingSpringLoadBalancerFactory.create方法

有個(gè)緩存,第一次進(jìn)來(lái)肯定緩存里沒(méi)有,會(huì)調(diào)用SpringClientFactory對(duì)象獲取ribbon的配置IClientConfig實(shí)例, ribbon的ILoadBalancer實(shí)例(調(diào)用的ribbon源碼)。最終包裝到 FeignLoadBalancer實(shí)例中。最后返回。

4.2.2、Ribbon負(fù)載均衡
獲取到FeignLoadBalancer實(shí)例之后,調(diào)用executeWithLoadBalancer方法

executeWithLoadBalancer方法里會(huì)創(chuàng)建 LoadBalancerCommand對(duì)象,并submit 一個(gè) ServerOperation匿名實(shí)現(xiàn)類對(duì)象。LoadBalancerCommand對(duì)象會(huì)調(diào)用它的call方法。
ServerOperation匿名實(shí)現(xiàn)類對(duì)象里會(huì)根據(jù) 負(fù)載均衡選擇的 具體服務(wù)進(jìn)行 http請(qǐng)求。

點(diǎn)進(jìn)LoadBalancerCommand的submit則會(huì) 有 服務(wù)的選擇。

最終則是調(diào)到ribbon源碼里的ZoneAwareLoadBalancer對(duì)象的chooseServer方法,從服務(wù)列表里通過(guò)負(fù)載均衡算法 選則 一個(gè)具體的服務(wù)實(shí)例。

選出具體的服務(wù)之后,調(diào)用ServerOperation匿名實(shí)現(xiàn)類對(duì)象的call方法,傳入具體的服務(wù)實(shí)例進(jìn)去,對(duì)其發(fā)起http請(qǐng)求。


最終返回請(qǐng)求結(jié)果。
4.2.3、調(diào)用ribbon總結(jié)
其中獲取ribbon的配置IClientConfig實(shí)例, ribbon的ILoadBalancer實(shí)例,包括后續(xù)用ILoadBalancer實(shí)例進(jìn)行負(fù)載均衡(選擇服務(wù)列表中的某個(gè)服務(wù)),遠(yuǎn)程調(diào)用,都是調(diào)用的ribbon的組件了。這里就不再贅述,之前的SpringCloud之Ribbon源碼 里有詳細(xì)介紹。
5、Feign源碼總結(jié)
經(jīng)過(guò)通篇源碼的介紹,我們可以更清楚的了解到Feign組件在整個(gè)調(diào)用過(guò)程中的職責(zé)定位。
可以看到 Feign組件 最核心的工作還是 對(duì)我們帶有@FeignClient注解的接口,生成動(dòng)態(tài)代理。然后在動(dòng)態(tài)代理對(duì)象的增強(qiáng)邏輯里,調(diào)用 hystrix 組件 進(jìn)行 服務(wù)隔離,降級(jí),熔斷等功能。如果hystrix組件判斷 允許接受本次請(qǐng)求,那么就會(huì)調(diào)用 ribbon 組件進(jìn)行負(fù)載均衡,最終對(duì) ribbon組件選擇出的某個(gè)服務(wù)實(shí)例 發(fā)起http請(qǐng)求。