Feign工作流程源碼解析

本文討論是建立在Spring Cloud整合Feign的基礎上。

Spring Boot的版本是1.4.5

feign-core的版本是9.3.1

什么是feign:一款基于注解和動態代理的聲明式restful http客戶端。


一,Feign發送請求實現原理


微服務啟動類上標記@EnableFeignClients注解,然后Feign接口上標記@FeignClient注解。@FeignClient注解有幾個參數需要配置,這里不再贅述,都很簡單。

Feign框架會掃描注解,然后通過Feign類來處理注解,并最終生成一個Feign對象。


1,解析@FeignClient注解,生成MethodHandler

具體的解析類是ParseHandlerByName。這個類是ReflectiveFeign的內部類。

// 解析注解元數據,使用Contract解析

List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(key.type());

拿到注解元數據以后,循環處理注解元數據,創建每個方法對應的MethodHandler,這個MethodHandler最終會被代理對象調用。最終MethodHandler都會保存到下面這個集合中,然后返回。

Map<String, MethodHandler> result = new LinkedHashMap();


2,解析完成以后,調用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,多么偉大的創舉!


3,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();

? ? }

}


4,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放在請求頭里。


5,使用feign.Request把RequestTemplate包裝成feign.Request

feign.Request由5部分組成:

method

url

headers

body

charset


6,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 RibbonRequest(this.delegate, request, uriWithoutHost);

// 配置超時時間

IClientConfig requestConfig = this.getClientConfig(options, clientName);

// 以負載均衡的方式發送請求

return ((RibbonResponse)this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();


7,以負載均衡的方式發送請求


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


OK,總算見到了我們熟悉的HttpClient發送請求的邏輯了,接下來就是執行HttpClient的請求了!Feign的實現原理到這里可以告一段落,不熟悉HttpClient可以接著往下看。


二,處理http相應


http請求經過上面一系列的轉發以后,最終還會回到SynchronousMethodHandler,然后SynchronousMethodHandler會進行一系列的處理,然后響應到瀏覽器。


三,注冊Feign客戶端bean到IOC容器


查看Feign框架源代碼,我們可以發現,FeignClientsRegistar的registerFeignClients()方法完成了feign相關bean的注冊。


四,Feign架構圖


圖片發自簡書App

第一步:基于JDK動態代理生成代理類。

第二步:根據接口類的注解聲明規則,解析出底層MethodHandler

第三步:基于RequestBean動態生成request。

第四步:Encoder將bean包裝成請求。

第五步:攔截器負責對請求和返回進行裝飾處理。

第六步:日志記錄。

第七步:基于重試器發送http請求,支持不同的http框架,默認使用的是HttpUrlConnection。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容