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

文章目录

  • 前言
  • 正文
    • 一、从启动类开始
    • 二、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项目示例)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

kafka原理看这一篇就够了

为何使用消息队列 异步。接口方式实现多个系统协作&#xff0c;如图A系统作为用户请求接收方&#xff0c;需要调用多个系统的接口&#xff0c;这些接口还有可能是在A系统里同步调用&#xff0c;所以最后的接口耗时是多个系统接口耗时的总和&#xff1b;mq方式则可以异步发送消…

终于有人把数据资产入表知识地图总结出来了,轻松看懂

在当前数字化的浪潮下&#xff0c;数据已经成为劳动、土地、知识、技术以后的第五大生产要素&#xff0c;“数据就是资源”已成为共识。如今数据资产“入表”已成定局&#xff0c;数据资产化迫在眉睫。 2023年8月21日&#xff0c;财政部正式印发《企业数据资源相关会计处理暂行…

[Linux] 进程入门

&#x1f4bb;文章目录 &#x1f4c4;前言计算机的结构体系与概念冯诺依曼体系结构操作系统概念目的与定位 进程概念描述进程-PCBtask_struct检查进程利用fork创建子进程 进程状态进程状态查看僵尸进程孤儿进程 &#x1f4d3;总结 &#x1f4c4;前言 作为一名程序员&#xff0c…

Django学习日志09

choices参数的使用 """对于以上可能被我们列举完的字段我们一般都是选择使用choices参来做""" class UserInfo(models.Model):username models.CharField(max_length64)password models.CharField(max_length32)# 先写一个映射关系gender_cho…

从零开始的搭建指南:开发高效的抖音预约服务小程序

预约服务小程序提高了效率&#xff0c;节省了用户时间。下文&#xff0c;小编将与大家一同探讨如何从零开始打造预约服务小程序。 第一步&#xff1a;明确需求和目标 确定你的小程序主要服务领域是什么&#xff1f;是医疗预约、美容美发、餐厅预订还是其他行业&#xff1f;明…

【C++ 学习 ㊴】- 详解 C++ 的 I/O 流

目录 一、C 的 I/O 流 二、C 的标准 I/O 流 三、C 的文件 I/O 流 一、C 的 I/O 流 C 语言有一套完成数据读写&#xff08;I/O&#xff09;的解决方案&#xff1a; 使用 scanf()、gets() 等函数从键盘读取数据&#xff0c;使用 printf()、puts() 等函数向屏幕输出数据&#…

Web前端—移动Web第三天(移动Web基础、rem、less、综合案例—极速问诊)

版本说明 当前版本号[20231120]。 版本修改说明20231120初版 目录 文章目录 版本说明目录移动 Web 第三天01-移动 Web 基础谷歌模拟器屏幕分辨率视口二倍图适配方案 02-rem简介媒体查询rem 布局flexible.jsrem 移动适配 03-less注释运算嵌套变量导入导出禁止导出 04-综合案例…

【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏2(附项目源码)

文章目录 先看看最终效果前言生成走廊生成房间修复死胡同增加走廊宽度获取走廊位置信息集合方法一方法二 源码完结 先看看最终效果 前言 上期已经实现了房间的生成&#xff0c;本期紧跟着上期内容&#xff0c;生成走廊并结合上期内容生成连通的房间。 生成走廊 修改Procedur…

WPF Button点击鼠标左键弹出菜单

目录 ContextMenu介绍WPF实现点击鼠标左键弹出菜单如何禁用右键菜单如何修改菜单样式菜单位置设置 本篇博客介绍WPF点击按钮弹出菜单&#xff0c;效果如下&#xff1a; 菜单的位置、央视可以自定义。 实现技巧&#xff1a;不在xaml里菜单&#xff0c;在按钮左键按下的点击事件里…

Linux系统编程 系统编程概念

1.系统调用 系统调用&#xff08;system call&#xff09;其实是 Linux 内核提供给应用层的应用编程接口&#xff08;API&#xff09;&#xff0c;是 Linux 应用层进入内核的入口。不止 Linux 系统&#xff0c;所有的操作系统都会向应用层提供系统调用&#xff0c;应用程序通过…

每日汇评:美日在两个月低点附近似乎较为脆弱,熊市可能会在FOMC会议纪要公布前暂停

美元/日元跌至两个月低点&#xff0c;并受到多种因素的压力&#xff1b; 美联储鸽派预期和美国债券收益率下降继续令美元承压&#xff1b; 美日利差缩小以及日本央行政策转变的押注提振了日元&#xff1b; 美元/日元货币对在周二持续第四天承受着沉重的卖压&#xff0c;同时也标…

jenkins-2.426.1-1.1.noarch.rpm 的公钥没有安装

执行命令 yum install jenkins 报错 jenkins-2.426.1-1.1.noarch.rpm 的公钥没有安装 下载的软件包保存在缓存中&#xff0c;直到下次成功执行事务。 您可以通过执行 yum clean packages 删除软件包缓存。 错误&#xff1a;GPG 检查失败 解决办法&#xff1a; 1、安装新的公…

Linux上通过SSL/TLS和start tls连接到LDAP服务器(附C++代码实现认证流程)

一&#xff0c;大致流程。 1.首先在Linux上搭建一个LDAP服务器 2.在LDAP服务器上安装CA证书&#xff0c;服务器证书&#xff0c;因为SSL/TLS&#xff0c;start tls都属于机密通信&#xff0c;需要客户端和服务器都存在一个相同的证书认证双方的身份。3.安装phpldapadmin工具&am…

集群创建(flannel)时候,没有自动创建出cni0网卡

给旧的集群加入四台新的服务器启动时候发现都是正常的&#xff0c;但是pod通信报错 集群通信失败&#xff0c;第一时刻想看看是不是cni0和flannel.1的网段是不是通的&#xff0c;点进去一看发现cni0网卡没有生成。 部署是通过kubeadm方式部署的集群&#xff0c;目前有两种解决…

数据库表的内连接和外连接

1.内连接查询语法 -- 隐式内链接 SELECT 字段列表 FROM 表1&#xff0c;表2WHERE 条件&#xff1b; -- 显示内连接 select 字段列表 from 表1 [inner] join 表2 on 条件&#xff1b; 如果两个表没用进行内连接&#xff0c;会生成笛卡尔积。A集合和B集合全部元素进行排列组合。 …

Oracle数据库安装踩坑记录

Oracle数据库安装踩坑记录 踩坑目录 可能会用到的教程1. 管理员用户&#xff08;sys&#xff09;登录oracle命令2. 默认密码&#xff1a;三个 如果忘记改密码参考 1. 登录后修改密码3. 查看账号密码&#xff1a;只有sys用户登录后才能查看4. sqldeveloper 连接oracle数据库5. o…

logic-flow 使用过程中遇到的bug - 拖动节点到画布的时候,鼠标松开,节点不落在画布,仍旧跟着鼠标走

背景&#xff1a; 插件&#xff1a;logicFlow 用途&#xff1a;画流程图 bug表现&#xff1a; 初始化的样子&#xff1a; bug的样子&#xff1a; 拖动第一个节点的时候&#xff0c;一切正常&#xff08;无论哪个节点作为第一个节点&#xff0c;都是正常的&#xff0c;但是拖动…

渗透实例------2个星期艰难的渗透纪实

2个星期艰难的渗透纪实 kyo327 入侵原因,需删一帖子,目标用www.111.com代替,前期通过初期的网站文件暴力猜解,扫描到robots.txt这个文件,有以下目录。如图1: 图1 再通过对这些文件的访问,从3gadm.php文件的标题栏得到该网站采用的是diy-page8.3的cms,自然可以先用搜索…

SpringBoot-Docker容器化部署发布

在生产环境都是怎么部署 Spring Boot? 打成 jar 直接一键运行打成 war 扔到 Tomcat 容器中运行容器化部署 一、准备Docker 在 CentOS7 上安装好 Docker 修改 Docker 配置&#xff0c;开启允许远程访问 Docker 的功能&#xff0c;开启方式很简单&#xff0c;修改 /usr/lib/s…

使你的软文更具说服力的技巧,媒介盒子分享

软文想要写好除了日常素材积累和思维的训练外&#xff0c;还需要充分的前期策划&#xff0c;这样才能使软文的写作效果更好&#xff0c;今天媒介盒子就来和大家分享&#xff0c;使你的软文更具说服力和吸引力的技巧&#xff01; 一、 受众分析 了解受众的基本属性这样能使写作…