一起学SF框架系列5.8-spring-Beans-Bean注解解析3-解析配置component-scan
本文主要讲述Spring是如何解析“context:component-scan”元素,扫描加载目录下的BeanDefinition。
解析内容
1、解析的元素如下:
<!-- 注解模式:配置bean扫描路径(注:自动包含子路径) --><context:component-scan base-package="com.learnsf.main,com.learnsf.service"/>
注:该元素解析过程中,会自动处理“context:annotation-config/”元素要解析的内容。
2、只扫描加载目录下的BeanDefinition,不对注解进行解析。在AbstractApplicationContext.invokeBeanFactoryPostProcessors统一解析。
解析
ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext)
解析元素“context:component-scan”。
@Override@Nullablepublic BeanDefinition parse(Element element, ParserContext parserContext) {// 获取属性“base-package”(多个包路径)String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);// 对包路径中占位符进行替换处理basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);// 分解成包数组String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// 生成BeanDefinition扫描器ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);// 扫描生成beanDefinitionSet<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);// 注册BeanDefinitionregisterComponents(parserContext.getReaderContext(), beanDefinitions, element);return null;}
ClassPathBeanDefinitionScanner.doScan(String… basePackages)
在包里扫描BeanDefinition。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();// 每个包进行扫描for (String basePackage : basePackages) {// 获取候选BeanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);// 每个侯选者处理for (BeanDefinition candidate : candidates) {// 解析Bean作用域ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());// 生成beanName String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition abstractBeanDefinition) {// 对 AbstractBeanDefinition类型的BeanDefinition 进一步处理:赋值BeanDefinition属性默认值,并设置 autowireCandidate 属性postProcessBeanDefinition(abstractBeanDefinition, beanName);}if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) {// 对 AnnotatedBeanDefinition 类型的 BeanDefinition 进一步处理:对通用注解的解析处理,通用注解包括 @Lazy、@Primary、@DependsOn、@Role、@Description。例如:如果当前类被@Lazy修饰,则会获取@Lazy 的value 值并保存到 BeanDefinition#lazyInit 属性中。AnnotationConfigUtils.processCommonDefinitionAnnotations(annotatedBeanDefinition);}// 检查给定候选bean的beanName,确定相应的bean定义是否需要注册或与现有定义冲突if (checkCandidate(beanName, candidate)) {// 封装候选BeanDefinition为BeanDefinitionHolder BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);// 对 BeanDefinitionHolder 填充代理信息definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);// 加入到返回集合beanDefinitions.add(definitionHolder);// 注册BeanDefinitionHolder 到bean工厂容器中registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}
ClassPathScanningCandidateComponentProvider.findCandidateComponents(String basePackage)
ClassPathScanningCandidateComponentProvider是ClassPathBeanDefinitionScanner父类。
获取注解的Bean的BeanDefinition。
public Set<BeanDefinition> findCandidateComponents(String basePackage) {if (this.componentsIndex != null && indexSupportsIncludeFilters()) {// @Indexed 注解的处理 注1return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);}else {// 非@Indexed 注解的处理return scanCandidateComponents(basePackage);}}
注1:@Indexed注解Spring在5.0版本引入的,主要解决启动时注解模式解析太长的问题。处理方式是在项目编译打包时,会自动生成META-INF/spring.components文件,文件包含被@Indexed注释的类的模式解析结果。当Spring应用上下文进行组件扫描时,META-INF/spring.components会被org.springframework.context.index.CandidateComponentsIndexLoader读取并加载,转换为CandidateComponentsIndex对象,此时组件扫描会读取CandidateComponentsIndex,而不进行实际扫描,从而提高组件扫描效率,减少应用启动时间。如果使用该功能,需要引入如下依赖:
<dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId><version>${spring.version}</version><optional>true</optional>
</dependency>
ClassPathScanningCandidateComponentProvider.scanCandidateComponents(String basePackage)
该方法是从指定的包路径获取到字节码文件,筛选出可能注入到Spring容器的Bean生成对应的ScannedGenericBeanDefinition
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {// // 形成完整包路径String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;// 扫描路径下的资源(字节码文件)Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();// 遍历所有资源(字节码文件),挑选有注解的字节码文件生成BeanDefinitionfor (Resource resource : resources) {String filename = resource.getFilename();if (filename != null && filename.contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {// Ignore CGLIB-generated classes in the classpathcontinue;}if (traceEnabled) {logger.trace("Scanning " + resource);}try {// 获得资源的MetadataReader(包含文件信息和对应类注解信息)MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// 校验是否是候选组件,条件是:包含在include-filters(扫描时需要实例化的类,默认都包含)且 @Conditional注解中不跳过的类(默认都不跳过)if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);// 校验是否是候选组件:bean是独立且具体的类 或者 是抽象类但被@Lookup注解修饰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;}
ComponentScanBeanDefinitionParser.registerComponents( XmlReaderContext readerContext, Set beanDefinitions, Element element)
protected void registerComponents(XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {Object source = readerContext.extractSource(element);// 构建CompositeComponentDefinitionCompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);// 将所有BeanDefinition添加到compositeDef的nestedComponents属性中for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));}// Register annotation config processors, if necessary.// 处理“annotation-config”:假定annotation-config是存在,这意味着配置了“context:component-scan”,则不需要再配置“context:annotation-config”boolean annotationConfig = true;if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {// 获取component-scan标签的annotation-config属性值(默认为true)annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));}if (annotationConfig) {// 获取所有处理注解类的BeanPostProcessors(BeanPostProcessor本身也是bean)Set<BeanDefinitionHolder> processorDefinitions =AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);// 将所有BeanPostProcessors的BeanDefinition添加到compositeDef的nestedComponents属性中for (BeanDefinitionHolder processorDefinition : processorDefinitions) {compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));}}// 触发组件注册事件readerContext.fireComponentRegistered(compositeDef);}
相关文章:

一起学SF框架系列5.8-spring-Beans-Bean注解解析3-解析配置component-scan
本文主要讲述Spring是如何解析“context:component-scan”元素,扫描加载目录下的BeanDefinition。 解析内容 1、解析的元素如下: <!-- 注解模式:配置bean扫描路径(注:自动包含子路径) --><conte…...

【LeetCode热题100】打卡第42天:滑动窗口最大值搜索二维矩阵II
文章目录 【LeetCode热题100】打卡第42天:滑动窗口最大值&搜索二维矩阵II⛅前言 滑动窗口最大值🔒题目🔑题解 搜索二维矩阵II🔒题目🔑题解 【LeetCode热题100】打卡第42天:滑动窗口最大值&搜索二维…...

[uni-app] 微信小程序 - 组件找不到/导入报错 (分包问题导致)
文章目录 问题表现问题原因 问题表现 切换了个路径下的组件, 导入失败, 尝试了清缓存\重启\删项目等一些列操作均无效 上面两个路径中, 都存在一模一样的videItem.vue Main路径是可以导入的 Main路径是无法导入的 问题原因 后来发现, 是 分包的问题导致. 我们先来假设一个场…...

从零构建医疗领域知识图谱的KBQA问答系统:其中7类实体,约3.7万实体,21万实体关系。
项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域):汇总有意义的项目设计集合,助力新人快速实…...

编程小白的自学笔记十二(python爬虫入门四Selenium的使用实例二)
系列文章目录 编程小白的自学笔记十一(python爬虫入门三Selenium的使用实例详解) 编程小白的自学笔记十(python爬虫入门二实例代码详解) 编程小白的自学笔记九(python爬虫入门代码详解) 目录 系列文章…...

技术笔记2023076 rBoot学习7
技术笔记2023076 rBoot学习7 继续之前的学习。 代码分析:函数find_image() // prevent this function being placed inline with main // to keep mains stack size as small as possible // dont mark as static or itll be optimised out when // using the ass…...

收藏这6个抠图工具,一键抠图不用愁!
在图片编辑工作中,抠图是设计师常用的操作。随着设计工具的不断增加,抠图操作摆脱了过去繁琐的操作步骤,几乎可以一键完成。今天本文将为大家介绍6个好用的抠图工具,一起来看看吧! 1、皮卡智能抠图 皮卡智能抠图是一…...

四,Eureka 第四章
2.1.3 增加依赖 <!--添加依赖--><dependencies><!--Eureka Server--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>&l…...

k8s常见的资源对象使用
目录 一、kubernetes内置资源对象 1.1、kubernetes内置资源对象介绍 1.2、kubernetes资源对象操作命令 二、job与cronjob计划任务 2.1、job计划任务 2.2、cronjob计划任务 三、RC/RS副本控制器 3.1、RC副本控制器 3.2、RS副本控制器 3.3、RS更新pod 四、Deployment副…...

JavaScript 简单实现观察者模式和发布订阅模式
JavaScript 简单实现观察者模式和发布订阅模式 1. 观察者模式1.1 如何理解1.2 代码实现 2. 发布订阅模式2.1 如何理解2.2 代码实现 1. 观察者模式 1.1 如何理解 概念:观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时ÿ…...

高通WLAN框架学习(37)-- TDLS(Tunneled Direct Link Setup)通道直接链路建立
一 TDLS概述 隧道直连设置(TDLS)基于IEEE 802.11z-2010IEEE标准802.11z标准(无线局域网介质访问控制(MAC)和物理层(PHY)规范。 TDLS允许与同一AP关联的设备之间建立直接链路。Wi-Fi Direct允许设备之间直接连接,而不需要AP。Wi-Fi联盟认证可用于IEEE 802.11a和802.11g设备的T…...

高算力AI模组前沿应用:基于ARM架构的SoC阵列式服务器
本期我们带来高算力AI模组前沿应用,基于ARM架构的SoC阵列式服务器相关内容。澎湃算力、创新架构、异构计算,有望成为未来信息化社会的智能算力底座。 ▌性能优势AI驱动,ARM架构服务器加速渗透 一直以来,基于ARM架构的各类处理器…...

老年公寓人员定位管理系统:提升安全与关怀的智能解决方案
老年公寓作为提供安全居住环境和关怀服务的重要场所,面临着人员管理和安全控制的挑战。为了解决这些问题,老年公寓人员定位管理系统应运而生。基于为提供全面的安全管理和个性化关怀服务,华安联大便通过老年公寓人员定位管理系统的技术原理、…...

每日一题之两个字符串的删除操作
题目链接 给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 **相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 示例 1: 输入: word1 "sea", word2 "eat" 输出: 2 解释: 第一步将 "sea" 变…...

nacos安装与基础配置
源码 https://github.com/alibaba/nacos https://gitee.com/mirrors/Nacos 编译 git clone https://github.com/alibaba/nacos.git cd nacos/ mvn -Prelease-nacos -Dmaven.test.skiptrue clean install -U ls -al distribution/target/// change the $version to your ac…...

GitHub Copilot:让开发编程变得像说话一样简单
引用: 人类天生就梦想、创造、创新。但今天,我们花太多时间被繁重的工作所消耗,花在消耗我们时间、创造力和精力的任务上。为了重新连接我们工作的灵魂,我们不仅需要一种更好的方式来做同样的事情,更需要一种全新的工…...

并发编程中锁的优化
在 Java 并发编程中,锁是一种常用的同步机制,用于控制对共享资源的访问。使用锁可以确保多个线程之间的互斥访问,避免数据竞争和并发问题。 然而,锁的使用可能会带来一定的性能开销,特别是在高并发场景下。 为了优化…...

笔试题:统计字符串中某字符串在其出现的字符个数
笔试题:统计字符串中某一子串的字符个数:例如字符串aabbcd,有aabb:4,ab:2 哈哈,这道题是小编面试音视频龙头企业的笔试题,以下是我写的代码:如果有错误,希望可以指正!!! 解题思路:利用双指针i和…...

Java NIO Files类读取文件流方式详解
Java NIO Files类读取文件流方式详解 Files类原理概述 java.nio.file.Files是Java标准库提供的一个工具类,用于操作文件和目录。它提供了一系列静态方法,可以用于创建、复制、删除、移动、重命名、读取、写入文件和目录等常见的文件系统操作。同时&…...

Mybatis快速入门,Mybatis的核心配置文件
Mybatis快速入门 一、Mybatis简介1.1Mybatis简化JDBC 二、Mybatis快速入门2.1创建user表,添加数据2.2创建模块,导入坐标2.3编写Mybatis核心配置文件 --> 替换连接信息,解决硬编码问题2.4编写SQL映射文件 --> 统一管理sql语句࿰…...

go语言中defer执行顺序
defer 执行顺序和调用顺序相反,类似于栈后进先出。 defer在 return 之后执行,但在函数推出之前,defer可以修改返回值。 func test() int {i : 0defer func() {fmt.Println("defer1")}()defer func() {i 1fmt.Println("defe…...

webpack xxx is not a constructor
环境 webpack5.88.2 vue-router 按需引入 原因 模块循环引用导致 有A B C三个模块 A B模块import C 中导出的class c又依赖B 中Class 的方法 B 又依赖C中的class 此时会导致import 的 C 为undefined...

安装支持vs2019的MFC(解决MSBuild 错误 MSB8041、MSB8042)
安装支持MFC的vs2019(解决MSBuild 错误 MSB8041、MSB8042) 常用安装选项解决MSBuild 错误 常用安装选项 解决MSBuild 错误 安装上述勾选内容后,即可解决MSBuild 错误 MSB8041 MSB8041:此项目需要 MFC/ATL 库。 https://learn.mic…...

校园电气安全风险分析及预防措施 安科瑞 许敏
摘要:校园属于人员密集场所,若安全风险排查、管控不到位,可能导致安全事故发生,造成严重事故后果。校园电气设备设施引起的电气火灾和触电等事故,是构成校园安全威胁之一,笔者通过对校园发生的电气安全事故案例原因分析…...

机器学习之十大经典算法
机器学习算法是计算机科学和人工智能领域的关键组成部分,它们用于从数据中学习模式并作出预测或做出决策。本文将为大家介绍十大经典机器学习算法,其中包括了线性回归、逻辑回归、支持向量机、朴素贝叶斯、决策树等算法,每种算法都在特定的领…...

系统架构设计师 11:未来信息综合技术
本章花了很多笔墨来写各项技术的发展历程,可以了解一下。 一、信息物理系统 信息物理系统(Cyber-Physical Systems,CPS)是控制系统、嵌入式系统的扩展与延伸。 CPS典型的应用场景有:健康管理、智能维护、远程征兆性…...

Docker 数据管理[文件互访] 端口映射[暴露端口提供服务] 容器互联[指定容器名防止IP变动]
Docker 的数据管理 管理 Docker 容器中数据主要有两种方式:数据卷(Data Volumes)和数据卷容器(DataVolumes Containers)。 1.数据卷(宿主机与容器间传输 防止删除容器后数据丢失) 数…...

【stable diffusion】保姆级入门课程04-Stable diffusion(SD)图生图-局部重绘的用法
目录 0.本章素材 1.什么是局部重绘 2.局部重绘和涂鸦有什么不同 3.操作界面讲解 3.1.蒙版模糊 3.2.蒙版模式 3.3.蒙版蒙住的内容 3.4.重绘区域 4.局部重绘的应用(面部修复) 5.课后训练 0.本章素材 chilloutmix模型(真人模型)百度地址…...

制作Java8环境Docker镜像
制作Java8环境Docker镜像 这里介绍如何制作一个java8环境的镜像,用于运行java应用程序。 1.安装包 这里采用OpenJDK,不会涉及版本问题。 同样思源中文字体也是开源的,没有版权问题。 OpenJDK8:OpenJDK8U-jdk_x64_linux_hotsp…...

抖音SEO源码开发指南:介绍如何开发抖音SEO源码的基本步骤和要点。
一、 抖音SEO源码开发指南: 确定目标:首先要明确开发抖音SEO源码的目标是什么,是提高搜索排名还是增加用户量等。根据不同的目标来制定开发策略和思路。 分析竞争:对于同类产品,要进行竞争分析,了解对手的…...