FeignClientFactoryBean創(chuàng)建動態(tài)代理詳細(xì)解讀
FeignClientFactoryBean創(chuàng)建動態(tài)代理
探索FeignClient的注冊流程
當(dāng)直接進(jìn)去注冊的方法中,一步步放下走,都是直接放bean的定義信息中放入值,然后轉(zhuǎn)成BeanDefinitionHolder,最后在注冊到IOC容器中。 具體的信息可以看下面斷點的圖。
在仔細(xì)看一下就會發(fā)現(xiàn)很奇怪,為什么此處傳入的是FeignClientFactoryBean,然后把feignclient的信息放它的里面,那我們就進(jìn)去看看。
那我們就進(jìn)行看一下FeignClientFactoryBean,里面的內(nèi)容
首先發(fā)現(xiàn)它實現(xiàn)了FactoryBean接口,可以返回bean的實例的工廠bean,通過實現(xiàn)該接口可以對bean進(jìn)行一些額外的操作。此處肯定重寫了getObject方法,就是在此處,這個方法可以往容器中注入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容器中獲取了?個FeignContext組件,應(yīng)該是Feign存放?具類的?個上下?組件,然后從FeignContext中獲取到了FeignLoggerFactory組件,?路追進(jìn)去發(fā)現(xiàn),原來在底層也是維護(hù)了?個Spring容器的緩存Map<String, AnnotationConfigApplicationContext>。Feign在執(zhí)?時涉及到?系列的組件,所以Feign?脆為每個服務(wù)創(chuàng)建了?個Spring容器類ApplicationContext,?來存放每個服務(wù)各?所需要的組件,每個服務(wù)的這些?具類、組件都是互不影 響的,所以我們看到它在底層是通過?個Map來緩存的,key為服務(wù)名,value為服務(wù)所對應(yīng)的的spring 容器ApplicationContext。
接下來我們陸續(xù)看到了其他的組件如:Decoder、Encoder、Contract等都被創(chuàng)建了出來,都設(shè)置到了Feign.Builder組件中,看來Feign.Builder應(yīng)該是要為創(chuàng)建對象設(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)在同包下面還有一個FeignClientsConfiguration配置類,那就點進(jìn)去看看。
Decoder、Encoder、Contract、FeignLoggerFactory的實際類型都很順利的找到了。 因為Feign最終還是要發(fā)送HTTP請求,這?就難免要涉及到序列化和反序列化、這個過程就會牽扯到編碼和解碼的過程,Encoder和Decoder就派上?場了。 因為Feign?帶的注解@FeignClient、以及SpringMVC注解它們是被誰處理的呢?Contract的實現(xiàn)類SpringMVCContract就是來解析它們的,解析所有的注解信息、然后拼湊成?個完整的HTTP請求所需要的信息。
在最后一行,有一個這個方法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)先級是要?默認(rèn)配置要?的,所以也會覆蓋默認(rèn)的配置信息。
分析到這?,F(xiàn)eign要創(chuàng)建?個對象的所需要的信息都設(shè)置的差不多了,簡單來說就四?部分:
(1)默認(rèn)的組件
(2)?定義組件
(3)默認(rèn)的配置信息
(4)指定服務(wù)的配置信息
創(chuàng)建Feign的動態(tài)代理對象
然后我們就繼續(xù)放行,最終得到了Feign.Builder對象,然后我們繼續(xù)放行,來到了截圖的哪行,這里會返回一個對象。
點進(jìn)去,我們繼續(xù)分析,第一行我們直接過,但是看idea的提示類型是LoadBalancerFeignClient,LoadBalancer是ribbon的三大核心組件之一,應(yīng)該是和reibbon負(fù)載均衡有關(guān)系。
那我們就找一下它是在那個配置類被創(chuàng)建的。
接下來,我們看到它直接從容器中獲取了?個Targeter對象,這個對象是HystrixTargeter對象,在FeignAutoConfiguration 中找到、然后直接調(diào)?target?法,如下圖所示:
那我們就來到它的這個方法中,繼續(xù)放下放行:
?路順勢跟到??后發(fā)現(xiàn),target?法中、調(diào)?了build?法,??創(chuàng)建了SynchronousMethodHandler.Factory,翻譯起來也就是同步?法處理器的??類,具體?什么?的、?前也不是好確定,反正只知道??就是?來創(chuàng)建對象?的,然后它把這個??類放到了ParseHandlersByName中,并且new上了?個ReflectiveFeign對象并返回了。
緊接著調(diào)?了newInstance?法,看樣?是要創(chuàng)建?個對象,繼續(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)建了動態(tài)代理。因為前面解析@FeignClient注解,并且封裝了一個Bean的定義信息注冊中Ioc容器中,比較ServiceAClient畢竟是一個接口,所以給每個接口創(chuàng)建了一個動態(tài)代理,然后調(diào)用接口時,去讓動態(tài)代理來執(zhí)行
一進(jìn)入到這個方法中,會看到有2個Map和一個List,那我們就先看一下第一個map,里面的數(shù)據(jù)如下圖:
當(dāng)我們點進(jìn)apply方法的時候,進(jìn)?到apply?法中、發(fā)現(xiàn)contract通過調(diào)?parseAndValidateMetadata?法得到了、List 、也就是接?中的所有?法的元數(shù)據(jù)信息,然后遍歷這些?法,通過factory創(chuàng)建MethodHandler,?這?的 factory就是我們前?在build?法中看到的SynchronousMethodHandler.Factory 。
后面的話,會遍歷所有的nameToHandler,把它轉(zhuǎn)成methodToHandler。
最終來到了這行InvocationHandler handler = factory.create(target, methodToHandler);把上面獲取到的方法和要代理的對象都放到了FeignInvocationHandler中。
最終創(chuàng)建出了代理對象。
到此這篇關(guān)于FeignClientFactoryBean創(chuàng)建動態(tài)代理詳細(xì)解讀的文章就介紹到這了,更多相關(guān)FeignClientFactoryBean動態(tài)代理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中property-placeholder的使用與解析詳解
本篇文章主要介紹了Spring中property-placeholder的使用與解析詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05springboot開啟mybatis駝峰命名自動映射的三種方式
這篇文章給大家總結(jié)springboot開啟mybatis駝峰命名自動映射的三種方式,文章并通過代碼示例給大家介紹的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2024-02-02Java數(shù)據(jù)結(jié)構(gòu)之有向圖設(shè)計與實現(xiàn)詳解
有向圖是具有方向性的圖,由一組頂點和一組有方向的邊組成,每條方向的邊都連著一對有序的頂點。本文為大家介紹的是有向圖的設(shè)計與實現(xiàn),需要的可以參考一下2022-11-11spring-cloud-gateway動態(tài)路由的實現(xiàn)方法
這篇文章主要介紹了spring-cloud-gateway動態(tài)路由的實現(xiàn)方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01