Feign工作流程源碼解析

本文討論是建立在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架構圖


圖片發(fā)自簡書App

第一步:基于JDK動態(tài)代理生成代理類鲤竹。

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

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

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

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

第六步:日志記錄。

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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氮墨,一起剝皮案震驚了整個濱河市纺蛆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌规揪,老刑警劉巖桥氏,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異粒褒,居然都是意外死亡识颊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門奕坟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人清笨,你說我怎么就攤上這事月杉。” “怎么了抠艾?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵苛萎,是天一觀的道長。 經(jīng)常有香客問我检号,道長腌歉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任齐苛,我火速辦了婚禮翘盖,結果婚禮上,老公的妹妹穿的比我還像新娘凹蜂。我一直安慰自己馍驯,他們只是感情好阁危,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汰瘫,像睡著了一般狂打。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上混弥,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天趴乡,我揣著相機與錄音,去河邊找鬼蝗拿。 笑死浙宜,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蛹磺。 我是一名探鬼主播粟瞬,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萤捆!你這毒婦竟也來了裙品?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤俗或,失蹤者是張志新(化名)和其女友劉穎市怎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辛慰,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡区匠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帅腌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驰弄。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖速客,靈堂內(nèi)的尸體忽然破棺而出戚篙,到底是詐尸還是另有隱情,我是刑警寧澤溺职,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布岔擂,位于F島的核電站,受9級特大地震影響浪耘,放射性物質(zhì)發(fā)生泄漏乱灵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一七冲、第九天 我趴在偏房一處隱蔽的房頂上張望痛倚。 院中可真熱鬧,春花似錦癞埠、人聲如沸状原。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颠区。三九已至削锰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毕莱,已是汗流浹背器贩。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留朋截,地道東北人蛹稍。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像部服,于是被迫代替她去往敵國和親唆姐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容