1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 7903字,需瀏覽 16分鐘

         ·

        2020-10-05 12:36

        點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

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

        ? 作者?|??泥粑

        來源 |? urlify.cn/amyIf2

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

        前言

        feign 是目前微服務(wù)間通信的主流方式,是springCloud中一個(gè)非常重要的組件。他涉及到了負(fù)載均衡、限流等組件。真正意義上掌握了feign可以說就掌握了微服務(wù)。

        一、feign的使用

        feign 的使用和dubbo的使用本質(zhì)上非常相似。dubbo的理念是:像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程方法。那么套在feign上同樣適用:像調(diào)用本地接口一樣調(diào)用遠(yuǎn)程接口。
        使用feign只需要2步:定義一個(gè)接口并用FeignClient注解說明接口所在服務(wù)和路徑,服務(wù)啟動(dòng)類上添加@EnableFeignClients。如下所示

        1.1,定義一個(gè)feign接口

        @FeignClient(contextId?=?"order",?name?=?"order",?path?=?"/app")
        public?interface?OrderApiFeignClient?{

        ???/**
        ????*?獲取訂單列表
        ????*?@return
        ????*/
        ???@RequestMapping("order/list")
        ???BaseResponse>?obtaining(@PathVariable("userId")?Long?userId);
        }

        1.2,再啟動(dòng)類上添加注解

        @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 接口如何被實(shí)例化到spring容器的?

        首先按照一般的思路,我們會(huì)猜測(cè)基于接口生成代理類,然后對(duì)接口的調(diào)用實(shí)際上調(diào)的是代理對(duì)象,那真的是這樣么?我們帶著猜想往下看。

        2.1 @EnableFeignClients 注解都做了些什么?

        可以看到注解本身主要定義了要掃描的feign接口包路徑以及配置,但是注解本身又有注解Import ,可以看到他引入了FeignClientsRegistrar到容器。從名字看這個(gè)類就應(yīng)該是在將feign接口注冊(cè)到容器中,接下來我們具體看一下這個(gè)類干了些什么。

        /**
        ?*?@author?Spencer?Gibb
        ?*?@author?Jakub?Narloch
        ?*?@author?Venil?Noronha
        ?*?@author?Gang?Li
        ?*/
        class?FeignClientsRegistrar
        ??implements?ImportBeanDefinitionRegistrar,?ResourceLoaderAware,?EnvironmentAware?{

        可以看到FeignClientsRegistrar實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口,但凡是實(shí)現(xiàn)了這個(gè)接口的類被注入到容器后,spring容器在啟用過程中都會(huì)去調(diào)用它的void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2)方法,可以確定的是FeignClientsRegistrar肯定重寫了此方法,我們接下來看一下該方法的實(shí)現(xiàn)。

        可以看到在這個(gè)方法中做了兩件事:1)注冊(cè)feign配置, 2)注冊(cè)feign接口。我們這里抓一下重點(diǎn),看一下feign接口是怎么注冊(cè)的?

        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并且注冊(cè)到容器
        ?????registerFeignClient(registry,?annotationMetadata,?attributes);
        ????}
        ???}
        ??}
        ?}

        上面這段代碼概括起來就是:先找了包路徑basePackages , 然后在從這些包路徑中查找?guī)в蠪eignClient注解的接口,最后將注解的信息解析出來作為屬性手動(dòng)構(gòu)建beanDefine注入到容器中。(這里有一個(gè)類ClassPathScanningCandidateComponentProvider,它可以根據(jù)filter掃描指定包下面的class對(duì)象,十分好用,建議收藏)。包路徑的獲取以及掃描feign相對(duì)簡單,這里不做闡述,我們看一下它生成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(請(qǐng)記住這里設(shè)置得FeignClientFactoryBean.type就是feign接口對(duì)應(yīng)得class對(duì)象)。那么所有的feign接口最終注冊(cè)到容器中的都是FeignClientFactoryBean對(duì)應(yīng)的一個(gè)實(shí)例(注意實(shí)際上注冊(cè)到容器中壓根就不是FeignClientFactoryBean對(duì)應(yīng)的實(shí)例化對(duì)象,具體原因看下文),到此feign接口對(duì)應(yīng)的實(shí)例注冊(cè)過程已經(jīng)完成。那么回到一開始的問題為什么我們調(diào)用接口的方法最終發(fā)起了請(qǐng)求?是否有代理類的生成呢?我們接下來看看FeignClientFactoryBean類的特殊之處

        2.2 FeignClientFactoryBean 類,feign接口代理生成類

        由上文知,每一個(gè)feign接口實(shí)際上最終都會(huì)生成FeignClientFactoryBean ,最終由FeignClientFactoryBean生成具體的bean實(shí)例注冊(cè)到容器中。

        /**
        ?*?@author?Spencer?Gibb
        ?*?@author?Venil?Noronha
        ?*?@author?Eko?Kurniawan?Khannedy
        ?*?@author?Gregor?Zurowski
        ?*/
        class?FeignClientFactoryBean
        ??implements?FactoryBean,?InitializingBean,?ApplicationContextAware?

        可以看到該類實(shí)現(xiàn)了FactoryBean接口,這意味著當(dāng)Spring注冊(cè)該bean實(shí)例到容器中時(shí),實(shí)際是調(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這個(gè)熟悉得身影了,沒錯(cuò)他就是基于JDK原生得動(dòng)態(tài)代理生成了FeignClientFactoryBean.type屬性對(duì)應(yīng)得class對(duì)應(yīng)得代理類。從前文我們知道FeignClientFactoryBean.type就是feign接口得class對(duì)象。所以最終我們調(diào)用feign接口得方法實(shí)際上調(diào)用得是InvocationHandler方法。

        三、小結(jié)

        總結(jié)起來,就是常常我們掛在口頭的東西就是將feign接口生成代理類,然后調(diào)用代理接口方法其實(shí)調(diào)用的代理類得方法,具體是為什么?不知道大家是否清楚。希望通過本文的閱讀能讓大家閱讀源碼的能力得到提升,也不在對(duì)feign有一種黑盒子的感覺??赡芷雌饋磔^少,其實(shí)feign的注冊(cè)過程牽涉到框架層面的知識(shí)還是蠻多的,包括springIoc、BeanDefine、動(dòng)態(tài)代理等等,仔細(xì)看明白的話收獲應(yīng)該還是有蠻多的。哈哈,懂得都懂。順手提一句:讀源碼一定要對(duì)SPI等等特別熟悉,要不然你會(huì)無從下手,沒有方向,抓不到重點(diǎn)。后續(xù)會(huì)更新文章講feign怎么實(shí)現(xiàn)負(fù)載均衡、熔斷等。





        粉絲福利:108本java從入門到大神精選電子書領(lǐng)取

        ???

        ?長按上方鋒哥微信二維碼?2 秒
        備注「1234」即可獲取資料以及
        可以進(jìn)入java1234官方微信群



        感謝點(diǎn)贊支持下哈?


        瀏覽 36
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            AV中文无码| 亚洲性爱一区二区三区| 懂色AV无码中字幕一区| 成人毛片AV无码| 欧美不卡一区二区三区| www99热| 网络自拍亚洲激情| 亚洲字幕AV| 亚洲视频网站在线观看| 四虎AV在线|