openFeign源码学习

news2024/12/25 2:23:30

openFeign这个框架要解决的问题是:通常在调用远程接口的时候,如果是http请求,需要我们通过restTemplate去拼接调用参数和连接,然后发起调用,openFeign帮我们把拼接参数的这个过程包装了起来,通过代理对象的模式,帮我们构建http请求,而我们在使用的时候,就向写mybatis中的mapper接口一样,即可

openFeign的源码,看起来是比较简单的,如果有看过spring的源码,看起来就会快很多

1.使用

在使用的时候
引入jar包

<dependency>
   <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>

然后在启动类上,加上@EnableFeignClients注解

最后,在我们定义的接口上,加上@FeignClient注解

@FeignClient注解所要修饰的接口,是我们在自己的应用中定义的,需要和调用的远程接口定义保持一模一样

在这里插入图片描述

在这里插入图片描述

2.源码

我们从启动类上的注解开始来看,通常在这个注解中,会有一些逻辑的处理
在这里插入图片描述

我们可以看到,FeignClientsRegistrar是ImportBeanDefinitionRegistrar的实现类,ImportBeanDefinitionRegistrar是spring提供的扩展点之一,我们可以在其实现方法中,自己注入对象到beanDefinitionMap集合中
在这里插入图片描述

2.1 registerDefaultConfiguration

可以看到,在实现方法中,有两个方法,registerDefaultConfiguration这个方法的逻辑看起来比较简单,就是根据@EnableFeignClients注解,build了一个beanDefinition对象,然后注入到了beanDefinitionMap集合中
在这里插入图片描述
在这里插入图片描述

2.2 registerFeignClients

我们需要关注的是第二个方法:registerFeignClients(),

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
	// 1.初始化一个扫描器,是为了去指定的包下,扫描指定的bean
	ClassPathScanningCandidateComponentProvider scanner = getScanner();
	scanner.setResourceLoader(this.resourceLoader);

	Set<String> basePackages;

	Map<String, Object> attrs = metadata
			.getAnnotationAttributes(EnableFeignClients.class.getName());
	AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
			FeignClient.class);
	final Class<?>[] clients = attrs == null ? null
			: (Class<?>[]) attrs.get("clients");
	//如果@Enable注解中没有指定client,会走这里的逻辑
	if (clients == null || clients.length == 0) {
		// 指定scanner扫描时,需要处理的类
		scanner.addIncludeFilter(annotationTypeFilter);
		// 获取要扫描的包
		basePackages = getBasePackages(metadata);
	}
	else {
		final Set<String> clientClasses = new HashSet<>();
		basePackages = new HashSet<>();
		for (Class<?> clazz : clients) {
			basePackages.add(ClassUtils.getPackageName(clazz));
			clientClasses.add(clazz.getCanonicalName());
		}
		AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
			@Override
			protected boolean match(ClassMetadata metadata) {
				String cleaned = metadata.getClassName().replaceAll("\\$", ".");
				return clientClasses.contains(cleaned);
			}
		};
		scanner.addIncludeFilter(
				new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
	}

	// 依次遍历要扫描的包,然后扫描指定的bean,在这里,就是加了@FeignClients注解的对象
	for (String basePackage : basePackages) {
		// 这里就是扫描bean的方法,和spring的扫描bean的方法极其一样
		Set<BeanDefinition> candidateComponents = scanner
				.findCandidateComponents(basePackage);
		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition) {
				// verify annotated class is an interface
				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				Assert.isTrue(annotationMetadata.isInterface(),
						"@FeignClient can only be specified on an interface");

				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(
								FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
				registerClientConfiguration(registry, name,
						attributes.get("configuration"));
				// 中间是解析@FeignClients注解的属性信息,然后会调用这里的register方法进行注册
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
}

可以看到,上面的方法其实就是去解析在指定包下,加了@FeignClients注解的class文件,解析了之后,会调用registerFeignClient方法进行bean的注册

在注册的方法中,有一个方法特别重要,

BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);

在这里插入图片描述

这里会看到,会把beanDefinition的beanClass对象设置为入参的beanClass,也就是FeignClientFactoryBean.class
这里的FeignClientFactoryBean对象极其重要,所有的逻辑都在这个bean中
如果有了解过mybatis和spring整合源码的,就会知道,是通过了一个factoryBean来实现的;这里也一样,也是通过factoryBean来实现的
在这里插入图片描述

截止到此,应用启动时,openFeign所要做的事情,已经结束了,简单来说,在启动的时候,会把加了@FeginClients注解的class文件,转换成beanDefinition对象,并把beanDefinition的beanClass设置为FeignClientFactoryBean,然后把beanDefinition对象放入到beanDefinitionMap集合中
做完这些之后,在spring源码启动的时候,会从beanDefinitionMap中,依次取出beanDefinition进行初始化,在初始化的时候,发现当前bean是factoryBean的子类,就会尝试调用实现类的getObject()方法获取要注入的对象,所以,真正的进行动态代理,或者说,openFeign真正起到核心左右的点,就在factoryBean的getObject()方法

3.FeignClientFactoryBean(重要)

这个getObject()方法是在进行属性注入的时候,调用的;所以核心代码在getTarget()方法中
在这里插入图片描述
在getTarget()方法中,大致有两个分支逻辑,分别是:指定了微服务名和使用url两个分支
针对两个分支,中间的处理逻辑不太一样,因为如果使用的是微服务名的话,需要在集成负载均衡的框架,来做ip和端口的选择;这是使用微服务时,接下来的代码中多出来的代码;如果使用的是url,直接指定了ip和端口,那就不需要由负载均衡这个过程

<T> T getTarget() {
	FeignContext context = this.applicationContext.getBean(FeignContext.class);
	Feign.Builder builder = feign(context);

	// 这里的if 是一个分支,这个分支,如果我们在@FeignClients注解中,没有指定url,那就使用name(这个name,需要配置为微服务的名字)
	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));
	}

	//走到这里的是另外一个分支,指定了url,url里面已经知道了具体的ip和端口
	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);
	// 最核心的是在这里的target()
	return (T) targeter.target(this, builder, context,
			new HardCodedTarget<>(this.type, this.name, url));
}

3.1 使用url,直接指定ip和端口

我们来看下如果指定了ip和端口的时候,相关的处理逻辑:targeter.target()(使用微服务,通过负载均衡的方式,最后也会调用这个方法)

targeter有两个实现类,hystrixTargeter比defaultTargeter多了一个降级的处理,是由一个配置来决定调用哪个的,我们先忽略这两个区别点,我们直接看默认的defaultTargeter实现类:
在这里插入图片描述
在这里插入图片描述

feign.Feign.Builder#target(feign.Target)

我们可以看到,在build()方法中,最后是返回了ReflectiveFeign对象,所以我们要看下这个对象的newInstance()方法干了什么

在这里插入图片描述

feign.ReflectiveFeign#newInstance
在这里插入图片描述

这里我们可以看到,最后在通过Proxy.newProxyInstance()的时候,指定的handler是通过factory.create()创建的,因为最后代理对象在被调用的时候,调用的就是handler的invoke方法,所以我们要看下这个handler初始化的逻辑

在这里插入图片描述
根据上面截图可以看到,最后就是初始化了一个对象:

feign.ReflectiveFeign.FeignInvocationHandler#FeignInvocationHandler

这里暂时,不再往下继续延伸,后面再介绍,我们只需要知道,这里返回了一个FeignInvovationHandler对象作为代理对象生成时的handler对象

3.2 如果使用微服务名,处理逻辑

我们接着来看,如果使用的是微服务名称,这里的逻辑有什么区别:根据上面target()方法来看,如果使用的是微服务名,会进入到loadBalance()这个分支,进行处理;可以看到,这里也是调用的targeter.target()方法,所以,我们可以看到,接下里的逻辑和3.1部分一模一样的
在这里插入图片描述

根据上面的论述,可以看到,最后是通过newInstance()初始化了一个对象,我们先知道,这里会返回一个对象,这个对象其实是一个代理对象,因为看newInstance()方法,就知道,最后是Proxy.newProxyInstance()方法来初始化的

所以,截止到此,我们知道,不管是使用微服务名称进行动态负载均衡,还是直接指定url,最后底层返回的都是一个代理对象

3.3 SynchronousMethodHandler

接下来要单独说一个类,这个类是极其重要的类,在前面3.1 newInstance()方法中,调用factory.create()方法的时候,入参了一个methodToHandler对象,这个对象极其重要;我们先看 在for循环中,如果走到else的逻辑中,会通过targetToHandlersByName.apply(target)方法返回的map集合中获取到methodHandler
在这里插入图片描述
这里可以看到,在create的时候,入参的methodHandler,实际上,入参到了feignInvocationHandler对象中的dispatch属性上,这里属性特别特别重要
在这里插入图片描述

我们接着来看nameToHandler是怎么来的
在这里插入图片描述
在这里插入图片描述

通过上面的逻辑,我们看到,在newInstance()方法中,如果满足Util.isDefault(method),使用的是defaultMethodHandler,如果不满足,使用的是SynchronousMethodHandler
这里我们需要关注的是SynchronousMethodHandler

一定一定要记住一个点,feignInvocationHandler对象的dispatch属性,就是一个map集合,这个map集合中的value是SynchronousMethodHandler
上面这句话一定一定要理解,多看几遍3.3 这部分,一定要理解了这一句话之后,再看第4部分

3.3 这部分先有个印象,在后面会用到

4.在方法被调用时

接下来的这部分,是目标方法在被调用时,代理对象是如何处理的
我们知道, 前面返回的代理对象,最后使用的invocationHandler是FeignInvocationHandler,所以,在目标方法被调用的时候,是会调用到FeignInvocationHandler.invoke()方法
在这里插入图片描述

这里的dispatch对象,前面3.3 说了好几遍,不做过多的解释了,这里get()获取到的是SynchronousMethodHandler,所以,我们直接来看看SynchronousMethodHandler的方法

在这里插入图片描述
最重要的,是这里的client对象,如果是使用了url地址,就是直接指定了ip和端口,那这里的client对象是default
如果使用的是微服务名,那这里的client对象是LoadBalancerClient对象,这个点我暂时还没有看到赋值的地方,后面再细看
在这里插入图片描述

在这里插入图片描述

4.1 Default.execute()

在这里插入图片描述

4.2 LoadBalancerFeignClient.execute()

在这里插入图片描述

这里调用的逻辑,不再细贴代码了,底层就是通过ribbon负载均衡去选取一个ip和端口,然后发起调用,这个其实就和ribbon的代码差不多

总结

所以,openFeign其实底层,就是通过代理对象的方式,帮我们去发起远程调用,在代理对象中,会分别区分使用微服务名进行负载均衡的逻辑和程序员直接指定ip、端口的方式,本质上没太大的区别,只是一个集成了ribbon,一个直接发起调用

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/337193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[WUSTCTF2020]level1 题解

1.查壳 64bit elf 还有一个文本文件&#xff0c;打开 打开是一串数字 根据这个txt文件的名称Output&#xff0c;可以猜测&#xff0c;这个文件的内容可能是程序的输出 2.静态分析 找到main函数反汇编 v7 __readfsqword(0x28u); stream fopen("flag", "r…

二叉搜索树之AVL树

AVL树的概念二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上…

PCCW-HKT Futurera NFT 作品集来袭!

欢迎来到 Futurera&#xff0c;未来的虚拟城市&#xff01; 凭借庞大的 web2 资源&#xff0c;在全球首创的虚拟 5G 移动网络技术的鼎力支持下&#xff0c;Futurera 正力争跨越元宇宙的边界。 NFT 系列介绍 为庆祝 The Sandbox 中 Futurera 体验的开放&#xff0c;我们发布了一…

LSTM已死,Transformer当立(LSTM is dead. Long Live Transformers! ):下

2017 年,Google 在论文 Attention is All you need 中提出了 Transformer 模型,其使用 Self-Attention 结构取代了在 NLP 任务中常用的 RNN 网络结构。而且实验也证明Transformer 在效果上已经完败传统的 RNN 网络。Transformer 的整体模型架构如下图所示。尽管它看起来还是很…

python网络爬虫—快速入门(理论+实战)(七)

系列文章目录 &#xff08;1&#xff09;python网络爬虫—快速入门&#xff08;理论实战&#xff09;&#xff08;一&#xff09; &#xff08;2&#xff09;python网络爬虫—快速入门&#xff08;理论实战&#xff09;&#xff08;二&#xff09; &#xff08;3&#xff09; p…

平价款的血糖血压监测工具,用它养成健康生活习惯,dido F50S Pro上手

之前看有数据显示国内的三高人群越来越年轻&#xff0c;很多人不到三十就有了高血压、高血糖的问题&#xff0c;埋下了不小的健康隐患&#xff0c;加上前阵子的疫情管控放松&#xff0c;人们了解到了新冠病毒对心脏负担的认知&#xff0c;预防慢病被大众提上了日程&#xff0c;…

获取成员userID

文章目录一、简介二、获取token1、获取秘钥2、获取Token三、获取部门数据1、获取部门列表2、获取子部门ID列表3、获取单个部门详情四、获取成员信息1、读取成员2、获取部门成员3、获取部门成员详情一、简介 同步数据到企微&#xff1a; 企业如果需要从自有的系统同步通讯录到…

操作系统systemd启动自启服务进程

概念与背景 Systemd 是 Linux 系统工具&#xff0c;用来启动守护进程&#xff0c;已成为大多数发行版的标准配置。历史上&#xff0c;Linux 的启动一直采用init进程。在ubuntu18.04以后&#xff0c;都采用systemd启动。 更换主要原因是init进程有两个原因 启动时间长。init进…

Java高级-多线程

本篇讲解java多线程 基本概念&#xff1a; 程序、进程、线程 **程序(program)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码&#xff0c;静态对象。 **进程(process)**是程序的一次执行过程&#xff0c;或是正在运行的一个程序。是一个动态的过程…

12年老外贸的经验分享

回想这12年的经历&#xff0c;很庆幸自己的三观一直是正确的&#xff0c;就是买家第一不管什么原因&#xff0c;只要你想退货&#xff0c;我都可以接受退款。不能退给上级供应商&#xff0c;我就自己留着&#xff0c;就是为了避免因为这个拒收而失去买家。不管是什么质量原因&a…

2022年11月软考领证通知

纸质证书领取时间 根据往年各地软考证书的领取时间看&#xff0c;上半年软考证书领取一般在10月底陆续开始&#xff0c;下半年的证书领取时间一般在次年2/3月份左右开始&#xff08;各地证书领取具体时间不一样&#xff0c;届时请多留意当地证书领取通知。&#xff09; 1、证…

PyTorch学习笔记:nn.LeakyReLU——LeakyReLU激活函数

PyTorch学习笔记&#xff1a;nn.LeakyReLU——LeakyReLU激活函数 功能&#xff1a;逐元素对数据应用如下函数公式进行激活 LeakyReLU(x)max⁡(0,x)α∗min⁡(0,x)\text{LeakyReLU}(x)\max(0,x)\alpha*\min(0,x) LeakyReLU(x)max(0,x)α∗min(0,x) 或者 LeakyReLU(x){x,ifx≥0α…

在浏览器输入url到发起http请求,这过程发生了什么

当用户输入url&#xff0c;操作系统会将输入事件传递到浏览器中&#xff0c;在这过程中&#xff0c;浏览器可能会做一些预处理&#xff0c;比如 Chrome 会根据历史统计来预估所输入字符对应的网站&#xff0c;例如输入goog&#xff0c;根据之前的历史发现 90% 的概率会访问「ww…

1理想的大数据处理框架设计

以下内容基于极客 蔡元楠老师的《大规模数据处理实战》做的笔记哈。感兴趣的去极客看蔡老师的课程即可。 MapReduce 缺点 高昂的维护成本 因为mapreduce模型只有map和reduce两个步骤。所以在处理复杂的架构的时候&#xff0c;需要协调多个map任务和多个reduce任务。 例如计…

C#开发的OpenRA的扩展方法

C#开发的OpenRA的扩展方法 在我们以往的开发方法认知里, 对一个类进行扩展方法,只有继父类,然后在子类里创建新的内容。 但是C#又给我们上了一课,它不但可以采用前面的方法, 而且可以对类没有进行继承,也能扩展类型的方法。 这种方式,对于没有进行学习之前,看到代码就是…

Allegro更改线段,丝印,走线,形状,铜箔到不同层的方法

更改线段到不同的Class和Subclass的方法下面以更改线段为例进行讲解1、原先线段在Board Geometry→Soldermask_Top层2、选中线段&#xff0c;鼠标右击选择→Change class/subclass更改到所想要的Class和Subclass3、更改后的线段到Package Geometry→Silkscreen_Top层更改丝印&a…

详解shell中的运算符

目录 前言 一、运算指令 二、运算符号 练习 总结 前言 上一篇文章我们着重学习了 &#xff0c;shell中的执行流控制&#xff0c;本章我很学习和执行流控制相结合使用的运算符号与运算指令。 一、运算指令 计算的三种方式 (()) ##((a12)) let …

51单片机——74HC595的应用(SPI实践)

目录 SPI总线 SPI总线概述 SPI总线分类 SPI 优点及缺点 SPI接口硬件原理 SPI四种工作模式 74HC595应用 74HC595芯片概述 74HC595封装及管脚功能 74HC595工作原理 ​编辑 74HC595串行转并行点亮LED灯 程序实现 Proteus运行结构示意图 SPI总线 SPI总线概述 SPI&#…

【FiddlerScript】利用Fiddler中的FiddlerScript解除7K7K小游戏的防沉迷

本文仅供技术探讨&#xff0c;切勿用于非法用途案例网站:http://www.7k7k.com/准备的工具:配置好的Fiddler一个Fiddler官方英文版配置教程:https://www.bilibili.com/video/BV1rP4y1t7ZLFiddler中文版配重教程:https://www.bilibili.com/video/BV1CP4y1t7DR开始教程来到Fiddler…

10 个最难理解的 Python 概念

文章目录技术提升面向对象编程 (OOP)装饰器生成器多线程异常处理正则表达式异步/等待函数式编程元编程网络编程大家好&#xff0c;与其他编程语言相比&#xff0c;Python 是一门相对简单的编程语言&#xff0c;如果你想真正学透这门语言&#xff0c;其实可能并不容易。 今天我…