SpringCloud-技術專區-從源碼層面讓你認識Feign工作流程和運作機制
- 2021 年 8 月 18 日
- 筆記
- SpringCloud-技術專區-系列專題
Feign工作流程源碼解析
什麼是feign:一款基於註解和動態代理的聲明式restful http客戶端。
原理
Feign發送請求實現原理
-
微服務啟動類上標記@EnableFeignClients註解,然後Feign接口上標記@FeignClient註解。@FeignClient註解有幾個參數需要配置,這裡不再贅述,都很簡單。
-
Feign框架會掃描註解,然後通過Feign類來處理註解,並最終生成一個Feign對象。
解析@FeignClient註解,生成MethodHandler
具體的解析類是ParseHandlerByName。這個類是ReflectiveFeign的內部類。
// 解析註解元數據,使用Contract解析
List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(key.type());
拿到註解元數據以後,循環處理註解元數據,創建每個方法對應的MethodHandler,這個MethodHandler最終會被代理對象調用。最終MethodHandler都會保存到下面這個集合中,然後返回。
Map<String, MethodHandler> result = new LinkedHashMap();
解析完成以後,調用ReflectiveFeign.newInstance()生成代理類。
MethodHandler是feign的一個接口,這個接口的invoke方法,是動態代理調用者InvocationHandler的invoke()方法最終調用的方法。
重新表述一遍:InvocationHandler的invoke()方法最終回調MethodHandler的invoke()來發送http請求。這就是Feign動態代理的具體實現。
ReflectiveFeign類的newInstance()方法的第57行:
// 創建動態代理調用者
InvocationHandler handler = this.factory.create(target, methodToHandler);
// 反射生成feign接口代理
T proxy = Proxy.newProxyInstance(加載器, 接口數組, handler);
InvocationHandler.invoke()的具體實現在FeignInvocationHandler.invoke(),FeignInvocationHandler也是ReflectiveFeign的一個內部類。裏面有很多細節處理這裡不再贅述,我們直接進入核心那一行代碼,以免影響思路,我們是理Feign的實現原理的!不要在意這些細節!
// InvocationHandler的invoke()方法最終回調MethodHandler的invoke()來發送http請求
ReflectiveFeign類的invoke()方法,第323行,代碼的後半段,如下:
(MethodHandler)this.dispatch.get(method). invoke(args);
-
this.dispatch:這是一個map,就是保存所有的MethodHandler的集合。參考創建InvocationHandler的位置:ReflectiveFeign類的newInstance()方法的第57行。
-
this.dispatch.get(method):這裡的method就是我們開發者寫的feign接口中定義的方法的方法名!這段代碼的意思就是從MethodHandler集合中拿到我們需要調用的那個方法。
-
this.dispatch.get(method). invoke(args):這裡的invoke就是調用的MethodHandler.invoke()!動態代理回調代理類,就這樣完成了,oh my god,多麼偉大的創舉!
MethodHandler.invoke()的具體實現:SynchronousMethodHandler.invoke()
到了這裡,就是發送請求的邏輯了。發送請求前,首先要創建請求模板,然後調用請求攔截器RequestInterceptor進行請求處理。
// 創建RequestTemplate
RequestTemplate template = this.buildTemlpateFromArgs.create(argv);
// 創建feign重試器,進行失敗重試
Retryer retryer = this.retryer.clone();
while(true){
try{
// 發送請求
return this.executeAndDecode(template);
} catch(RetryableException var5) {
// 失敗重試,最多重試5次
retryer.continueOrPropagate();
}
}
RequestTemplate處理
RequestTemplate模板需要經過一系列攔截器的處理,主要有以下攔截器:
-
BasicAuthRequestInterceptor:授權攔截器,主要是設置請求頭的Authorization信息,這裡是base64轉碼後的用戶名和密碼。
-
FeignAcceptGzipEncodingInterceptor:編碼類型攔截器,主要是設置請求頭的Accept-Encoding信息,默認值{gzip, deflate}。
-
FeignContextGzipEncodingInterceptor:壓縮格式攔截器,該攔截器會判斷請求頭中Context-Length屬性的值,是否大於請求內容的最大長度,如果超過最大長度2048,則設置請求頭的Context-Encoding信息,默認值{gzip, deflate}。注意,這裡的2048是可以設置的,可以在配置文件中進行配置:
feign.compression.request.enabled=true
feign.compression.request.min-request-size=2048
min-request-size是通過FeignClientEncodingProperties來解析的,默認值是2048。
我們還可以自定義請求攔截器,我們自定義的攔截器,也會在此時進行調用,所有實現了RequestTemplate接口的類,都會在這裡被調用。比如我們可以自定義攔截器把全局事務id放在請求頭裡。
使用feign.Request把RequestTemplate包裝成feign.Request
feign.Request由5部分組成:
-
method
-
url
-
headers
-
body
-
charset
http請求客戶端
Feign發送http請求支持下面幾種http客戶端:
-
JDK自帶的HttpUrlConnection
-
Apache HttpClient
-
OkHttpClient
// 具體實現有2個類Client.Default 和LoadBalancerFeignClient
response = this.client.execute(request, this.options);
Client接口定義了execute()的接口,並且通過接口內部類實現了Client.execute()。
HttpURLConnection connection = this.convertAndSend(request, options);
return this.convertResponse(connection).toBuilder(). request(request).build();
-
這裡的Options定義了2個參數:
-
connectTimeoutMillis:連接超時時間,默認10秒。
-
readTimeoutMillis:讀取數據超時時間,默認60秒。
-
這種方式是最簡單的實現,但是不支持負載均衡,Spring Cloud整合了Feign和Ribbon,所以自然會把Feign和Ribbon結合起來使用。也就是說,Feign發送請求前,會先把請求再經過一層包裝,包裝成RibbonRequest。
也就是發送請求的另一種實現LoadBalancerFeignClient。
// 把Request包裝成RibbonRequest
RibbonRequest ribbonRequest = new (this.delegate, request, uriWithoutHost);
// 配置超時時間
IClientConfig requestConfig = this.getClientConfig(options, clientName);
// 以負載均衡的方式發送請求
return ((RibbonResponse)this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
以負載均衡的方式發送請求
-
this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig))的具體實現在AbstractLoadBalancerAwareClient類中。
-
executeWithLoaderBalancer()方法的實現也參考了響應式編程,通過LoadBalancerCommand提交請求,然後使用Observable接收響應信息。
AbstractLoadBalancerAwareClient類的executeWithLoadBalancer()方法的第54行:
Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
AbstractLoadBalancerAwareClient實現了IClient接口,該接口定義了execute()方法,
-
AbstractLoadBalancerAwareClient.this.execute()的具體實現有很多種:
-
OkHttpLoadBalancingClient
-
RetryableOkHttpLoadBalancingClient
-
RibbonLoadBalancingHttpClient
-
RetryableRibbonLoadBalancingHttpClient
-
我們以RibbonLoadBalancingHttpClient為例來說明,RibbonLoadBalancingHttpClient.execute()
第62行代碼:
// 組裝HttpUriRequest
HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
// 發送http請求
HttpResponse httpResponse = ((HttpClient)this.delegate).execute(httpUriRequest);
// 使用RibbonApacheHttpResponse包裝http響應信息
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
RibbonApacheHttpResponse由2部分組成:
httpResponse
uri
處理http相應
http請求經過上面一系列的轉發以後,最終還會回到SynchronousMethodHandler,然後SynchronousMethodHandler會進行一系列的處理,然後響應到瀏覽器。
-
註冊Feign客戶端bean到IOC容器
-
查看Feign框架源代碼,我們可以發現,FeignClientsRegistar的registerFeignClients()方法完成了feign相關bean的註冊。
Feign架構圖
-
第一步:基於JDK動態代理生成代理類。
-
第二步:根據接口類的註解聲明規則,解析出底層MethodHandler
-
第三步:基於RequestBean動態生成request。
-
第四步:Encoder將bean包裝成請求。
-
第五步:攔截器負責對請求和返回進行裝飾處理。
-
第六步:日誌記錄。
-
第七步:基於重試器發送http請求,支持不同的http框架,默認使用的是HttpUrlConnection。