OpenFeign 如何做到 "隔空取物" ?
程序員的成長之路互聯(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.");
@FeignClient(value?=?"hello-world-serv")?
public?interface?HelloWorldService?{?
????@PostMapping("/sayHello")?
????String?hello(String?guestName);?
}
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)代理流程:
上圖中的步驟 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è)過程,你可以參考一下。
我把 OpenFeign 組件加載過程的重要階段畫在了上圖中。接下來我?guī)闶崂硪幌翺penFeign 動(dòng)態(tài)代理類的創(chuàng)建過程。- 項(xiàng)目加載:在項(xiàng)目的啟動(dòng)階段,EnableFeignClients 注解扮演了“啟動(dòng)開關(guān)”的角色,它使用 Spring 框架的?Import 注解導(dǎo)入了 FeignClientsRegistrar 類,開始了OpenFeign 組件的加載過程。
- 掃包:FeignClientsRegistrar?負(fù)責(zé) FeignClient 接口的加載,它會(huì)在指定的包路徑下掃描所有的 FeignClients 類,并構(gòu)造 FeignClientFactoryBean 對(duì)象來解析FeignClient 接口。
- 解析 FeignClient 注解:FeignClientFactoryBean?有兩個(gè)重要的功能,一個(gè)是解析FeignClient 接口中的請(qǐng)求路徑和降級(jí)函數(shù)的配置信息;另一個(gè)是觸發(fā)動(dòng)態(tài)代理的構(gòu)造過程。其中,動(dòng)態(tài)代理構(gòu)造是由更下一層的 ReflectiveFeign 完成的。
- 構(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ì)象來承接。
//?解析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<>());
}
@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)??!
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??朕已閱?
![]()
