SpringCloud原理-OpenFeign篇(二、OpenFeign包扫描和FeignClient的注册原理)

news2024/10/5 17:28:13

文章目录

  • 前言
  • 正文
    • 一、从启动类开始
    • 二、EnableFeignClients 的源码分析
    • 三、Import FeignClientsRegistrar 的作用
    • 四、FeignClientsRegistrar#registerFeignClients(...)
    • 五、饥饿注册&懒注册 FeignClientsRegistrar#registerFeignClient(...)
    • 六、通过Holder真正注册beanDefinition
  • 附录
    • 附1:图解本文
    • 附2:本系列其他文章

前言

本篇是SpringCloud原理系列的 OpenFeign 模块的第二篇。主要研究是使用了FeignClient 注解的接口的初始化原理。也就是它是如何将什么类型的实例,放到容器中的。

另外,本文附录中,图解了本文代码的执行链路。

使用java 17,spring cloud 4.0.4,springboot 3.1.4

正文

一、从启动类开始

先看看如下启动类:

package org.feng;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients(basePackages = "org.feng.feigns")
@SpringBootApplication
public class ClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(ClientApplication.class, args);
    }

}

我们都知道,想要使用 OpenFeign,就需要在启动类中,使用注解EnableFeignClients,而该注解就是一切的开始。

二、EnableFeignClients 的源码分析

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	String[] value() default {};
	
	String[] basePackages() default {};
	
	Class<?>[] basePackageClasses() default {};
	
	Class<?>[] defaultConfiguration() default {};
	
	Class<?>[] clients() default {};
}

三、Import FeignClientsRegistrar 的作用

首先它实现了接口ImportBeanDefinitionRegistrar。这个方法会被自动执行到。
在这里插入图片描述

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		// 注册配置信息
		registerDefaultConfiguration(metadata, registry);
		// 注册FeignClient接口
		registerFeignClients(metadata, registry);
	}

注册配置信息的,我们暂且先不去分析。直接先看看registerFeignClients(metadata, registry) 的具体实现。

四、FeignClientsRegistrar#registerFeignClients(…)

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		// 获取EnableFeignClients注解的属性值
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			// 获取包扫描器
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			// 增加扫描过滤,只获取带有FeignClient注解的
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			// 解析注解EnableFeignClients的属性,获取属性中指定的扫描包信息
			Set<String> basePackages = getBasePackages(metadata);
			// 执行扫描指定的所有包
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}

		for (BeanDefinition candidateComponent : candidateComponents) {
			if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
				// 强制校验,使用FeignClient注解的,只能是一个接口
				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
				Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

				// 获取FeignClient的属性值
				Map<String, Object> attributes = annotationMetadata
						.getAnnotationAttributes(FeignClient.class.getCanonicalName());

				String name = getClientName(attributes);
				String className = annotationMetadata.getClassName();
				// 注册配置信息
				registerClientConfiguration(registry, name, className, attributes.get("configuration"));
				// 注册FeignClient,主要是界定是否是懒加载,进行特殊处理
				registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}

五、饥饿注册&懒注册 FeignClientsRegistrar#registerFeignClient(…)

本文只分析饥饿注册模式。

	private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		if (String.valueOf(false).equals(
				environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
			// 饥饿注册
			eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
		}
		else {
			// 懒注册
			lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
		}
	}

然后我们来看看饥饿注册时,是如何处理的。

private void eagerlyRegisterFeignClientBeanDefinition(String className, Map<String, Object> attributes,
			BeanDefinitionRegistry registry) {
		// 校验属性,即校验FeignClient属性是否正确使用
		validate(attributes);
		// 获取FeignClientFactoryBean的BeanDefinition建造器
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
		
		// 处理FeignClient的属性
		// 此处省略若干代码...
		
		// 通过建造器获取到一个bean描述器
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		
		// 此处省略若干代码...

		// 包装bean描述器,获得一个holder实例,holder有beanDefinition, className, qualifiers 这3个属性
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
		// 通过注册器注册FeignClientFactoryBean的beanDefinition
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
		registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class);
		registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class);
	}

六、通过Holder真正注册beanDefinition

BeanDefinitionReaderUtils#registerBeanDefinition(...)的源码如下:

    public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
        String beanName = definitionHolder.getBeanName();
		// 注册beanDefinition
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            String[] var4 = aliases;
            int var5 = aliases.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String alias = var4[var6];
                registry.registerAlias(beanName, alias);
            }
        }

    }

至此,成功的将FeignClient注解对应的接口,转变为FeignClientFactoryBeanBeanDefinition,并且将其放入Spring容器中。

附录

附1:图解本文

在这里插入图片描述

附2:本系列其他文章

SpringCloud原理-OpenFeign篇(一、Hello OpenFeign项目示例)
SpringCloud原理-OpenFeign篇(二、OpenFeign包扫描和FeignClient的注册原理)
SpringCloud原理-OpenFeign篇(三、FeignClient的动态代理原理)

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

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

相关文章

一文概括AxureRP的优缺点和替代软件

AxureRP是目前流行的设计精美的用户界面和交互软件。AxureRP根据其应用领域提供了一组丰富的UI控制。 Axure是什么软件&#xff1f; Axure是目前流行的设计精美的用户界面和交互软件。Axure已经存在了近十年&#xff0c;让UX设计师轻松了解创建软件原型的细节。作为一种原型设…

【阿里云】图像识别 摄像模块 语音模块

USB 摄像头模块测试及配置 一、首先将 USB 摄像头插入到 Orange Pi 开发板的 USB 接口中二、然后通过 lsmod 命令可以看到内核自动加载了下面的模块三、通过 v4l2-ctl 命令可以看到 USB 摄像头的设备节点信息为 /dev/video0四、使用 fswebcam 测试 USB 摄像头五、使用 motion …

【SA8295P 源码分析】132 - GMSL2 协议分析 之 GPIO/SPI/I2C/UART 等通迅控制协议带宽消耗计算

【SA8295P 源码分析】132 - GMSL2 协议分析 之 GPIO/SPI/I2C/UART 等通迅控制协议带宽消耗计算 一、GPIO 透传带宽消耗计算二、SPI 通迅带宽消耗计算三、I2C 通迅带宽消耗计算四、UART 通迅带宽消耗计算系列文章汇总见:《【SA8295P 源码分析】00 - 系列文章链接汇总》 本文链接…

ROS2中Executors对比和优化

目录 SingleThreadExecutorEventExecutor SingleThreadExecutor 执行流程 EventExecutor 通信图

现在的发票有发票专用章吗?如何验证发票真伪?百望云为您详解!

大部分企业的财务都开始真正用上数电票了&#xff0c;但目前还是处于税控发票与数电票并行的阶段&#xff0c;一些财务朋友并没有深入理解二者的区别&#xff0c;就总会遇到以下的问题&#xff1a; 收到一张数电票&#xff0c;发现没有发票专用章&#xff0c;询问销售方为什么不…

什么是办公RPA?办公RPA解决什么问题?办公RPA实施难点在哪里?

什么是办公RPA&#xff1f; 办公RPA是一种能够模拟人类在计算机上执行任务的自动化软件。它可以在没有人工干预的情况下&#xff0c;执行重复的、规则化的任务&#xff0c;例如数据输入、网页爬取、电子邮件管理等。办公RPA可以帮助企业提高工作效率&#xff0c;降低人力成本&…

图像处理中常用的相似度评估指标

导读 有时候我们想要计算两张图片是否相似&#xff0c;而用来衡量两张图片相似度的算法也有很多&#xff0c;例如&#xff1a;RMSE、PSNR、SSIM、UQI、SIFT以及深度学习等。这篇文章主要介绍&#xff0c;RMSE、PSNR、SSIM、UQI这些指标的计算和应用&#xff0c;关于SIFT算法来…

Spring-IOC-Spring6和JUnit5集成

1、父工程pom.xml <properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><spring.version>…

国产压力测试工具的主要作用

国产压力测试工具可以帮助软件开发和维护团队对系统进行全面的性能测试&#xff0c;以评估系统在高负载下的性能表现。以下是国产压力测试工具的主要作用&#xff1a; 性能评估&#xff1a;国产压力测试工具可以模拟多用户同时对系统进行访问和操作&#xff0c;通过对系统的响应…

SwiftUI 如何动态开始和停止播放永久重复(repeatForever)动画

0. 功能需求 在 SwiftUI 丰富多彩的动画世界中,我们有时希望可以随意开始和停止永久循环(repeatForever)的动画,不过这时往往会产生错误的动画“叠加”效果。 从上图可以看到:虽然我们希望密码输入框背景只在用户输入密码时才发生闪烁,但顶部的密码输入框随着不断输入其…

gitlab 实战

一.安装依赖 yum install -y curl policycoreutils-python openssh-server perl 二.安装gitlab yum install gitlab-jh-16.0.3-jh.0.el7.x86_64.rpm 三.修改下面的 vim /etc/gitlab/gitlab.rbexternal_url http://192.168.249.156 四.初始化 gitlab-ctl reconfigure 五.查看状…

三字经||无聊数了下三字经的字数

三字经总字数去除标点后1416个 该文章无技术含量&#xff0c;仅三字经原文&#xff0c;学技术的同学可以止步了 三字经&#xff08;原文&#xff09; 【作者】王应麟 【朝代】南宋 人之初&#xff0c;性本善。性相近&#xff0c;习相远。 苟不教&#xff0c;性乃迁。教之道&a…

c语言-数据结构-链式二叉树

目录 1、二叉树的概念及结构 2、二叉树的遍历概念 2.1 二叉树的前序遍历 2.2 二叉树的中序遍历 2.3 二叉树的后序遍历 2.4 二叉树的层序遍历 3、创建一颗二叉树 4、递归方法实现二叉树前、中、后遍历 4.1 实现前序遍历 4.2 实现中序遍历 4.3 实现后序遍历 5、…

从零开始的c语言日记day35——数据在内存中的储存

数据类型介绍 之前已经学了了一些基本的内置类型&#xff0c;以及空间大小。 类型的意义&#xff1a; 使用这个类型开辟内存空间的大小&#xff08;大小决定了使用范围&#xff09;。如何看待内存空间的视角 类型的基本归类 整形&#xff1a; 字符的本质是ASCLL码值&#x…

Java实现堆算法

堆是一种特殊的数据结构&#xff0c;它是一棵完全二叉树&#xff0c;且满足堆的性质&#xff1a;对于每个节点&#xff0c;它的值都不小于&#xff08;或不大于&#xff09;它的孩子节点的值。根节点的值就是堆中的最大值&#xff08;或最小值&#xff09;。 Java中提供了一个…

算法分析与设计课后练习22

设W(5,7,10,12,15,18,20)和M35&#xff0c;使用过程SUMOFSUB找出W种使得和数等于M的全部子集并画出所生成的部分状态空间树

Linux C 网络编程概述

网络编程 计算机网络概述分类网络体系结构通信协议通信流程网络通信帧格式以太网帧格式分析ARP 协议分析IP 数据报分析IP分类IP 分配子网掩码 TCP 段分析 TCP三次握手协议 ⭐TCP四次挥手协议 ⭐ TCP编程基于 TCP 客户端编程-步骤说明基于 TCP 服务器端编程-步骤说明基于 TCP 服…

TypeError: expected np.ndarray (got Tensor)解决办法

文章目录 一、错误展示二、错误分析三、解决办法四、其余解决办法总结 一、错误展示 二、错误分析 这个错误表示正在尝试将一个PyTorch的Tensor对象作为numpy的ndarray对象来使用。我们需要使用numpy的ndarray而不是PyTorch的Tensor。 三、解决办法 在我的程序中去掉这一行代…

Redis跳跃表

前言 跳跃表(skiplist)是一种有序数据结构&#xff0c;它通过在每一个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。 跳跃表支持平均O(logN)&#xff0c;最坏O(N)&#xff0c;复杂度的节点查找&#xff0c;还可以通过顺序性来批量处理节点…

局域网文件共享神器:Landrop

文章目录 前言解决方案Landrop软件界面手机打开效果 软件操作 前言 平常为了方便传文件&#xff0c;我们都是使用微信或者QQ等聊天软件&#xff0c;互传文件。这样传输有两个问题&#xff1a; 必须登录微信或者QQ聊天软件。手机传电脑还有网页版微信&#xff0c;电脑传手机比…