OpenFeign 如何做到 "隔空取物" ?
來源:碼猿技術專欄
大家好,我是不才陳某~
OpenFeign 元件的前身是 Netflix Feign 專案,它最早是作為 Netflix OSS 專案的一部分,由 Netflix 公司開發。後來 Feign 專案被貢獻給了開源組織,於是才有了我們今天使用的 Spring Cloud OpenFeign 元件。
OpenFeign 提供了一種宣告式的遠端呼叫介面,它可以大幅簡化遠端呼叫的程式設計體驗。在瞭解 OpenFeign 的原理之前,先來體驗一下 OpenFeign 的最終療效。我用了一個Hello World 的小案例,帶你看一下由 OpenFeign 發起的遠端服務呼叫的程式碼風格是什麼樣的。
String response = helloWorldService.hello("Vincent Y.");
你可能會問,這不就是本地方法呼叫嗎?沒錯!使用 OpenFeign 元件來實現遠端呼叫非常簡單,就像我們使用本地方法一樣,只要一行程式碼就能實現 WebClient 元件好幾行程式碼乾的事情。而且這段程式碼不包含任何業務無關的資訊,完美實現了呼叫邏輯和業務邏輯之間的職責分離。
那麼,OpenFeign 元件在底層是如何實現遠端呼叫的呢?接下來我就帶你瞭解OpenFeign 元件背後的工作流程。
OpenFeign 使用了一種“動態代理”技術來封裝遠端服務呼叫的過程,我們在上面的例子中看到的 helloWorldService 其實是一個特殊的介面,它是由 OpenFeign 元件中的FeignClient 註解所宣告的介面,介面中的程式碼如下所示。
@FeignClient(value = "hello-world-serv")
public interface HelloWorldService {
@PostMapping("/sayHello")
String hello(String guestName);
}
到這裡你一定恍然大悟了,原來遠端服務呼叫的資訊被寫在了 FeignClient 介面中。
在上面的程式碼裡,你可以看到,服務的名稱、介面型別、訪問路徑已經透過註解做了宣告。
OpenFeign 透過解析這些註解標籤生成一個“動態代理類”,這個代理類會將介面呼叫轉化為一個遠端服務呼叫的 Request,併傳送給目標服務。
那麼 OpenFeign 的動態代理是如何運作的呢?接下來,我就帶你去深入瞭解這背後的流程。
OpenFeign 的動態代理
在專案初始化階段,OpenFeign 會生成一個代理類,對所有透過該介面發起的遠端呼叫進行動態代理。我畫了一個流程圖,幫你理解 OpenFeign 的動態代理流程:
上圖中的步驟 1 到步驟 3 是在專案啟動階段載入完成的,只有第 4 步“呼叫遠端服務”是發生在專案的執行階段。
下面我來解釋一下上圖中的幾個關鍵步驟。
首先,在專案啟動階段,OpenFeign 框架會發起一個主動的掃包流程,從指定的目錄下掃描並載入所有被 @FeignClient 註解修飾的介面。
然後,OpenFeign 會針對每一個 FeignClient 介面生成一個動態代理物件,即圖中的FeignProxyService,這個代理物件在繼承關係上屬於 FeignClient 註解所修飾的介面的例項。
接下來,這個動態代理物件會被新增到 Spring 上下文中,並注入到對應的服務裡,也就是圖中的 LocalService 服務。
最後,LocalService 會發起底層方法呼叫。實際上這個方法呼叫會被 OpenFeign 生成的代理物件接管,由代理物件發起一個遠端服務呼叫,並將呼叫的結果返回給LocalService。
我猜你一定很好奇:OpenFeign 是如何透過動態代理技術建立代理物件的?我畫了一張流程圖幫你梳理這個過程,你可以參考一下。
我把 OpenFeign 元件載入過程的重要階段畫在了上圖中。接下來我帶你梳理一下OpenFeign 動態代理類的建立過程。
專案載入:在專案的啟動階段,EnableFeignClients 註解扮演了“啟動開關”的角色,它使用 Spring 框架的 Import 註解匯入了 FeignClientsRegistrar 類,開始了OpenFeign 元件的載入過程。 掃包:FeignClientsRegistrar 負責 FeignClient 介面的載入,它會在指定的包路徑下掃描所有的 FeignClients 類,並構造 FeignClientFactoryBean 物件來解析FeignClient 介面。 解析 FeignClient 註解:FeignClientFactoryBean 有兩個重要的功能,一個是解析FeignClient 介面中的請求路徑和降級函式的配置資訊;另一個是觸發動態代理的構造過程。其中,動態代理構造是由更下一層的 ReflectiveFeign 完成的。 構建動態代理物件:ReflectiveFeign 包含了 OpenFeign 動態代理的核心邏輯,它主要負責建立出 FeignClient 介面的動態代理物件。ReflectiveFeign 在這個過程中有兩個重要任務,一個是解析 FeignClient 介面上各個方法級別的註解,將其中的遠端介面URL、介面型別(GET、POST 等)、各個請求引數等封裝成後設資料,併為每一個方法生成一個對應的 MethodHandler 類作為方法級別的代理;另一個重要任務是將這些MethodHandler 方法代理做進一步封裝,透過 Java 標準的動態代理協議,構建一個實現了 InvocationHandler 介面的動態代理物件,並將這個動態代理物件繫結到FeignClient 介面上。這樣一來,所有發生在 FeignClient 介面上的呼叫,最終都會由它背後的動態代理物件來承接。
MethodHandler 的構建過程涉及到了複雜的後設資料解析,OpenFeign 元件將FeignClient 介面上的各種註解封裝成後設資料,並利用這些後設資料把一個方法呼叫“翻譯”成一個遠端呼叫的 Request 請求。
那麼上面說到的“後設資料的解析”是如何完成的呢?
它依賴於 OpenFeign 元件中的Contract 協議解析功能。Contract 是 OpenFeign 元件中定義的頂層抽象介面,它有一系列的具體實現,其中和我們實戰專案有關的是 SpringMvcContract 這個類,從這個類的名字中我們就能看出來,它是專門用來解析 Spring MVC 標籤的。
SpringMvcContract 的繼承結構是 SpringMvcContract->BaseContract->Contract。我這裡拿一段 SpringMvcContract 的程式碼,幫助你深入理解它是如何將註解解析為後設資料的。這段程式碼的主要功能是解析 FeignClient 方法級別上定義的 Spring MVC 註解。
// 解析FeignClient介面方法級別上的RequestMapping註解
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
// 省略部分程式碼...
// 如果方法上沒有使用RequestMapping註解,則不進行解析
// 其實GetMapping、PostMapping等註解都屬於RequestMapping註解
if (!RequestMapping.class.isInstance(methodAnnotation)
&& !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
return;
}
// 獲取RequestMapping註解例項
RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
// 解析Http Method定義,即註解中的GET、POST、PUT、DELETE方法型別
RequestMethod[] methods = methodMapping.method();
// 如果沒有定義methods屬性則預設當前方法是個GET方法
if (methods.length == 0) {
methods = new RequestMethod[] { RequestMethod.GET };
}
checkOne(method, methods, "method");
data.template().method(Request.HttpMethod.valueOf(methods[0].name()));
// 解析Path屬性,即方法上寫明的請求路徑
checkAtMostOne(method, methodMapping.value(), "value");
if (methodMapping.value().length > 0) {
String pathValue = emptyToNull(methodMapping.value()[0]);
if (pathValue != null) {
pathValue = resolve(pathValue);
// 如果path沒有以斜槓開頭,則補上/
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 對 RequestMappings 註解的各個屬性都做了解析。
如果你在專案中使用的是 GetMapping、PostMapping 之類的註解,沒有使用 RequestMapping,那麼 OpenFeign 還能解析嗎?當然可以。以 GetMapping 為例,它對 RequestMapping 註解做了一層封裝。如果你檢視下面關於 GetMapping 註解的程式碼,你會發現這個註解頭上也掛了一個 RequestMapping 註解。因此 OpenFeign 可以正確識別 GetMapping 並完成載入。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
// ...省略部分程式碼
}
總結
今天你清楚了 OpenFeign 要解決的問題,我還帶你瞭解了 OpenFeign 的工作流程,這裡面的重點是動態代理機制。OpenFeing 透過 Java 動態代理生成了一個“代理類”,這個代理類將介面呼叫轉化成為了一個遠端服務呼叫。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024922/viewspace-2953344/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Guuci羅生門,唯品會與得物隔空互撕!
- 隔空竊取雷達幣,新型詐騙方式出現
- 如何使用 iOS 15 和 macOS Monterey 將視訊隔空播放到 MaciOSMac
- iQOO手機隔空解鎖怎麼用?iQOO手機隔空解鎖的設定方法教程
- macOS Monterey原生支援「隔空播放到 Mac」Mac
- OpenFeign 原理
- OpenFeign概述
- 如何使用隔空播放來映象或擴充套件蘋果Mac顯示器?套件蘋果Mac
- 國際空運公司對於大件貨物如何收費?
- https 如何做到安全HTTP
- SpringCloud-OpenFeignSpringGCCloud
- scrapy 爬取空值
- 如何把Nginx做到最優?Nginx
- OpenFeign簡單使用
- OpenFeign 使用細節
- OpenFeign使用步驟
- 獲取表空間DDL
- 如何實現css隔離?CSS
- 如何做到告警的智慧降噪?
- [Flutter翻譯]為什麼要在Flutter中使用隔離物?Flutter
- 需要取最近的非空值
- 06.OpenFeign介面呼叫
- Spring Cloud OpenFeign呼叫流程SpringCloud
- 大物始於小:我是如何做到 GitHub star 在 5 天內從 0 飆至 666 的Github
- 時隔5年復活上線,這款剪影風ARPG手遊做到了不過時
- 如何做到API文件規範化API
- 【c#版本Openfeign】Net8 自帶OpenFeign實現遠端介面呼叫C#
- 隔空充電來了!或將帶來一場充電革命
- OpenFeign深入學習筆記筆記
- SpringCloud(3)-OpenFeign相關配置SpringGCCloud
- Spring Cloud OpenFeign的原理(六)SpringCloud
- Spring Cloud OpenFeign整合Protocol BufferSpringCloudProtocol
- 如何做到用資料說話(一)
- Flutter的Hot Reload是如何做到的Flutter
- STO在美國如何做到合規?
- [php]如何做到高併發優化PHP優化
- MySQL是如何實現事物隔離?MySql
- PHP 獲取伺服器磁碟空間PHP伺服器