關(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