本文討論是建立在Spring Cloud整合Feign的基礎上沈堡。
Spring Boot的版本是1.4.5
feign-core的版本是9.3.1
什么是feign:一款基于注解和動態(tài)代理的聲明式restful http客戶端呜象。
一医瘫,F(xiàn)eign發(fā)送請求實現(xiàn)原理
微服務啟動類上標記@EnableFeignClients注解,然后Feign接口上標記@FeignClient注解欠痴。@FeignClient注解有幾個參數(shù)需要配置尉桩,這里不再贅述侈询,都很簡單肌幽。
Feign框架會掃描注解晚碾,然后通過Feign類來處理注解,并最終生成一個Feign對象喂急。
1格嘁,解析@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)建每個方法對應的MethodHandler,這個MethodHandler最終會被代理對象調(diào)用苗膝。最終MethodHandler都會保存到下面這個集合中殃恒,然后返回。
Map<String, MethodHandler> result = new LinkedHashMap();
2辱揭,解析完成以后离唐,調(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)代理的具體實現(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()的具體實現(xiàn)在FeignInvocationHandler.invoke(),F(xiàn)eignInvocationHandler也是ReflectiveFeign的一個內(nèi)部類较剃。里面有很多細節(jié)處理這里不再贅述咕别,我們直接進入核心那一行代碼,以免影響思路写穴,我們是理Feign的實現(xiàn)原理的惰拱!不要在意這些細節(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)舉赞枕!
3,MethodHandler.invoke()的具體實現(xiàn):SynchronousMethodHandler.invoke()
到了這里坪创,就是發(fā)送請求的邏輯了炕婶。發(fā)送請求前,首先要創(chuàng)建請求模板莱预,然后調(diào)用請求攔截器RequestInterceptor進行請求處理柠掂。
// 創(chuàng)建RequestTemplate
RequestTemplate template = this.buildTemlpateFromArgs.create(argv);
// 創(chuàng)建feign重試器,進行失敗重試
Retryer retryer = this.retryer.clone();
while(true){
? ? try{
? ? ? ? // 發(fā)送請求
? ? ? ? return this.executeAndDecode(template);
? ? } catch(RetryableException var5) {
? ? ? ? // 失敗重試锁施,最多重試5次
? ? ? ? retryer.continueOrPropagate();
? ? }
}
4陪踩,RequestTemplate處理
RequestTemplate模板需要經(jīng)過一系列攔截器的處理杖们,主要有以下攔截器:
BasicAuthRequestInterceptor:授權攔截器悉抵,主要是設置請求頭的Authorization信息,這里是base64轉(zhuǎn)碼后的用戶名和密碼摘完。
FeignAcceptGzipEncodingInterceptor:編碼類型攔截器姥饰,主要是設置請求頭的Accept-Encoding信息,默認值{gzip, deflate}孝治。
FeignContextGzipEncodingInterceptor:壓縮格式攔截器列粪,該攔截器會判斷請求頭中Context-Length屬性的值,是否大于請求內(nèi)容的最大長度谈飒,如果超過最大長度2048岂座,則設置請求頭的Context-Encoding信息,默認值{gzip, deflate}杭措。注意费什,這里的2048是可以設置的,可以在配置文件中進行配置:
feign.compression.request.enabled=true
feign.compression.request.min-request-size=2048
min-request-size是通過FeignClientEncodingProperties來解析的手素,默認值是2048鸳址。
我們還可以自定義請求攔截器,我們自定義的攔截器泉懦,也會在此時進行調(diào)用稿黍,所有實現(xiàn)了RequestTemplate接口的類,都會在這里被調(diào)用崩哩。比如我們可以自定義攔截器把全局事務id放在請求頭里巡球。
5言沐,使用feign.Request把RequestTemplate包裝成feign.Request
feign.Request由5部分組成:
method
url
headers
body
charset
6,http請求客戶端
Feign發(fā)送http請求支持下面幾種http客戶端:
JDK自帶的HttpUrlConnection
Apache HttpClient
OkHttpClient
// 具體實現(xiàn)有2個類Client.Default 和LoadBalancerFeignClient
response = this.client.execute(request, this.options);
Client接口定義了execute()的接口酣栈,并且通過接口內(nèi)部類實現(xiàn)了Client.execute()呢灶。
HttpURLConnection connection = this.convertAndSend(request, options);
return this.convertResponse(connection).toBuilder(). request(request).build();
這里的Options定義了2個參數(shù):
connectTimeoutMillis:連接超時時間,默認10秒钉嘹。
readTimeoutMillis:讀取數(shù)據(jù)超時時間鸯乃,默認60秒。
這種方式是最簡單的實現(xiàn)跋涣,但是不支持負載均衡缨睡,Spring Cloud整合了Feign和Ribbon,所以自然會把Feign和Ribbon結合起來使用陈辱。也就是說奖年,F(xiàn)eign發(fā)送請求前,會先把請求再經(jīng)過一層包裝沛贪,包裝成RibbonRequest陋守。
也就是發(fā)送請求的另一種實現(xiàn)LoadBalancerFeignClient。
// 把Request包裝成RibbonRequest
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
// 配置超時時間
IClientConfig requestConfig = this.getClientConfig(options, clientName);
// 以負載均衡的方式發(fā)送請求
return ((RibbonResponse)this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
7利赋,以負載均衡的方式發(fā)送請求
this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig))的具體實現(xiàn)在AbstractLoadBalancerAwareClient類中水评。
executeWithLoaderBalancer()方法的實現(xiàn)也參考了響應式編程,通過LoadBalancerCommand提交請求媚送,然后使用Observable接收響應信息中燥。
響應式編程這里不做過多討論,我也不是很清楚塘偎,暫且不說疗涉。我們回到主題,看關鍵代碼吟秩,AbstractLoadBalancerAwareClient類的executeWithLoadBalancer()方法的第54行:
Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
AbstractLoadBalancerAwareClient實現(xiàn)了IClient接口咱扣,該接口定義了execute()方法,AbstractLoadBalancerAwareClient.this.execute()的具體實現(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響應信息
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
RibbonApacheHttpResponse由2部分組成:
httpResponse
uri
OK闹伪,總算見到了我們熟悉的HttpClient發(fā)送請求的邏輯了,接下來就是執(zhí)行HttpClient的請求了武学!Feign的實現(xiàn)原理到這里可以告一段落祭往,不熟悉HttpClient可以接著往下看。
二火窒,處理http相應
http請求經(jīng)過上面一系列的轉(zhuǎn)發(fā)以后硼补,最終還會回到SynchronousMethodHandler,然后SynchronousMethodHandler會進行一系列的處理熏矿,然后響應到瀏覽器已骇。
三离钝,注冊Feign客戶端bean到IOC容器
查看Feign框架源代碼,我們可以發(fā)現(xiàn)褪储,F(xiàn)eignClientsRegistar的registerFeignClients()方法完成了feign相關bean的注冊卵渴。
四,F(xiàn)eign架構圖
第一步:基于JDK動態(tài)代理生成代理類鲤竹。
第二步:根據(jù)接口類的注解聲明規(guī)則浪读,解析出底層MethodHandler
第三步:基于RequestBean動態(tài)生成request。
第四步:Encoder將bean包裝成請求辛藻。
第五步:攔截器負責對請求和返回進行裝飾處理碘橘。
第六步:日志記錄。
第七步:基于重試器發(fā)送http請求吱肌,支持不同的http框架痘拆,默認使用的是HttpUrlConnection。