微服務(wù)通信之feign的注冊、發(fā)現(xiàn)過程
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??泥粑
來源 |? urlify.cn/amyIf2
前言
feign 是目前微服務(wù)間通信的主流方式,是springCloud中一個非常重要的組件。他涉及到了負載均衡、限流等組件。真正意義上掌握了feign可以說就掌握了微服務(wù)。
一、feign的使用
feign 的使用和dubbo的使用本質(zhì)上非常相似。dubbo的理念是:像調(diào)用本地方法一樣調(diào)用遠程方法。那么套在feign上同樣適用:像調(diào)用本地接口一樣調(diào)用遠程接口。
使用feign只需要2步:定義一個接口并用FeignClient注解說明接口所在服務(wù)和路徑,服務(wù)啟動類上添加@EnableFeignClients。如下所示
1.1,定義一個feign接口
@FeignClient(contextId?=?"order",?name?=?"order",?path?=?"/app")
public?interface?OrderApiFeignClient?{
???/**
????*?獲取訂單列表
????*?@return
????*/
???@RequestMapping("order/list")
???BaseResponse>?obtaining(@PathVariable("userId")?Long?userId);
}
1.2,再啟動類上添加注解
@EnableSwagger2
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.xxx.*")
@ComponentScan(value={"com.xxx"})
public?class?OrderApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(OrderApplication?.class,?args);
????}
}
二、feign 接口如何被實例化到spring容器的?
首先按照一般的思路,我們會猜測基于接口生成代理類,然后對接口的調(diào)用實際上調(diào)的是代理對象,那真的是這樣么?我們帶著猜想往下看。
2.1 @EnableFeignClients 注解都做了些什么?

可以看到注解本身主要定義了要掃描的feign接口包路徑以及配置,但是注解本身又有注解Import ,可以看到他引入了FeignClientsRegistrar到容器。從名字看這個類就應(yīng)該是在將feign接口注冊到容器中,接下來我們具體看一下這個類干了些什么。
/**
?*?@author?Spencer?Gibb
?*?@author?Jakub?Narloch
?*?@author?Venil?Noronha
?*?@author?Gang?Li
?*/
class?FeignClientsRegistrar
??implements?ImportBeanDefinitionRegistrar,?ResourceLoaderAware,?EnvironmentAware?{
可以看到FeignClientsRegistrar實現(xiàn)了ImportBeanDefinitionRegistrar接口,但凡是實現(xiàn)了這個接口的類被注入到容器后,spring容器在啟用過程中都會去調(diào)用它的void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2)方法,可以確定的是FeignClientsRegistrar肯定重寫了此方法,我們接下來看一下該方法的實現(xiàn)。

可以看到在這個方法中做了兩件事:1)注冊feign配置, 2)注冊feign接口。我們這里抓一下重點,看一下feign接口是怎么注冊的?
public?void?registerFeignClients(AnnotationMetadata?metadata,
???BeanDefinitionRegistry?registry)?{
??ClassPathScanningCandidateComponentProvider?scanner?=?getScanner();
??scanner.setResourceLoader(this.resourceLoader);
??Set?basePackages;
??Map?attrs?=?metadata
????.getAnnotationAttributes(EnableFeignClients.class.getName());
??AnnotationTypeFilter?annotationTypeFilter?=?new?AnnotationTypeFilter(
????FeignClient.class);
??final?Class>[]?clients?=?attrs?==?null???null
????:?(Class>[])?attrs.get("clients");
??if?(clients?==?null?||?clients.length?==?0)?{
????????????????????????//?限定只掃描FeingClient注解
???scanner.addIncludeFilter(annotationTypeFilter);
???basePackages?=?getBasePackages(metadata);
??}
??else?{
???final?Set?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)));
??}
??for?(String?basePackage?:?basePackages)?{
???Set?candidateComponents?=?scanner
?????.findCandidateComponents(basePackage);
???for?(BeanDefinition?candidateComponent?:?candidateComponents)?{
????if?(candidateComponent?instanceof?AnnotatedBeanDefinition)?{
?????//?verify?annotated?class?is?an?interface
?????AnnotatedBeanDefinition?beanDefinition?=?(AnnotatedBeanDefinition)?candidateComponent;
?????AnnotationMetadata?annotationMetadata?=?beanDefinition.getMetadata();
?????Assert.isTrue(annotationMetadata.isInterface(),
???????"@FeignClient?can?only?be?specified?on?an?interface");
?????Map?attributes?=?annotationMetadata
???????.getAnnotationAttributes(
?????????FeignClient.class.getCanonicalName());
?????String?name?=?getClientName(attributes);
?????registerClientConfiguration(registry,?name,
???????attributes.get("configuration"));
????????????????????????????????????????//?這里生成bean并且注冊到容器
?????registerFeignClient(registry,?annotationMetadata,?attributes);
????}
???}
??}
?}
上面這段代碼概括起來就是:先找了包路徑basePackages , 然后在從這些包路徑中查找?guī)в蠪eignClient注解的接口,最后將注解的信息解析出來作為屬性手動構(gòu)建beanDefine注入到容器中。(這里有一個類ClassPathScanningCandidateComponentProvider,它可以根據(jù)filter掃描指定包下面的class對象,十分好用,建議收藏)。包路徑的獲取以及掃描feign相對簡單,這里不做闡述,我們看一下它生成bean的過程,關(guān)注上面代碼中的registerFeignClient方法。
private?void?registerFeignClient(BeanDefinitionRegistry?registry,
???AnnotationMetadata?annotationMetadata,?Map?attributes)?{
??String?className?=?annotationMetadata.getClassName();
??BeanDefinitionBuilder?definition?=?BeanDefinitionBuilder
????.genericBeanDefinition(FeignClientFactoryBean.class);
??validate(attributes);
??definition.addPropertyValue("url",?getUrl(attributes));
??definition.addPropertyValue("path",?getPath(attributes));
??String?name?=?getName(attributes);
??definition.addPropertyValue("name",?name);
??String?contextId?=?getContextId(attributes);
??definition.addPropertyValue("contextId",?contextId);
??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);
????????????????//?這里省略部分代碼
??BeanDefinitionReaderUtils.registerBeanDefinition(holder,?registry);
?}
代碼中通過BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class)生成的BeanDefine(請記住這里設(shè)置得FeignClientFactoryBean.type就是feign接口對應(yīng)得class對象)。那么所有的feign接口最終注冊到容器中的都是FeignClientFactoryBean對應(yīng)的一個實例(注意實際上注冊到容器中壓根就不是FeignClientFactoryBean對應(yīng)的實例化對象,具體原因看下文),到此feign接口對應(yīng)的實例注冊過程已經(jīng)完成。那么回到一開始的問題為什么我們調(diào)用接口的方法最終發(fā)起了請求?是否有代理類的生成呢?我們接下來看看FeignClientFactoryBean類的特殊之處
2.2 FeignClientFactoryBean 類,feign接口代理生成類
由上文知,每一個feign接口實際上最終都會生成FeignClientFactoryBean ,最終由FeignClientFactoryBean生成具體的bean實例注冊到容器中。
/**
?*?@author?Spencer?Gibb
?*?@author?Venil?Noronha
?*?@author?Eko?Kurniawan?Khannedy
?*?@author?Gregor?Zurowski
?*/
class?FeignClientFactoryBean
??implements?FactoryBean可以看到該類實現(xiàn)了FactoryBean接口,這意味著當Spring注冊該bean實例到容器中時,實際是調(diào)用其getObject方法,那么FeignClientFactoryBean一定是重寫了getObject()方法,接下來我們看一下getObject()干了什么事情:
?public?Object?getObject()?throws?Exception?{
??return?getTarget();
?}
我們繼續(xù)追蹤getTarget()方法:
??T?getTarget()?{
??FeignContext?context?=?this.applicationContext.getBean(FeignContext.class);
??Feign.Builder?builder?=?feign(context);
??
????????????????//?省略部分代碼...
???
??Targeter?targeter?=?get(context,?Targeter.class);
??return?(T)?targeter.target(this,?builder,?context,
????new?HardCodedTarget<>(this.type,?this.name,?url));
?}
顯然最終的bean是通過target.target()方法生成,我們繼續(xù)往下看:
?@Override
?public??T?target(FeignClientFactoryBean?factory,?Feign.Builder?feign,
???FeignContext?context,?Target.HardCodedTarget?target)?{
??return?feign.target(target);
?}
顯然最終的bean是通過feign.target(target)生成。我們繼續(xù)往下看:
????public??T?target(Target?target)?{
??????return?build().newInstance(target);
????}
顯然最終得bean是通過build().newInstance(target)生成。我們繼續(xù)往下看:
??public??T?newInstance(Target?target)?{
????//?省略部分代碼
????InvocationHandler?handler?=?factory.create(target,?methodToHandler);
????T?proxy?=?(T)?Proxy.newProxyInstance(target.type().getClassLoader(),
????????new?Class>[]?{target.type()},?handler);
????for?(DefaultMethodHandler?defaultMethodHandler?:?defaultMethodHandlers)?{
??????defaultMethodHandler.bindTo(proxy);
????}
????return?proxy;
??}
可以看到Proxy.newProxyInstance這個熟悉得身影了,沒錯他就是基于JDK原生得動態(tài)代理生成了FeignClientFactoryBean.type屬性對應(yīng)得class對應(yīng)得代理類。從前文我們知道FeignClientFactoryBean.type就是feign接口得class對象。所以最終我們調(diào)用feign接口得方法實際上調(diào)用得是InvocationHandler方法。
三、小結(jié)
總結(jié)起來,就是常常我們掛在口頭的東西就是將feign接口生成代理類,然后調(diào)用代理接口方法其實調(diào)用的代理類得方法,具體是為什么?不知道大家是否清楚。希望通過本文的閱讀能讓大家閱讀源碼的能力得到提升,也不在對feign有一種黑盒子的感覺??赡芷雌饋磔^少,其實feign的注冊過程牽涉到框架層面的知識還是蠻多的,包括springIoc、BeanDefine、動態(tài)代理等等,仔細看明白的話收獲應(yīng)該還是有蠻多的。哈哈,懂得都懂。順手提一句:讀源碼一定要對SPI等等特別熟悉,要不然你會無從下手,沒有方向,抓不到重點。后續(xù)會更新文章講feign怎么實現(xiàn)負載均衡、熔斷等。
粉絲福利:108本java從入門到大神精選電子書領(lǐng)取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進入java1234官方微信群
感謝點贊支持下哈?
