Spring Cloud Openfeign 源碼筆記

關(guān)鍵類(lèi)分析

# 1.FeignAutoConfiguration
  配置了一個(gè)管理 feign 子容器的工廠(FeignContext). 
  配置一個(gè) Targeter, 直接中專(zhuān) fegin 的 target 方法(DefaultTargeter, 這里擴(kuò)展可以實(shí)現(xiàn)降級(jí)哦)
  配置了一個(gè) feign client (ApacheHttpClient), 用于執(zhí)行 HTTP 請(qǐng)求
  還配備了 ok http client 方式的 feign client, 但默認(rèn)不啟用

# 2.FeignClientsRegistrar
  被 @EnableFeignClients 引入
  掃描帶 @FeignClient 注解的接口, 生成代理對(duì)象(FeignClientFactoryBean)注冊(cè)到容器中

# 3.FeignClientFactoryBean
  繼承自 FactoryBean, Spring 的東西, getBean() 時(shí)調(diào)用跳轉(zhuǎn)到 getObject()
  getObject() 會(huì)調(diào)用通過(guò) feign 對(duì)象生成代理對(duì)象

# 4.FeignInvocationHandler
    JDK 動(dòng)態(tài)代理生成對(duì)象的的方法攔截器
    通過(guò)調(diào)用 SynchronousMethodHandler 的 invoke() 實(shí)現(xiàn)發(fā)送請(qǐng)求的功能

# 5.SynchronousMethodHandler
    invoke() 會(huì)調(diào)用 FeignBlockingLoadBalancerClient 的 execute() 通過(guò)負(fù)載均衡獲取 url 再調(diào)用 ApacheHttpClient 的 execute() 發(fā)送帶實(shí)際 url 的 HTTP 請(qǐng)求.

# 6.ParseHandlersByName
    具有核心方法 apply, 解析 @FeignClient 接口的所有方法的注解和參數(shù)信息, 轉(zhuǎn)化為 RequestTemplate, 可用于構(gòu)造 HTTP 請(qǐng)求對(duì)象. 轉(zhuǎn)化后的信息存于 SynchronousMethodHandler 字段中.

# 7.FeignBlockingLoadBalancerClient
    execute() 會(huì)調(diào)用 LoadBalancerClient 的 choose() 根據(jù) serviceId(即 HTTP 的 host) 獲取 url.
    還負(fù)責(zé)調(diào)用 ApacheHttpClient 的 execute() 真正的發(fā)送 HTTP 請(qǐng)求.

# 8.LoadBalancerClient
  commons 下 loadbalancer 項(xiàng)目的老伙計(jì)了... 干啥的來(lái)著? 忘了

# 9.ApacheHttpClient
    負(fù)責(zé)發(fā)送 HTTP 請(qǐng)求.
    

openfeign 原理(@EnableFeignClients 生效步驟)

1.先解析 @EnableFeignClients 導(dǎo)入 FeignClientsRegistrar.class
2.FeignClientsRegistrar 將掃描帶 @FeignClient 注解的接口, 注冊(cè)到容器中
3.注冊(cè)進(jìn)容器的是一個(gè) FeignClientFactoryBean
4.FeignClientFactoryBean 其本質(zhì)是一個(gè) FactoryBean, 會(huì)在被 getBean() 時(shí)調(diào)用 getObject()
5.getObject() 會(huì)從容器中取 Feign.Builder builder 對(duì)象再根據(jù)配置文件進(jìn)行配置 
6.接著會(huì)走 Targeter 對(duì)象(默認(rèn)為 DefaultTargeter)的 target(), 其默認(rèn)為 builder.target(target); 其中 builder 為 Feign.Builder 對(duì)象, target 為 type(class), name(serviceId), url(http://name/path)
7.target() 會(huì)先調(diào)用 build()生成一個(gè) feign 對(duì)象, 再調(diào)用 feign.newInstance(target) 生成一個(gè)代理對(duì)象
8.先看 build() 方法, 初始化了重要屬性 targetToHandlersByName, 值為 new ParseHandlersByName(), 這個(gè)類(lèi)的 apply() 會(huì)在過(guò)一會(huì)用到. apply() 邏輯是遍歷挨個(gè)解析方法上的注解(如@RequestMapping, @RequestParam, @PathVariable等等)和參數(shù), 轉(zhuǎn)化后包裝成 HTTP 請(qǐng)求相關(guān)的實(shí)體類(lèi), 存到 SynchronousMethodHandler 的字段中, 再返回 SynchronousMethodHandler 對(duì)象存到 map 里.
9.再看 feign.newInstance(), 先調(diào)用剛說(shuō)的重要屬性 targetToHandlersByName.apply(), 獲得一個(gè) map, 里面鍵大致為類(lèi)名+方法名+形參組成, 值是 SynchronousMethodHandler 對(duì)象(見(jiàn) SynchronousMethodHandler.Factory.create()), 然后遍歷代理類(lèi)的所有方法, 將方法所對(duì)應(yīng)的 SynchronousMethodHandler 從 map 中取出再存到另一個(gè) map, 這個(gè) map 的鍵則為 method; 接著使用 JDK 動(dòng)態(tài)代理生成一個(gè)代理對(duì)象, 其用于攔截方法的類(lèi) InvocationHandler 具體為 FeignInvocationHandler, 這個(gè)類(lèi)里面的 invoke 邏輯很簡(jiǎn)單, 先排除 Object 通用的方法的影響, 對(duì)于正常方法會(huì)調(diào)用 SynchronousMethodHandler 的 invoke()... 鬧了半天就是個(gè)中轉(zhuǎn)唄, 有類(lèi)的 Handler 到了方法的 MethodHandler.
10.SynchronousMethodHandler 的 invoke() 的核心代碼是 this.client.execute(), 這里會(huì)走到 feign.Client 的具體實(shí)現(xiàn)類(lèi). 可能是 ApacheHttpClient(當(dāng) url 有地址時(shí)), 那就直接執(zhí)行請(qǐng)求了, 也可能是 FeignBlockingLoadBalancerClient, 那么會(huì)調(diào)用 loadBalancerClient 的 choose 方法獲取 url, 再執(zhí)行 feignClient.execute(), 這次這個(gè) feignClient 則肯定是 ApacheHttpClient 了, 于是最終就這樣發(fā)送了請(qǐng)求.

#PS: 
有人問(wèn)我(無(wú)中生友)發(fā)送的 HTTP 請(qǐng)求怎么構(gòu)建? 
其實(shí)我上面說(shuō)的很清楚了, 關(guān)鍵就在于 apply(), 好吧, 我說(shuō)的具體一點(diǎn)... 關(guān)鍵在于 SpringMvcContract#parseAndValidateMetadata()
這個(gè)的入口在 apply() 的第一行, this.contract.parseAndValidateMetadata() 一直往里跳轉(zhuǎn), 就會(huì)進(jìn)入 SpringMvcContract...  具體代碼挺多的... 真的沒(méi)必要細(xì)讀... 因?yàn)槠浔旧碇幌喈?dāng)于一個(gè)工具類(lèi), 而讀源碼應(yīng)該放眼大局, 否則每段代碼都認(rèn)真讀, 那太難了!!!

總結(jié): 掃描 @FeignClient, 生成代理對(duì)象, 扔進(jìn)容器. 攔截代理對(duì)象的方法, 調(diào)用方法對(duì)應(yīng)的 SynchronousMethodHandler, 此類(lèi)調(diào)用之前解析好的 RequestTemplate 生成請(qǐng)求對(duì)象, 再通過(guò) Client 執(zhí)行請(qǐng)求, 若配置了負(fù)載均衡, 則會(huì)調(diào)用 LoadBalancerClient 將 serviceId 先解析成具體的地址再轉(zhuǎn)交給 ApacheHttpClient 執(zhí)行請(qǐng)求.

代理對(duì)象的生成步驟

graph TB

A1(FeignClientFactoryBean)
A2(Feign.Builder)
A3(Feign)
A4(FeignInvocationHandler)
A5(SynchronousMethodHandler)
A6(FeignBlockingLoadBalancerClient)
A7(ApacheHttpClient)
A8(LoadBalancerClient)
A9(ServiceInstance)
A10(Response)
A11(Decoder 鏈)

A1--getObject 觸發(fā)-->A2
A2--build 得到-->A3
A3--Proxy.newProxyInstance 用到-->A4
A4--invoke 里調(diào)用-->A5
A5--invoke 里調(diào)用-->A6
A6--execute 中先調(diào)用 -->A8
A6--execute 中后調(diào)用-->A7
A8--獲取-->A9
A7--得到-->A10
A10--交給-->A11
A11--編碼得到-->A12(json)


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

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,586評(píng)論 19 139
  • 一、@EnableFeignClients詳解 1、作用 掃描和注冊(cè)所有使用注解@FeignClient定義的fe...
    楊健kimyeung閱讀 3,102評(píng)論 1 0
  • 一、概要 GitHub 我們通過(guò)RestTemplate調(diào)用其它服務(wù)的API時(shí),所需要的參數(shù)須在請(qǐng)求的URL中進(jìn)行...
    楊健kimyeung閱讀 271評(píng)論 0 0
  • 在Spring Cloud體系中,F(xiàn)eign是封裝了底層的HttpClient組件來(lái)做的一次遠(yuǎn)程的接口調(diào)用...
    xjz1842閱讀 1,568評(píng)論 0 2
  • Feign簡(jiǎn)介 ??在Feign的官方文檔上, 我們可以看到Feign最重要的一句話是:Feign makes w...
    良辰美景TT閱讀 1,042評(píng)論 0 2

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