【SpringCloud技術(shù)專題】「Fegin技術(shù)系列」從源碼層面讓你認(rèn)識(shí)Feign工作流程和運(yùn)作機(jī)制

Feign工作流程源碼解析

什么是feign:一款基于注解和動(dòng)態(tài)代理的聲明式restful http客戶端。

原理

Feign發(fā)送請求實(shí)現(xiàn)原理

  • 微服務(wù)啟動(dòng)類上標(biāo)記@EnableFeignClients注解,然后Feign接口上標(biāo)記@FeignClient注解。@FeignClient注解有幾個(gè)參數(shù)需要配置,這里不再贅述,都很簡單。

  • Feign框架會(huì)掃描注解,然后通過Feign類來處理注解,并最終生成一個(gè)Feign對(duì)象。

解析@FeignClient注解,生成MethodHandler

具體的解析類是ParseHandlerByName。這個(gè)類是ReflectiveFeign的內(nèi)部類。

// 解析注解元數(shù)據(jù),使用Contract解析
List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(key.type());

拿到注解元數(shù)據(jù)以后,循環(huán)處理注解元數(shù)據(jù),創(chuàng)建每個(gè)方法對(duì)應(yīng)的MethodHandler,這個(gè)MethodHandler最終會(huì)被代理對(duì)象調(diào)用。最終MethodHandler都會(huì)保存到下面這個(gè)集合中,然后返回。

Map<String, MethodHandler> result = new LinkedHashMap();
解析完成以后,調(diào)用ReflectiveFeign.newInstance()生成代理類。

MethodHandler是feign的一個(gè)接口,這個(gè)接口的invoke方法,是動(dòng)態(tài)代理調(diào)用者InvocationHandler的invoke()方法最終調(diào)用的方法。

重新表述一遍:InvocationHandler的invoke()方法最終回調(diào)MethodHandler的invoke()來發(fā)送http請求。這就是Feign動(dòng)態(tài)代理的具體實(shí)現(xiàn)。

ReflectiveFeign類的newInstance()方法的第57行:
// 創(chuàng)建動(dòng)態(tài)代理調(diào)用者
InvocationHandler handler = this.factory.create(target, methodToHandler);
// 反射生成feign接口代理
T proxy = Proxy.newProxyInstance(加載器, 接口數(shù)組, handler);

InvocationHandler.invoke()的具體實(shí)現(xiàn)在FeignInvocationHandler.invoke(),F(xiàn)eignInvocationHandler也是ReflectiveFeign的一個(gè)內(nèi)部類。里面有很多細(xì)節(jié)處理這里不再贅述,我們直接進(jìn)入核心那一行代碼,以免影響思路,我們是理Feign的實(shí)現(xiàn)原理的!不要在意這些細(xì)節(jié)!

// InvocationHandler的invoke()方法最終回調(diào)MethodHandler的invoke()來發(fā)送http請求

ReflectiveFeign類的invoke()方法,第323行,代碼的后半段,如下:
(MethodHandler)this.dispatch.get(method). invoke(args);
  • this.dispatch:這是一個(gè)map,就是保存所有的MethodHandler的集合。參考創(chuàng)建InvocationHandler的位置:ReflectiveFeign類的newInstance()方法的第57行。

  • this.dispatch.get(method):這里的method就是我們開發(fā)者寫的feign接口中定義的方法的方法名!這段代碼的意思就是從MethodHandler集合中拿到我們需要調(diào)用的那個(gè)方法。

  • this.dispatch.get(method). invoke(args):這里的invoke就是調(diào)用的MethodHandler.invoke()!動(dòng)態(tài)代理回調(diào)代理類,就這樣完成了,oh my god,多么偉大的創(chuàng)舉!

MethodHandler.invoke()的具體實(shí)現(xiàn):SynchronousMethodHandler.invoke()

到了這里,就是發(fā)送請求的邏輯了。發(fā)送請求前,首先要?jiǎng)?chuàng)建請求模板,然后調(diào)用請求攔截器RequestInterceptor進(jìn)行請求處理。

// 創(chuàng)建RequestTemplate
RequestTemplate template = this.buildTemlpateFromArgs.create(argv);
// 創(chuàng)建feign重試器,進(jìn)行失敗重試
Retryer retryer = this.retryer.clone();
while(true){
    try{
        // 發(fā)送請求
        return this.executeAndDecode(template);
    } catch(RetryableException var5) {
        // 失敗重試,最多重試5次
        retryer.continueOrPropagate();
    }
}
RequestTemplate處理

RequestTemplate模板需要經(jīng)過一系列攔截器的處理,主要有以下攔截器:

  • BasicAuthRequestInterceptor:授權(quán)攔截器,主要是設(shè)置請求頭的Authorization信息,這里是base64轉(zhuǎn)碼后的用戶名和密碼。

  • FeignAcceptGzipEncodingInterceptor:編碼類型攔截器,主要是設(shè)置請求頭的Accept-Encoding信息,默認(rèn)值{gzip, deflate}。

  • FeignContextGzipEncodingInterceptor:壓縮格式攔截器,該攔截器會(huì)判斷請求頭中Context-Length屬性的值,是否大于請求內(nèi)容的最大長度,如果超過最大長度2048,則設(shè)置請求頭的Context-Encoding信息,默認(rèn)值{gzip, deflate}。注意,這里的2048是可以設(shè)置的,可以在配置文件中進(jìn)行配置:

feign.compression.request.enabled=true
feign.compression.request.min-request-size=2048

min-request-size是通過FeignClientEncodingProperties來解析的,默認(rèn)值是2048。

我們還可以自定義請求攔截器,我們自定義的攔截器,也會(huì)在此時(shí)進(jìn)行調(diào)用,所有實(shí)現(xiàn)了RequestTemplate接口的類,都會(huì)在這里被調(diào)用。比如我們可以自定義攔截器把全局事務(wù)id放在請求頭里。

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

feign.Request由5部分組成:

  • method

  • url

  • headers

  • body

  • charset

http請求客戶端

Feign發(fā)送http請求支持下面幾種http客戶端:

  • JDK自帶的HttpUrlConnection

  • Apache HttpClient

  • OkHttpClient

// 具體實(shí)現(xiàn)有2個(gè)類Client.Default 和LoadBalancerFeignClient

response = this.client.execute(request, this.options);

Client接口定義了execute()的接口,并且通過接口內(nèi)部類實(shí)現(xiàn)了Client.execute()。

HttpURLConnection connection = this.convertAndSend(request, options);

return this.convertResponse(connection).toBuilder(). request(request).build();

  • 這里的Options定義了2個(gè)參數(shù):

    • connectTimeoutMillis:連接超時(shí)時(shí)間,默認(rèn)10秒。

    • readTimeoutMillis:讀取數(shù)據(jù)超時(shí)時(shí)間,默認(rèn)60秒。

這種方式是最簡單的實(shí)現(xiàn),但是不支持負(fù)載均衡,Spring Cloud整合了Feign和Ribbon,所以自然會(huì)把Feign和Ribbon結(jié)合起來使用。也就是說,F(xiàn)eign發(fā)送請求前,會(huì)先把請求再經(jīng)過一層包裝,包裝成RibbonRequest。

也就是發(fā)送請求的另一種實(shí)現(xiàn)LoadBalancerFeignClient。

// 把Request包裝成RibbonRequest
RibbonRequest ribbonRequest = new   (this.delegate, request, uriWithoutHost);
// 配置超時(shí)時(shí)間
IClientConfig requestConfig = this.getClientConfig(options, clientName);
// 以負(fù)載均衡的方式發(fā)送請求
return ((RibbonResponse)this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
以負(fù)載均衡的方式發(fā)送請求
  • this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig))的具體實(shí)現(xiàn)在AbstractLoadBalancerAwareClient類中。

  • executeWithLoaderBalancer()方法的實(shí)現(xiàn)也參考了響應(yīng)式編程,通過LoadBalancerCommand提交請求,然后使用Observable接收響應(yīng)信息。

AbstractLoadBalancerAwareClient類的executeWithLoadBalancer()方法的第54行:

Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));

AbstractLoadBalancerAwareClient實(shí)現(xiàn)了IClient接口,該接口定義了execute()方法,

  • AbstractLoadBalancerAwareClient.this.execute()的具體實(shí)現(xiàn)有很多種:

    • OkHttpLoadBalancingClient

    • RetryableOkHttpLoadBalancingClient

    • RibbonLoadBalancingHttpClient

    • RetryableRibbonLoadBalancingHttpClient

我們以RibbonLoadBalancingHttpClient為例來說明,RibbonLoadBalancingHttpClient.execute()

第62行代碼:

// 組裝HttpUriRequest

HttpUriRequest httpUriRequest = request.toRequest(requestConfig);

// 發(fā)送http請求

HttpResponse httpResponse = ((HttpClient)this.delegate).execute(httpUriRequest);

// 使用RibbonApacheHttpResponse包裝http響應(yīng)信息

return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());

RibbonApacheHttpResponse由2部分組成:

httpResponse

uri

處理http相應(yīng)

http請求經(jīng)過上面一系列的轉(zhuǎn)發(fā)以后,最終還會(huì)回到SynchronousMethodHandler,然后SynchronousMethodHandler會(huì)進(jìn)行一系列的處理,然后響應(yīng)到瀏覽器。

  • 注冊Feign客戶端bean到IOC容器

  • 查看Feign框架源代碼,我們可以發(fā)現(xiàn),F(xiàn)eignClientsRegistar的registerFeignClients()方法完成了feign相關(guān)bean的注冊。

Feign架構(gòu)圖

新建位圖圖像.jpg
  • 第一步:基于JDK動(dòng)態(tài)代理生成代理類。

  • 第二步:根據(jù)接口類的注解聲明規(guī)則,解析出底層MethodHandler

  • 第三步:基于RequestBean動(dòng)態(tài)生成request。

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

  • 第五步:攔截器負(fù)責(zé)對(duì)請求和返回進(jìn)行裝飾處理。

  • 第六步:日志記錄。

  • 第七步:基于重試器發(fā)送http請求,支持不同的http框架,默認(rèn)使用的是HttpUrlConnection。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容