Spring实例化源码解析之ClassPathBeanDefinitionScanner(五)

news2024/11/16 7:24:54

Spring实例化源码解析之ClassPathBeanDefinitionScanner(五)

上一章我们分析了ComponentScanAnnotationParser,主要就是分析了@ComponentScan注解内的属性和属性的作用,而注解解析的信息会交给ClassPathBeanDefinitionScanner扫描器使用,也就是本章需要分析的内容。话不多说,直接进入正题。

doScan(String… basePackages)

首先入口就是doScan方法,我们按照规矩还是从注释开始。

在指定的基础包中执行扫描,并返回注册的 Bean 定义。该方法不会注册注解配置处理器,而是将这个责任留给调用者。

解释如下:

  1. “Perform a scan within the specified base packages, returning the registered bean definitions.”:在指定的基础包中执行扫描,即扫描这些包及其子包中的类文件,以查找与 Spring 相关的注解(例如 @Component@Service@Repository 等)标记的类,这些都是上一章节的内容。扫描的结果将会得到注册为 Bean 的定义。

  2. “This method does not register an annotation config processor”:这个方法不会注册注解配置处理器。注解配置处理器是负责解析和处理注解配置的组件,例如 @Configuration@Bean@ComponentScan 等。通常,在 Spring 中会有一个注解配置处理器负责处理这些注解,并将它们转换为相应的 Bean 定义。但是,这个方法并不包含这个处理器的注册过程。@Configuration@Bean第二章也有分析和介绍。

  3. “but rather leaves this up to the caller.”:相反,这个方法将这个责任留给调用者。也就是说,调用者需要自己处理注解配置,包括注册注解配置处理器和执行相应的解析和处理过程。

总结起来,这段话的意思是,该方法提供了执行扫描并返回注册的 Bean 定义的功能,但不包含具体的注解配置处理过程,这个过程需要由调用者自行处理。调用者需要负责注册注解配置处理器,并执行相应的解析和处理操作,以确保扫描到的注解配置能够正确地转换为相应的 Bean 定义。

/**
	 * Perform a scan within the specified base packages,
	 * returning the registered bean definitions.
	 * <p>This method does <i>not</i> register an annotation config processor
	 * but rather leaves this up to the caller.
	 * @param basePackages the packages to check for annotated classes
	 * @return set of beans registered if any for tooling registration purposes (never {@code null})
	 */
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		// 这里面的代码看起来很简单,但是他应该是处理了@ComponentScan注解里的所有属性
		Assert.notEmpty(basePackages, "At least one base package must be specified");

		// 存储所有扫描并注册好的beanDefinitions,set集合
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		// 我加的,用于输出注释,源码中忽略。
		AtomicInteger index = new AtomicInteger();

		for (String basePackage : basePackages) {
			// 包含过滤器的使用在此
			// 根据名称 查询候选的Components,根据包路径,返回的是Bean定义信息集合,所以核心在此。
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					// 在这里就把我们需要spring管理的类组装BeanDefinition,放到了(BeanDefinitionRegistry)BeanFactory中
					System.out.println("当前加载的beanName:"+beanName);
					index.addAndGet(1);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}

		}
		System.out.println("当前加载的所有beanName的个数为:"+ index);
		return beanDefinitions;
	}

根据方法的源码来看,for循环中的findCandidateComponents方法就是其核心方法。打端点可以看到candidates返回的就是我们自定义的这些bean的定义信息。
在这里插入图片描述

findCandidateComponents

查询候选的Component修饰的class,从方法名称就能判断出其干了什么,spring源码中的方法定义的还是非常优秀。basePackage来源于我的AopConfig类中的注解。默认情况下componentsIndex为null,也就是说默认会走else逻辑。

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // 这个basePackage来源于注解类AopConfig
    // componentsIndex不知道是什么,应该是一个扩展点,因为spring启动在这里打断点,此处为null
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
       return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    else {
       // 默认走这里
       return scanCandidateComponents(basePackage);
    }
}

scanCandidateComponents

scanCandidateComponents方法就是去扫描,根据basePackage去组装包的搜索路径,也就是我们想要被spring管理的bean的路径,然后根据路径去获取resources。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
       // 根据basePackage组装包的搜索路径,当前是classpath*:com.qhyu.cloud.**/**/*.class
       String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
             resolveBasePackage(basePackage) + '/' + this.resourcePattern;
       // 获取资源数据,就是具体到文件
       Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
       // 所以我们平常的日志需要打开trace和debug日志,至少在开发过程中需要这么做
       boolean traceEnabled = logger.isTraceEnabled();
       boolean debugEnabled = logger.isDebugEnabled();
       for (Resource resource : resources) {
          if (traceEnabled) {
             logger.trace("Scanning " + resource);
          }
          try {
             // 通过resource获取到元数据
             MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
             // 通过元数据判断是不是候选component,所以此处是核心
             // 此处用到了排除过滤器和包含过滤器
             if (isCandidateComponent(metadataReader)) {
                ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                sbd.setSource(resource);
                if (isCandidateComponent(sbd)) {
                   if (debugEnabled) {
                      logger.debug("Identified candidate component class: " + resource);
                   }
                   candidates.add(sbd);
                }
                else {
                   if (debugEnabled) {
                      logger.debug("Ignored because not a concrete top-level class: " + resource);
                   }
                }
             }
             else {
                if (traceEnabled) {
                   logger.trace("Ignored because not matching any filter: " + resource);
                }
             }
          }
          catch (FileNotFoundException ex) {
             if (traceEnabled) {
                logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
             }
          }
          catch (Throwable ex) {
             throw new BeanDefinitionStoreException(
                   "Failed to read candidate component class: " + resource, ex);
          }
       }
    }
    catch (IOException ex) {
       throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

根据basePackage组装的包搜索路径,获取到文件资源。

Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath)

在这里插入图片描述

拿到所有的resources之后循环判断是不是候选的Component类,如果是就执行candidates.add。

isCandidateComponent

这里就使用到了排除过滤器和包含过滤器。作用是根据给定的 MetadataReader 对象,结合排除过滤器和包含过滤器,判断该组件是否符合候选组件的条件。过滤器可以用于排除或包含特定的组件类型或特征,以实现组件的筛选和过滤。

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.excludeFilters) {
       if (tf.match(metadataReader, getMetadataReaderFactory())) {
          return false;
       }
    }
    for (TypeFilter tf : this.includeFilters) {
       if (tf.match(metadataReader, getMetadataReaderFactory())) {
          return isConditionMatch(metadataReader);
       }
    }
    return false;
}

如果和excludeFilters有匹配就直接返回false,并且至少与一个includeFilters匹配,同时需要满足没有@Condition注解。

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    AnnotationMetadata metadata = beanDefinition.getMetadata();
    return (metadata.isIndependent() && (metadata.isConcrete() ||
          (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

判断注解元数据是否符合候选组件的条件,返回布尔值。

  • metadata.isIndependent(): 判断注解元数据表示的类是否是独立的,即不依赖于其他类。如果是独立的,则继续下面的判断条件。
  • metadata.isConcrete(): 判断注解元数据表示的类是否是具体的类,即非抽象类。如果是具体类,则返回 true。
  • (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())): 判断注解元数据表示的类是否是抽象类,并且是否具有被 @Lookup 注解标注的方法。如果是抽象类且具有 @Lookup 注解的方法,则返回 true。

registerBeanDefinition

拿到了bean的定义信息之后,就往工厂DefaultListableBeanFactory中注册,也就是put进beanDefinitionMap中。

for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					// lazyinit在这里,getBean之后才用
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					// 设置beanDefinition的属性信息,后续用
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					// 组装holder
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					// 在这里就把我们需要spring管理的类组装BeanDefinition,放到了(BeanDefinitionRegistry)BeanFactory中
					System.out.println("当前加载的beanName:"+beanName);
					index.addAndGet(1);
					//DefaultListableBeanFactory中的beanDefinitionMap中
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}

这段代码是一个循环遍历,对一组候选的 BeanDefinition 进行处理和注册。让我们逐行进行分析:

  1. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
    解析候选的 BeanDefinition 的作用域(scope)元数据。
  2. candidate.setScope(scopeMetadata.getScopeName());
    设置候选的 BeanDefinition 的作用域为解析得到的作用域名称。
  3. String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
    使用 BeanNameGenerator 生成候选 BeanDefinition 的唯一名称。
  4. if (candidate instanceof AbstractBeanDefinition) { ... }
    如果候选的 BeanDefinition 是 AbstractBeanDefinition 的实例,执行下面的代码块。
    • postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      对 AbstractBeanDefinition 进行后处理,例如设置懒加载等属性。
  5. if (candidate instanceof AnnotatedBeanDefinition) { ... }
    如果候选的 BeanDefinition 是 AnnotatedBeanDefinition 的实例,执行下面的代码块。
    • AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      处理通用的注解定义,例如处理 @Lazy@Primary@DependsOn 等注解。
  6. if (checkCandidate(beanName, candidate)) { ... }
    检查候选 BeanDefinition 是否符合要求,即是否满足注册的条件。
  7. BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    创建一个 BeanDefinitionHolder 对象,用于持有候选 BeanDefinition 和其对应的名称。
  8. definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    根据作用域元数据,应用相应的代理模式(如创建作用域代理)。
  9. beanDefinitions.add(definitionHolder);
    将 BeanDefinitionHolder 添加到 beanDefinitions 集合中,用于后续的处理。
  10. registerBeanDefinition(definitionHolder, this.registry);
    将 BeanDefinition 注册到 BeanDefinitionRegistry 中,即将其添加到 BeanFactory 的 beanDefinitionMap 中,以便后续的获取和使用。

该代码片段的作用是将一组候选的 BeanDefinition 进行处理和注册,将它们转化为完整的 BeanDefinition,并添加到 BeanFactory 中以便后续的实例化和管理。在处理过程中,会解析作用域元数据、生成唯一的 Bean 名称、处理注解定义、应用代理模式等操作,以确保注册的 BeanDefinition 符合预期的配置和行为。

总结

至此,我们所有需要被spring管理的bean的定义信息都被注册到工厂中,后续bean的初始化实例化都是后续的工作了。invokeBeanFactoryPostProcessors(beanFactory)这句代码就分析了五章,后续将开始BeanFactoryPostProcessors的注册源码分析。

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

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

相关文章

运动控制:步进电机

在机械设计中&#xff0c;我们经常用到步进电机。 比如&#xff0c;用步进电机驱动同步带轴&#xff0c;实现直线运动。 再比如&#xff0c;用步进电机驱动滚珠丝杠轴&#xff0c;也可以把旋转运动转换为直线运动。 因为不需要反馈系统&#xff0c;所以步进电机的最大优点是…

【ESP32 + Edge Impulse平台】运行AI算法模拟多传感器数据融合实现异常检测

本篇博文主要以ESP32+MQ Sensor 气体传感器为例,通过连接 Edge Impulse 平台,实现数据的实时采集和训练,进而实现在嵌入式设备上部署 ML 机器学习。本教程介绍如何使用 Edge Impulse 和机器学习来实现ESP32 异常检测系统,系统使用一个机器学习模型,检测气体何时出现异常。…

基于自动化设备和现代化仓储精益管理思想开发的仓库管理系统

一、开源项目简介 立体仓库WMS deer-wms是基于自动化输送线、机械臂、点数机、提升机、堆垛机等自动化设备和现代化仓储精益管理思想开发出来的仓库管理系统。通过对接工厂的EBS(erp中一种)、MES&#xff08;生产执行系统&#xff09;、deer-wcs&#xff08;设备调度系统&…

分类预测 | MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结合支持向量机分类预测

分类预测 | MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结合支持向量机分类预测 目录 分类预测 | MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结合支持向量机分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结…

tortoiseSVN树冲突解决方案

方案一&#xff1a; 手动导出 trunk 上的文件(夹)&#xff0c;把本地目录文件(夹)删了并替换成 trunk上的&#xff0c;再点击测试合并方案二&#xff1a; 如果执行了方案一还是冲突&#xff0c;确认本地和trunk文件一致后&#xff0c;可以跳过冲突的revision

numpy中的keepdims参数

numpy.mean,sum,max,min等函数中都有keepdims这个参数&#xff0c;这个参数的作用&#xff1a; 当 keepidmsTrue,保持其二维或者三维的特性,(结果保持其原来维数) 默认为 False,不保持其二维或者三维的特性.(结果不保持其原来维数) 假设我们有一个二维数组A&#xff0c;其中A的…

什么人适合学NPDP产品经理认证?

NPDP产品经理认证&#xff0c;最适合学的就是产品经理了&#xff0c;但是证书没有局限性&#xff0c;适用于所有与产品开发相关的行业和领域&#xff0c;特别是那些需要提高产品成功率和效率的团队成员。就是一句话&#xff0c;只要你觉得有用&#xff0c;都可以考。 通过学习N…

k8s的入门和项目部署

1、k8s简介 1.1 k8s是什么 kubernetes&#xff0c;希腊文中舵手的意思&#xff0c;简称K8s&#xff0c;是Google开源的容器编排引擎。在Docker容器引擎的基础上&#xff0c;为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等一系列完整功能&#xff0c;提高了大规…

打造卓越摄影作品,尽享专业级编辑体验——DxO PhotoLab 7 for Mac

DxO PhotoLab 7 for Mac 是一款功能强大的专业照片编辑软件&#xff0c;为摄影爱好者和专业摄影师提供了优秀的工具和功能&#xff0c;让您能够轻松打造卓越的摄影作品并实现专业级的编辑效果。 DxO PhotoLab 7 提供了一套先进的图像处理算法&#xff0c;能够准确地还原照片的…

最近很火的AIGC人工智能之AI赋能运营(巧用ChatGPT轻松上手新媒体)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

打造香港最安全便捷的银行,众安银行发布首份技术白皮书

作者&#xff1a;林海宾&李龙 作为香港金融科技的代表&#xff0c;香港虚拟银行通过科技驱动&#xff0c;为客户提供了安全、便捷、普惠的金融服务。在八间持牌的虚拟银行中&#xff0c;众安银行目前在用户数量、存款、资产和收入规模上均处于领先水平。最快120秒线上开户…

基于微信小程序的新闻发布平台小程序设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

4700 万美元损失,Xn00d 合约漏洞攻击事件分析

4700 万美元损失&#xff0c;Xn00d 合约漏洞攻击事件分析 基础知识 ERC777 ERC777 是 ERC20 标准的高级代币标准&#xff0c;要提供了一些新的功能&#xff1a;运营商及钩子。 运营商功能。通过此功能能够允许第三方账户代表某一合约或者地址 进行代币的发送交易钩子功能。…

怎么选择伪原创工具?伪原创工具推荐

什么是伪原创工具&#xff1f;伪原创工具是一种可以将已有文本进行修改、改写或重新组合&#xff0c;生成新的文本内容的工具。 伪原创工具的作用 节省时间和精力&#xff1a;手工创作内容需要耗费大量时间和精力&#xff0c;而伪原创工具可以在短时间内生成大量内容&#xf…

【TCP/UDP】MSS和MTU、UDP报文、TCP报文、如何实现TCP的长连接、TCP的粘包问题如何解决等重点知识汇总

目录 MSS 和 MTU UDP 和 TCP 区别及应用场景 UDP头部中有哪些信息&#xff1f; TCP头部中有哪些信息&#xff1f; TCP的长连接如何实现 TCP粘包和解决 TCP&#xff08;Transmission Control Protocol 传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输…

漏洞扫描环境:win10系统用VMware Workstation打开虚拟机若干问题

win10系统用VMware Workstation打开虚拟机若干问题 一 .VMware打开虚拟机就蓝屏重启怎么解决&#xff1f;一. VMware打开虚拟机就蓝屏重启怎么解决&#xff1f;方法一&#xff1a;1、同时按下CTRLSHIFTESC打开任务管理器功能&#xff0c;之后依次点击-详细信息-性能后出现下列界…

制作PE启动盘

文章目录 ⭐️写在前面的话⭐️1、下载微PE2、格式化U盘3、安装PE到U盘4、下载镜像 ⭐️写在前面的话⭐️ &#x1f4d2;博客主页&#xff1a; 程序员好冰 &#x1f389;欢迎 【点赞&#x1f44d; 关注&#x1f50e; 收藏⭐️ 留言&#x1f4dd;】 &#x1f4cc;本文由 程序员好…

二维码智慧门牌管理系统引领未来

文章目录 前言一、数据安全性与保密性二、跨网络交互与灵活部署三、系统的优势 前言 在科技飞速发展的时代&#xff0c;传统门牌管理系统逐渐无法满足现代社会的需求。为了解决这一问题&#xff0c;一款基于二维码智慧门牌的管理系统应运而生。这款系统不仅运用了先进的科技力…

java常用API之Object

Objct toString() package myObjct;public class myObjct {public static void main(String[] args) {Object onew Object();System.out.println(o.toString());//打印结果java.lang.Object27f674d} }java.lang.Object27f674d后面的27f674d是地址值 package myObjct;import ja…