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