feign 是目前微服務間通信的主流方式,是springCloud中一個非常重要的元件。他涉及到了負載均衡、限流等元件。真正意義上掌握了feign可以說就掌握了微服務。
feign 的使用和dubbo的使用本質上非常相似。dubbo的理念是:像調用本地方法一樣調用遠端方法。那麼套在feign上同樣适用:像調用本地接口一樣調用遠端接口。
使用feign隻需要2步:定義一個接口并用FeignClient注解說明接口所在服務和路徑,服務啟動類上添加@EnableFeignClients。如下所示
首先按照一般的思路,我們會猜測基于接口生成代理類,然後對接口的調用實際上調的是代理對象,那真的是這樣麼?我們帶着猜想往下看。
可以看到注解本身主要定義了要掃描的feign接口包路徑以及配置,但是注解本身又有注解Import ,可以看到他引入了FeignClientsRegistrar到容器。從名字看這個類就應該是在将feign接口注冊到容器中,接下來我們具體看一下這個類幹了些什麼。
可以看到FeignClientsRegistrar實作了ImportBeanDefinitionRegistrar接口,但凡是實作了這個接口的類被注入到容器後,spring容器在啟用過程中都會去調用它的void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2)方法,可以确定的是FeignClientsRegistrar肯定重寫了此方法,我們接下來看一下該方法的實作。
可以看到在這個方法中做了兩件事:1)注冊feign配置, 2)注冊feign接口。我們這裡抓一下重點,看一下feign接口是怎麼注冊的?
上面這段代碼概括起來就是:先找了包路徑basePackages , 然後在從這些包路徑中查找帶有FeignClient注解的接口,最後将注解的資訊解析出來作為屬性手動建構beanDefine注入到容器中。(這裡有一個類ClassPathScanningCandidateComponentProvider,它可以根據filter掃描指定包下面的class對象,十分好用,建議收藏)。包路徑的擷取以及掃描feign相對簡單,這裡不做闡述,我們看一下它生成bean的過程,關注上面代碼中的registerFeignClient方法。
代碼中通過
BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class)生成的BeanDefine(請記住這裡設定得FeignClientFactoryBean.type就是feign接口對應得class對象)。那麼所有的feign接口最終注冊到容器中的都是FeignClientFactoryBean對應的一個執行個體(注意實際上注冊到容器中壓根就不是FeignClientFactoryBean對應的執行個體化對象,具體原因看下文),到此feign接口對應的執行個體注冊過程已經完成。那麼回到一開始的問題為什麼我們調用接口的方法最終發起了請求?是否有代理類的生成呢?我們接下來看看FeignClientFactoryBean類的特殊之處
由上文知,每一個feign接口實際上最終都會生成FeignClientFactoryBean ,最終由FeignClientFactoryBean生成具體的bean執行個體注冊到容器中。
可以看到該類實作了FactoryBean接口,這意味着當Spring注冊該bean執行個體到容器中時,實際是調用其getObject方法,那麼FeignClientFactoryBean一定是重寫了getObject()方法,接下來我們看一下getObject()幹了什麼事情:
我們繼續追蹤getTarget()方法:
顯然最終的bean是通過target.target()方法生成,我們繼續往下看:
顯然最終的bean是通過feign.target(target)生成。我們繼續往下看:
顯然最終得bean是通過build().newInstance(target)生成。我們繼續往下看:
可以看到Proxy.newProxyInstance這個熟悉得身影了,沒錯他就是基于JDK原生得動态代理生成了FeignClientFactoryBean.type屬性對應得class對應得代理類。從前文我們知道FeignClientFactoryBean.type就是feign接口得class對象。是以最終我們調用feign接口得方法實際上調用得是InvocationHandler方法。
總結起來,就是常常我們挂在口頭的東西就是将feign接口生成代理類,然後調用代理接口方法其實調用的代理類得方法,具體是為什麼?不知道大家是否清楚。希望通過本文的閱讀能讓大家閱讀源碼的能力得到提升,也不在對feign有一種黑盒子的感覺。可能篇幅看起來較少,其實feign的注冊過程牽涉到架構層面的知識還是蠻多的,包括springIoc、BeanDefine、動态代理等等,仔細看明白的話收獲應該還是有蠻多的。哈哈,懂得都懂。順手提一句:讀源碼一定要對SPI等等特别熟悉,要不然你會無從下手,沒有方向,抓不到重點。後續會更新文章講feign怎麼實作負載均衡、熔斷等。