Feign工作流程源碼解析
什么是feign:一款基于注解和動態(tài)代理的聲明式restful http客戶端窗骑。
原理
Feign發(fā)送請求實(shí)現(xiàn)原理
微服務(wù)啟動類上標(biāo)記@EnableFeignClients注解岛马,然后Feign接口上標(biāo)記@FeignClient注解默勾。@FeignClient注解有幾個參數(shù)需要配置鄙币,這里不再贅述税迷,都很簡單淤翔。
Feign框架會掃描注解蚂夕,然后通過Feign類來處理注解迅诬,并最終生成一個Feign對象。
解析@FeignClient注解婿牍,生成MethodHandler
具體的解析類是ParseHandlerByName侈贷。這個類是ReflectiveFeign的內(nèi)部類。
// 解析注解元數(shù)據(jù)等脂,使用Contract解析
List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(key.type());
拿到注解元數(shù)據(jù)以后俏蛮,循環(huán)處理注解元數(shù)據(jù),創(chuàng)建每個方法對應(yīng)的MethodHandler上遥,這個MethodHandler最終會被代理對象調(diào)用搏屑。最終MethodHandler都會保存到下面這個集合中,然后返回粉楚。
Map<String, MethodHandler> result = new LinkedHashMap();
解析完成以后辣恋,調(diào)用ReflectiveFeign.newInstance()生成代理類。
MethodHandler是feign的一個接口模软,這個接口的invoke方法伟骨,是動態(tài)代理調(diào)用者InvocationHandler的invoke()方法最終調(diào)用的方法。
重新表述一遍:InvocationHandler的invoke()方法最終回調(diào)MethodHandler的invoke()來發(fā)送http請求撵摆。這就是Feign動態(tài)代理的具體實(shí)現(xiàn)底靠。
ReflectiveFeign類的newInstance()方法的第57行:
// 創(chuà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的一個內(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:這是一個map,就是保存所有的MethodHandler的集合枚抵。參考創(chuàng)建InvocationHandler的位置:ReflectiveFeign類的newInstance()方法的第57行线欲。
this.dispatch.get(method):這里的method就是我們開發(fā)者寫的feign接口中定義的方法的方法名!這段代碼的意思就是從MethodHandler集合中拿到我們需要調(diào)用的那個方法汽摹。
this.dispatch.get(method). invoke(args):這里的invoke就是調(diào)用的MethodHandler.invoke()李丰!動態(tài)代理回調(diào)代理類,就這樣完成了逼泣,oh my god趴泌,多么偉大的創(chuàng)舉!
MethodHandler.invoke()的具體實(shí)現(xiàn):SynchronousMethodHandler.invoke()
到了這里拉庶,就是發(fā)送請求的邏輯了嗜憔。發(fā)送請求前,首先要創(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:壓縮格式攔截器搁进,該攔截器會判斷請求頭中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。
我們還可以自定義請求攔截器捐名,我們自定義的攔截器旦万,也會在此時進(jìn)行調(diào)用,所有實(shí)現(xiàn)了RequestTemplate接口的類镶蹋,都會在這里被調(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個類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個參數(shù):
connectTimeoutMillis:連接超時時間,默認(rèn)10秒拂酣。
readTimeoutMillis:讀取數(shù)據(jù)超時時間琼腔,默認(rèn)60秒。
這種方式是最簡單的實(shí)現(xiàn)踱葛,但是不支持負(fù)載均衡丹莲,Spring Cloud整合了Feign和Ribbon,所以自然會把Feign和Ribbon結(jié)合起來使用尸诽。也就是說甥材,F(xiàn)eign發(fā)送請求前,會先把請求再經(jīng)過一層包裝性含,包裝成RibbonRequest洲赵。
也就是發(fā)送請求的另一種實(shí)現(xiàn)LoadBalancerFeignClient。
// 把Request包裝成RibbonRequest
RibbonRequest ribbonRequest = new (this.delegate, request, uriWithoutHost);
// 配置超時時間
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ā)以后腹殿,最終還會回到SynchronousMethodHandler,然后SynchronousMethodHandler會進(jìn)行一系列的處理例书,然后響應(yīng)到瀏覽器锣尉。
注冊Feign客戶端bean到IOC容器
查看Feign框架源代碼,我們可以發(fā)現(xiàn)决采,F(xiàn)eignClientsRegistar的registerFeignClients()方法完成了feign相關(guān)bean的注冊自沧。
Feign架構(gòu)圖
第一步:基于JDK動態(tài)代理生成代理類。
第二步:根據(jù)接口類的注解聲明規(guī)則树瞭,解析出底層MethodHandler
第三步:基于RequestBean動態(tài)生成request拇厢。
第四步:Encoder將bean包裝成請求爱谁。
第五步:攔截器負(fù)責(zé)對請求和返回進(jìn)行裝飾處理。
第六步:日志記錄旺嬉。
第七步:基于重試器發(fā)送http請求管行,支持不同的http框架,默認(rèn)使用的是HttpUrlConnection邪媳。