<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          微服務(wù)通信之feign的注冊、發(fā)現(xiàn)過程

          共 7903字,需瀏覽 16分鐘

           ·

          2020-10-05 12:36

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

          ? 作者?|??泥粑

          來源 |? urlify.cn/amyIf2

          66套java從入門到精通實戰(zhàn)課程分享

          前言

          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,?InitializingBean,?ApplicationContextAware?

          可以看到該類實現(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官方微信群



          感謝點贊支持下哈?


          瀏覽 36
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    久久夜色免费视频 | 黄色免费网站在线 | 一级看片免费视频 | 无码日韩电影 | 在线高清无码不卡 |