FeignClientFactoryBean創(chuàng)建動(dòng)態(tài)代理詳細(xì)解讀
FeignClientFactoryBean創(chuàng)建動(dòng)態(tài)代理
探索FeignClient的注冊(cè)流程
當(dāng)直接進(jìn)去注冊(cè)的方法中,一步步放下走,都是直接放bean的定義信息中放入值,然后轉(zhuǎn)成BeanDefinitionHolder,最后在注冊(cè)到IOC容器中。 具體的信息可以看下面斷點(diǎn)的圖。
在仔細(xì)看一下就會(huì)發(fā)現(xiàn)很奇怪,為什么此處傳入的是FeignClientFactoryBean,然后把feignclient的信息放它的里面,那我們就進(jìn)去看看。
那我們就進(jìn)行看一下FeignClientFactoryBean,里面的內(nèi)容
首先發(fā)現(xiàn)它實(shí)現(xiàn)了FactoryBean接口,可以返回bean的實(shí)例的工廠bean,通過(guò)實(shí)現(xiàn)該接口可以對(duì)bean進(jìn)行一些額外的操作。此處肯定重寫了getObject方法,就是在此處,這個(gè)方法可以往容器中注入Bean的。
@Override public Object getObject() throws Exception { return getTarget(); } <T> T getTarget() { FeignContext context = this.applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { this.url = "http://" + this.name; } else { this.url = this.name; } this.url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); } if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient) client).getDelegate(); } if (client instanceof FeignBlockingLoadBalancerClient) { // not load balancing because we have a url, // but Spring Cloud LoadBalancer is on the classpath, so unwrap client = ((FeignBlockingLoadBalancerClient) client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url)); }
在getObject?法中,?先從applicationContext中、也就是從Spring容器中獲取了?個(gè)FeignContext組件,應(yīng)該是Feign存放?具類的?個(gè)上下?組件,然后從FeignContext中獲取到了FeignLoggerFactory組件,?路追進(jìn)去發(fā)現(xiàn),原來(lái)在底層也是維護(hù)了?個(gè)Spring容器的緩存Map<String, AnnotationConfigApplicationContext>。Feign在執(zhí)?時(shí)涉及到?系列的組件,所以Feign?脆為每個(gè)服務(wù)創(chuàng)建了?個(gè)Spring容器類ApplicationContext,?來(lái)存放每個(gè)服務(wù)各?所需要的組件,每個(gè)服務(wù)的這些?具類、組件都是互不影 響的,所以我們看到它在底層是通過(guò)?個(gè)Map來(lái)緩存的,key為服務(wù)名,value為服務(wù)所對(duì)應(yīng)的的spring 容器ApplicationContext。
接下來(lái)我們陸續(xù)看到了其他的組件如:Decoder、Encoder、Contract等都被創(chuàng)建了出來(lái),都設(shè)置到了Feign.Builder組件中,看來(lái)Feign.Builder應(yīng)該是要為創(chuàng)建對(duì)象設(shè)置?些必要的組件信息。
protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); // @formatter:off Feign.Builder builder = get(context, Feign.Builder.class) // required values .logger(logger) .encoder(get(context, Encoder.class)) .decoder(get(context, Decoder.class)) .contract(get(context, Contract.class)); // @formatter:on configureFeign(context, builder); return builder; }
但是發(fā)現(xiàn)FeignContext、FeignLoggerFactory還是Encoder、Decoder、Contract,他們都是接?
發(fā)現(xiàn)在同包下面還有一個(gè)FeignClientsConfiguration配置類,那就點(diǎn)進(jìn)去看看。
Decoder、Encoder、Contract、FeignLoggerFactory的實(shí)際類型都很順利的找到了。 因?yàn)镕eign最終還是要發(fā)送HTTP請(qǐng)求,這?就難免要涉及到序列化和反序列化、這個(gè)過(guò)程就會(huì)牽扯到編碼和解碼的過(guò)程,Encoder和Decoder就派上?場(chǎng)了。 因?yàn)镕eign?帶的注解@FeignClient、以及SpringMVC注解它們是被誰(shuí)處理的呢?Contract的實(shí)現(xiàn)類SpringMVCContract就是來(lái)解析它們的,解析所有的注解信息、然后拼湊成?個(gè)完整的HTTP請(qǐng)求所需要的信息。
在最后一行,有一個(gè)這個(gè)方法configureFeign(context, builder);,看方法名字就是配置feign,猜一猜應(yīng)該是把配置文件的一些配置綁定到feign的配置類上。
protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = this.applicationContext .getBean(FeignClientProperties.class); FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class); setInheritParentContext(feignClientConfigurer.inheritParentConfiguration()); if (properties != null && inheritParentContext) { if (properties.isDefaultToProperties()) { //這行默認(rèn)取的是配置文件的信息。 configureUsingConfiguration(context, builder); //這行取的是yml中的默認(rèn)配置 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); //這行是yml中指定服務(wù)的配置 configureUsingProperties(properties.getConfig().get(this.contextId), builder); } else { configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.contextId), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }
下圖為配置類中的信息
下面為指定服務(wù)的yml中的信息
服務(wù)的配置信息優(yōu)先級(jí)是要?默認(rèn)配置要?的,所以也會(huì)覆蓋默認(rèn)的配置信息。
分析到這?,F(xiàn)eign要?jiǎng)?chuàng)建?個(gè)對(duì)象的所需要的信息都設(shè)置的差不多了,簡(jiǎn)單來(lái)說(shuō)就四?部分:
(1)默認(rèn)的組件
(2)?定義組件
(3)默認(rèn)的配置信息
(4)指定服務(wù)的配置信息
創(chuàng)建Feign的動(dòng)態(tài)代理對(duì)象
然后我們就繼續(xù)放行,最終得到了Feign.Builder對(duì)象,然后我們繼續(xù)放行,來(lái)到了截圖的哪行,這里會(huì)返回一個(gè)對(duì)象。
點(diǎn)進(jìn)去,我們繼續(xù)分析,第一行我們直接過(guò),但是看idea的提示類型是LoadBalancerFeignClient,LoadBalancer是ribbon的三大核心組件之一,應(yīng)該是和reibbon負(fù)載均衡有關(guān)系。
那我們就找一下它是在那個(gè)配置類被創(chuàng)建的。
接下來(lái),我們看到它直接從容器中獲取了?個(gè)Targeter對(duì)象,這個(gè)對(duì)象是HystrixTargeter對(duì)象,在FeignAutoConfiguration 中找到、然后直接調(diào)?target?法,如下圖所示:
那我們就來(lái)到它的這個(gè)方法中,繼續(xù)放下放行:
?路順勢(shì)跟到??后發(fā)現(xiàn),target?法中、調(diào)?了build?法,??創(chuàng)建了SynchronousMethodHandler.Factory,翻譯起來(lái)也就是同步?法處理器的??類,具體?什么?的、?前也不是好確定,反正只知道??就是?來(lái)創(chuàng)建對(duì)象?的,然后它把這個(gè)??類放到了ParseHandlersByName中,并且new上了?個(gè)ReflectiveFeign對(duì)象并返回了。
緊接著調(diào)?了newInstance?法,看樣?是要?jiǎng)?chuàng)建?個(gè)對(duì)象,繼續(xù)跟進(jìn)看下:
public <T> T target(Target<T> target) { return build().newInstance(target); } @Override public <T> T newInstance(Target<T> target) { Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
看見 InvocationHandler handler = factory.create(target, methodToHandler);這行,基本就可以確定,是創(chuàng)建了動(dòng)態(tài)代理。因?yàn)榍懊娼馕鯜FeignClient注解,并且封裝了一個(gè)Bean的定義信息注冊(cè)中Ioc容器中,比較ServiceAClient畢竟是一個(gè)接口,所以給每個(gè)接口創(chuàng)建了一個(gè)動(dòng)態(tài)代理,然后調(diào)用接口時(shí),去讓動(dòng)態(tài)代理來(lái)執(zhí)行
一進(jìn)入到這個(gè)方法中,會(huì)看到有2個(gè)Map和一個(gè)List,那我們就先看一下第一個(gè)map,里面的數(shù)據(jù)如下圖:
當(dāng)我們點(diǎn)進(jìn)apply方法的時(shí)候,進(jìn)?到apply?法中、發(fā)現(xiàn)contract通過(guò)調(diào)?parseAndValidateMetadata?法得到了、List 、也就是接?中的所有?法的元數(shù)據(jù)信息,然后遍歷這些?法,通過(guò)factory創(chuàng)建MethodHandler,?這?的 factory就是我們前?在build?法中看到的SynchronousMethodHandler.Factory 。
后面的話,會(huì)遍歷所有的nameToHandler,把它轉(zhuǎn)成methodToHandler。
最終來(lái)到了這行InvocationHandler handler = factory.create(target, methodToHandler);把上面獲取到的方法和要代理的對(duì)象都放到了FeignInvocationHandler中。
最終創(chuàng)建出了代理對(duì)象。
到此這篇關(guān)于FeignClientFactoryBean創(chuàng)建動(dòng)態(tài)代理詳細(xì)解讀的文章就介紹到這了,更多相關(guān)FeignClientFactoryBean動(dòng)態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中property-placeholder的使用與解析詳解
本篇文章主要介紹了Spring中property-placeholder的使用與解析詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05springboot開啟mybatis駝峰命名自動(dòng)映射的三種方式
這篇文章給大家總結(jié)springboot開啟mybatis駝峰命名自動(dòng)映射的三種方式,文章并通過(guò)代碼示例給大家介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-02-02Java數(shù)據(jù)結(jié)構(gòu)之有向圖設(shè)計(jì)與實(shí)現(xiàn)詳解
有向圖是具有方向性的圖,由一組頂點(diǎn)和一組有方向的邊組成,每條方向的邊都連著一對(duì)有序的頂點(diǎn)。本文為大家介紹的是有向圖的設(shè)計(jì)與實(shí)現(xiàn),需要的可以參考一下2022-11-11drools的簡(jiǎn)單入門案例場(chǎng)景分析
drools是一款由JBoss組織提供的基于Java語(yǔ)言開發(fā)的開源規(guī)則引擎,可以將復(fù)雜且多變的業(yè)務(wù)規(guī)則從硬編碼中解放出來(lái),這篇文章主要介紹了drools的簡(jiǎn)單入門案例,需要的朋友可以參考下2022-05-05spring-cloud-gateway動(dòng)態(tài)路由的實(shí)現(xiàn)方法
這篇文章主要介紹了spring-cloud-gateway動(dòng)態(tài)路由的實(shí)現(xiàn)方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01