<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>

          OpenFeign 如何做到 "隔空取物" ?

          共 5881字,需瀏覽 12分鐘

           ·

          2023-05-27 19:11

          3cc9b1289f3506595d15a8be2c15379c.webp程序員的成長之路互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享? 關(guān)注


          閱讀本文大概需要 6?分鐘。

          來自:網(wǎng)絡(luò),侵刪

          OpenFeign 組件的前身是 Netflix Feign 項(xiàng)目,它最早是作為 Netflix OSS 項(xiàng)目的一部分,由 Netflix 公司開發(fā)。后來 Feign 項(xiàng)目被貢獻(xiàn)給了開源組織,于是才有了我們今天使用的 Spring Cloud OpenFeign 組件。OpenFeign 提供了一種聲明式的遠(yuǎn)程調(diào)用接口,它可以大幅簡(jiǎn)化遠(yuǎn)程調(diào)用的編程體驗(yàn)。在了解 OpenFeign 的原理之前,先來體驗(yàn)一下 OpenFeign 的最終療效。我用了一個(gè)Hello World 的小案例,帶你看一下由 OpenFeign 發(fā)起的遠(yuǎn)程服務(wù)調(diào)用的代碼風(fēng)格是什么樣的。
              

          String?response?=?helloWorldService.hello("Vincent?Y.");

          你可能會(huì)問,這不就是本地方法調(diào)用嗎?沒錯(cuò)!使用 OpenFeign 組件來實(shí)現(xiàn)遠(yuǎn)程調(diào)用非常簡(jiǎn)單,就像我們使用本地方法一樣,只要一行代碼就能實(shí)現(xiàn)?WebClient?組件好幾行代碼干的事情。而且這段代碼不包含任何業(yè)務(wù)無關(guān)的信息,完美實(shí)現(xiàn)了調(diào)用邏輯和業(yè)務(wù)邏輯之間的職責(zé)分離。那么,OpenFeign 組件在底層是如何實(shí)現(xiàn)遠(yuǎn)程調(diào)用的呢?接下來我就帶你了解OpenFeign 組件背后的工作流程。OpenFeign 使用了一種“動(dòng)態(tài)代理”技術(shù)來封裝遠(yuǎn)程服務(wù)調(diào)用的過程,我們?cè)谏厦娴睦又锌吹降?span>?helloWorldService?其實(shí)是一個(gè)特殊的接口,它是由 OpenFeign 組件中的FeignClient 注解所聲明的接口,接口中的代碼如下所示。
              

          @FeignClient(value?=?"hello-world-serv")?
          public?interface?HelloWorldService?{?
          ????@PostMapping("/sayHello")?
          ????String?hello(String?guestName);?
          }

          到這里你一定恍然大悟了,原來遠(yuǎn)程服務(wù)調(diào)用的信息被寫在了 FeignClient 接口中。在上面的代碼里,你可以看到,服務(wù)的名稱、接口類型、訪問路徑已經(jīng)通過注解做了聲明。OpenFeign 通過解析這些注解標(biāo)簽生成一個(gè)“動(dòng)態(tài)代理類”,這個(gè)代理類會(huì)將接口調(diào)用轉(zhuǎn)化為一個(gè)遠(yuǎn)程服務(wù)調(diào)用的 Request,并發(fā)送給目標(biāo)服務(wù)。那么 OpenFeign 的動(dòng)態(tài)代理是如何運(yùn)作的呢?接下來,我就帶你去深入了解這背后的流程。

          OpenFeign ? 的動(dòng)態(tài)代理

          在項(xiàng)目初始化階段,OpenFeign 會(huì)生成一個(gè)代理類,對(duì)所有通過該接口發(fā)起的遠(yuǎn)程調(diào)用進(jìn)行動(dòng)態(tài)代理。我畫了一個(gè)流程圖,幫你理解 OpenFeign 的動(dòng)態(tài)代理流程:ff081b2d592d8148cdfde1f13a6a5976.webp上圖中的步驟 1 到步驟 3 是在項(xiàng)目啟動(dòng)階段加載完成的,只有第 4 步“調(diào)用遠(yuǎn)程服務(wù)”是發(fā)生在項(xiàng)目的運(yùn)行階段。下面我來解釋一下上圖中的幾個(gè)關(guān)鍵步驟。首先,在項(xiàng)目啟動(dòng)階段,OpenFeign 框架會(huì)發(fā)起一個(gè)主動(dòng)的掃包流程,從指定的目錄下掃描并加載所有被?@FeignClient?注解修飾的接口。然后,OpenFeign 會(huì)針對(duì)每一個(gè) FeignClient 接口生成一個(gè)動(dòng)態(tài)代理對(duì)象,即圖中的FeignProxyService,這個(gè)代理對(duì)象在繼承關(guān)系上屬于 FeignClient 注解所修飾的接口的實(shí)例。接下來,這個(gè)動(dòng)態(tài)代理對(duì)象會(huì)被添加到 Spring 上下文中,并注入到對(duì)應(yīng)的服務(wù)里,也就是圖中的 LocalService 服務(wù)。最后,LocalService 會(huì)發(fā)起底層方法調(diào)用。實(shí)際上這個(gè)方法調(diào)用會(huì)被 OpenFeign 生成的代理對(duì)象接管,由代理對(duì)象發(fā)起一個(gè)遠(yuǎn)程服務(wù)調(diào)用,并將調(diào)用的結(jié)果返回給LocalService。我猜你一定很好奇:OpenFeign 是如何通過動(dòng)態(tài)代理技術(shù)創(chuàng)建代理對(duì)象的?我畫了一張流程圖幫你梳理這個(gè)過程,你可以參考一下。7c1698d08f96e85af4d11e45dfe4482a.webp我把 OpenFeign 組件加載過程的重要階段畫在了上圖中。接下來我?guī)闶崂硪幌翺penFeign 動(dòng)態(tài)代理類的創(chuàng)建過程。
          1. 項(xiàng)目加載:在項(xiàng)目的啟動(dòng)階段,EnableFeignClients 注解扮演了“啟動(dòng)開關(guān)”的角色,它使用 Spring 框架的?Import 注解導(dǎo)入了 FeignClientsRegistrar 類,開始了OpenFeign 組件的加載過程。
          2. 掃包FeignClientsRegistrar?負(fù)責(zé) FeignClient 接口的加載,它會(huì)在指定的包路徑下掃描所有的 FeignClients 類,并構(gòu)造 FeignClientFactoryBean 對(duì)象來解析FeignClient 接口。
          3. 解析 FeignClient 注解FeignClientFactoryBean?有兩個(gè)重要的功能,一個(gè)是解析FeignClient 接口中的請(qǐng)求路徑和降級(jí)函數(shù)的配置信息;另一個(gè)是觸發(fā)動(dòng)態(tài)代理的構(gòu)造過程。其中,動(dòng)態(tài)代理構(gòu)造是由更下一層的 ReflectiveFeign 完成的。
          4. 構(gòu)建動(dòng)態(tài)代理對(duì)象ReflectiveFeign?包含了 OpenFeign 動(dòng)態(tài)代理的核心邏輯,它主要負(fù)責(zé)創(chuàng)建出 FeignClient 接口的動(dòng)態(tài)代理對(duì)象。ReflectiveFeign 在這個(gè)過程中有兩個(gè)重要任務(wù),一個(gè)是解析 FeignClient 接口上各個(gè)方法級(jí)別的注解,將其中的遠(yuǎn)程接口URL、接口類型(GET、POST 等)、各個(gè)請(qǐng)求參數(shù)等封裝成元數(shù)據(jù),并為每一個(gè)方法生成一個(gè)對(duì)應(yīng)的 MethodHandler 類作為方法級(jí)別的代理;另一個(gè)重要任務(wù)是將這些MethodHandler 方法代理做進(jìn)一步封裝,通過 Java 標(biāo)準(zhǔn)的動(dòng)態(tài)代理協(xié)議,構(gòu)建一個(gè)實(shí)現(xiàn)了 InvocationHandler 接口的動(dòng)態(tài)代理對(duì)象,并將這個(gè)動(dòng)態(tài)代理對(duì)象綁定到FeignClient 接口上。這樣一來,所有發(fā)生在 FeignClient 接口上的調(diào)用,最終都會(huì)由它背后的動(dòng)態(tài)代理對(duì)象來承接。
          MethodHandler?的構(gòu)建過程涉及到了復(fù)雜的元數(shù)據(jù)解析,OpenFeign 組件將FeignClient 接口上的各種注解封裝成元數(shù)據(jù),并利用這些元數(shù)據(jù)把一個(gè)方法調(diào)用“翻譯”成一個(gè)遠(yuǎn)程調(diào)用的 Request 請(qǐng)求。那么上面說到的“元數(shù)據(jù)的解析”是如何完成的呢?它依賴于 OpenFeign 組件中的Contract 協(xié)議解析功能。Contract 是 OpenFeign 組件中定義的頂層抽象接口,它有一系列的具體實(shí)現(xiàn),其中和我們實(shí)戰(zhàn)項(xiàng)目有關(guān)的是 SpringMvcContract 這個(gè)類,從這個(gè)類的名字中我們就能看出來,它是專門用來解析 Spring MVC 標(biāo)簽的。SpringMvcContract 的繼承結(jié)構(gòu)是?SpringMvcContract->BaseContract->Contract。我這里拿一段 SpringMvcContract 的代碼,幫助你深入理解它是如何將注解解析為元數(shù)據(jù)的。這段代碼的主要功能是解析 FeignClient 方法級(jí)別上定義的 Spring MVC 注解。
              


          //?解析FeignClient接口方法級(jí)別上的RequestMapping注解
          protected?void?processAnnotationOnMethod(MethodMetadata?data,?Annotation?methodAnnotation,?Method?method)?{
          ???//?省略部分代碼...
          ???
          ???//?如果方法上沒有使用RequestMapping注解,則不進(jìn)行解析
          ???//?其實(shí)GetMapping、PostMapping等注解都屬于RequestMapping注解
          ???if?(!RequestMapping.class.isInstance(methodAnnotation)
          ?????????&&?!methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class))?
          {
          ??????return;
          ???}

          ???//?獲取RequestMapping注解實(shí)例
          ???RequestMapping?methodMapping?=?findMergedAnnotation(method,?RequestMapping.class);
          ???//?解析Http?Method定義,即注解中的GET、POST、PUT、DELETE方法類型
          ???RequestMethod[]?methods?=?methodMapping.method();
          ???//?如果沒有定義methods屬性則默認(rèn)當(dāng)前方法是個(gè)GET方法
          ???if?(methods.length?==?0)?{
          ??????methods?=?new?RequestMethod[]?{?RequestMethod.GET?};
          ???}
          ???checkOne(method,?methods,?"method");
          ???data.template().method(Request.HttpMethod.valueOf(methods[0].name()));

          ???//?解析Path屬性,即方法上寫明的請(qǐng)求路徑
          ???checkAtMostOne(method,?methodMapping.value(),?"value");
          ???if?(methodMapping.value().length?>?0)?{
          ??????String?pathValue?=?emptyToNull(methodMapping.value()[0]);
          ??????if?(pathValue?!=?null)?{
          ?????????pathValue?=?resolve(pathValue);
          ?????????//?如果path沒有以斜杠開頭,則補(bǔ)上/
          ?????????if?(!pathValue.startsWith("/")?&&?!data.template().path().endsWith("/"))?{
          ????????????pathValue?=?"/"?+?pathValue;
          ?????????}
          ?????????data.template().uri(pathValue,?true);
          ?????????if?(data.template().decodeSlash()?!=?decodeSlash)?{
          ????????????data.template().decodeSlash(decodeSlash);
          ?????????}
          ??????}
          ???}

          ???//?解析RequestMapping中定義的produces屬性
          ???parseProduces(data,?method,?methodMapping);

          ???//?解析RequestMapping中定義的consumer屬性
          ???parseConsumes(data,?method,?methodMapping);

          ???//?解析RequestMapping中定義的headers屬性
          ???parseHeaders(data,?method,?methodMapping);
          ???data.indexToExpander(new?LinkedHashMap<>());
          }

          通過上面的方法,我們可以看到,OpenFeign 對(duì) RequestMappings 注解的各個(gè)屬性都做了解析。如果你在項(xiàng)目中使用的是 GetMapping、PostMapping 之類的注解,沒有使用 RequestMapping,那么 OpenFeign 還能解析嗎?當(dāng)然可以。以 GetMapping 為例,它對(duì) RequestMapping 注解做了一層封裝。如果你查看下面關(guān)于 GetMapping 注解的代碼,你會(huì)發(fā)現(xiàn)這個(gè)注解頭上也掛了一個(gè) RequestMapping 注解。因此 OpenFeign 可以正確識(shí)別 GetMapping 并完成加載。
              


          @Target(ElementType.METHOD)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @RequestMapping(method?=?RequestMethod.GET)
          public?@interface?GetMapping?{
          //?...省略部分代碼
          }

          總結(jié)

          今天你清楚了 OpenFeign 要解決的問題,我還帶你了解了 OpenFeign 的工作流程,這里面的重點(diǎn)是動(dòng)態(tài)代理機(jī)制。OpenFeing 通過 Java 動(dòng)態(tài)代理生成了一個(gè)“代理類”,這個(gè)代理類將接口調(diào)用轉(zhuǎn)化成為了一個(gè)遠(yuǎn)程服務(wù)調(diào)用。<END>

          推薦閱讀:

          還在服務(wù)器上撈日志?試試這款可視化監(jiān)控系統(tǒng)吧,真香!

          Spring Security 添加二次認(rèn)證:提高應(yīng)用安全性

              
                  互聯(lián)網(wǎng)初中高級(jí)大廠面試題(9個(gè)G)
                
              

          內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!

          ?戳閱讀原文領(lǐng)??! ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??朕已閱? bd0c200b05436e070ffc76a84ae5fdf0.webp

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  三级大香蕉 | 一级a啪啪啪 | 天堂网在线免费 | 亚洲国产综合视频 | 丁香六月婷婷综合缴 |