FeignClientFactoryBean创建动态代理详细解读

 更新时间:2023年11月21日 09:12:20   作者:dalianpai  
这篇文章主要介绍了FeignClientFactoryBean创建动态代理详细解读,当直接进去注册的方法中,一步步放下走,都是直接放bean的定义信息中放入值,然后转成BeanDefinitionHolder,最后在注册到IOC容器中,需要的朋友可以参考下

FeignClientFactoryBean创建动态代理

探索FeignClient的注册流程

当直接进去注册的方法中,一步步放下走,都是直接放bean的定义信息中放入值,然后转成BeanDefinitionHolder,最后在注册到IOC容器中。 具体的信息可以看下面断点的图。

image-20211019140122997

image-20211019140213072

在仔细看一下就会发现很奇怪,为什么此处传入的是FeignClientFactoryBean,然后把feignclient的信息放它的里面,那我们就进去看看。

那我们就进行看一下FeignClientFactoryBean,里面的内容

image-20211019140829389

首先发现它实现了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组件,⼀路追进去发现,原来在底层也是维护了⼀个Spring容器的缓存Map<String, AnnotationConfigApplicationContext>。Feign在执⾏时涉及到⼀系列的组件,所以Feign⼲脆为每个服务创建了⼀个Spring容器类ApplicationContext,⽤来存放每个服务各⾃所需要的组件,每个服务的这些⼯具类、组件都是互不影 响的,所以我们看到它在底层是通过⼀个Map来缓存的,key为服务名,value为服务所对应的的spring 容器ApplicationContext。

接下来我们陆续看到了其他的组件如:Decoder、Encoder、Contract等都被创建了出来,都设置到了Feign.Builder组件中,看来Feign.Builder应该是要为创建对象设置⼀些必要的组件信息。

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;
	}

但是发现FeignContext、FeignLoggerFactory还是Encoder、Decoder、Contract,他们都是接⼝

image-20211019150645159

发现在同包下面还有一个FeignClientsConfiguration配置类,那就点进去看看。

image-20211019150847945

image-20211019151005982

Decoder、Encoder、Contract、FeignLoggerFactory的实际类型都很顺利的找到了。 因为Feign最终还是要发送HTTP请求,这⾥就难免要涉及到序列化和反序列化、这个过程就会牵扯到编码和解码的过程,Encoder和Decoder就派上⽤场了。 因为Feign⾃带的注解@FeignClient、以及SpringMVC注解它们是被谁处理的呢?Contract的实现类SpringMVCContract就是来解析它们的,解析所有的注解信息、然后拼凑成⼀个完整的HTTP请求所需要的信息。

image-20211019144245884

在最后一行,有一个这个方法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);
		}
	}

下图为配置类中的信息

image-20211020101245947

下面为指定服务的yml中的信息

image-20211020101338558

image-20211020101454713

服务的配置信息优先级是要⽐默认配置要⾼的,所以也会覆盖默认的配置信息。

分析到这⾥,Feign要创建⼀个对象的所需要的信息都设置的差不多了,简单来说就四⼤部分:

(1)默认的组件

(2)⾃定义组件

(3)默认的配置信息

(4)指定服务的配置信息

创建Feign的动态代理对象

然后我们就继续放行,最终得到了Feign.Builder对象,然后我们继续放行,来到了截图的哪行,这里会返回一个对象。

image-20211019144325708

点进去,我们继续分析,第一行我们直接过,但是看idea的提示类型是LoadBalancerFeignClient,LoadBalancer是ribbon的三大核心组件之一,应该是和reibbon负载均衡有关系。

image-20211019144547474

那我们就找一下它是在那个配置类被创建的。

image-20211020104444844

接下来,我们看到它直接从容器中获取了⼀个Targeter对象,这个对象是HystrixTargeter对象,在FeignAutoConfiguration 中找到、然后直接调⽤target⽅法,如下图所示:

image-20211020102344252

那我们就来到它的这个方法中,继续放下放行:

image-20211020104727702

⼀路顺势跟到⾥⾯后发现,target⽅法中、调⽤了build⽅法,⾥⾯创建了SynchronousMethodHandler.Factory,翻译起来也就是同步⽅法处理器的⼯⼚类,具体⼲什么⽤的、⽬前也不是好确定,反正只知道⼯⼚就是⽤来创建对象⽤的,然后它把这个⼯⼚类放到了ParseHandlersByName中,并且new上了⼀个ReflectiveFeign对象并返回了。

紧接着调⽤了newInstance⽅法,看样⼦是要创建⼀个对象,继续跟进看下:

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);这行,基本就可以确定,是创建了动态代理。因为前面解析@FeignClient注解,并且封装了一个Bean的定义信息注册中Ioc容器中,比较ServiceAClient毕竟是一个接口,所以给每个接口创建了一个动态代理,然后调用接口时,去让动态代理来执行

image-20211019155531287

一进入到这个方法中,会看到有2个Map和一个List,那我们就先看一下第一个map,里面的数据如下图:

image-20211019160113229

当我们点进apply方法的时候,进⼊到apply⽅法中、发现contract通过调⽤parseAndValidateMetadata⽅法得到了、List 、也就是接⼝中的所有⽅法的元数据信息,然后遍历这些⽅法,通过factory创建MethodHandler,⽽这⾥的 factory就是我们前⾯在build⽅法中看到的SynchronousMethodHandler.Factory 。

image-20211019161015024

后面的话,会遍历所有的nameToHandler,把它转成methodToHandler。image-20211020110050039

最终来到了这行InvocationHandler handler = factory.create(target, methodToHandler);把上面获取到的方法和要代理的对象都放到了FeignInvocationHandler中。

最终创建出了代理对象。

到此这篇关于FeignClientFactoryBean创建动态代理详细解读的文章就介绍到这了,更多相关FeignClientFactoryBean动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java实现页面多查询条件必选的统一处理思路

    java实现页面多查询条件必选的统一处理思路

    这篇文章主要为大家介绍了java实现页面多查询条件必选的统一处理思路详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • Spring Boot中利用JavaMailSender发送邮件的方法示例(附源码)

    Spring Boot中利用JavaMailSender发送邮件的方法示例(附源码)

    这篇文章主要介绍了Spring Boot中利用JavaMailSender发送邮件的方法示例, 相信使用过Spring的众多开发者都知道Spring提供了非常好用的JavaMailSender接口实现邮件发送。在Spring Boot的Starter模块中也为此提供了自动化配置。需要的朋友可以参考借鉴。
    2017-02-02
  • Java之如何正确地对包装类进行装箱与拆箱

    Java之如何正确地对包装类进行装箱与拆箱

    在这篇文章中给大家继续讲解包装类的装箱和拆箱问题。你可能会很好奇,做java开发,怎么还装起箱子来了?那么就请大家带着疑惑往下看吧
    2023-04-04
  • Java后端接入微信小程序实现登录功能

    Java后端接入微信小程序实现登录功能

    这篇文章主要介绍了Java如何在后端接入微信小程序从而实现登录功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-06-06
  • Java版的7种单例模式写法示例

    Java版的7种单例模式写法示例

    这篇文章主要给大家介绍了关于Java版的7种单例模式写法,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-10-10
  • Java8深入学习系列(二)函数式编程

    Java8深入学习系列(二)函数式编程

    函数式编程,这个词语由两个名词构成,函数,编程。编程这个词我就不用解释了,大家都是做这个的。函数,其实单独抽离出来这个词语,也并不陌生,那二者组合后的到底是什么呢,下面这篇文章主要给大家介绍了关于Java8函数式编程的相关资料,需要的朋友可以参考下。
    2017-08-08
  • 实例详解Java库中的LocalDate类

    实例详解Java库中的LocalDate类

    在做报表统计时,需要对指定时间内的数据做统计,则需要使用到时间日期API,下面这篇文章主要给大家介绍了关于Java库中LocalDate类的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-01-01
  • Java爬虫范例之使用Htmlunit爬取学校教务网课程表信息

    Java爬虫范例之使用Htmlunit爬取学校教务网课程表信息

    htmlunit 是一款开源的java 页面分析工具,读取页面后,可以有效的使用htmlunit分析页面上的内容。项目可以模拟浏览器运行,被誉为java浏览器的开源实现。今天我们用这款分析工具来爬取学校教务网课程表信息
    2021-11-11
  • 浅谈java中String与StringBuffer的不同

    浅谈java中String与StringBuffer的不同

    String在栈中,StringBuffer在堆中!所以String是不可变的,数据是共享的。StringBuffer都是独占的,是可变的(因为每次都是创建新的对象!)
    2015-11-11
  • Java加载ICC文件的方法和示例代码

    Java加载ICC文件的方法和示例代码

    ICC文件,通常用于颜色管理,定义了如何将一个颜色空间转换为另一个颜色空间,在Java中,我们可能需要加载这些文件来进行颜色转换或管理,本文将为您提供加载ICC文件的方法和示例代码,需要的朋友参考下吧
    2023-08-08

最新评论