Spring-IOC之组件扫描

版本 Spring Framework 6.0.9​

1. 前言

通过自动扫描,Spring 会自动从扫描指定的包及其子包下的所有类,并根据类上的特定注解将该类装配到容器中,而无需在 XML 配置文件或 Java 配置类中逐一声明每一个 Bean。

支持的注解
Spring 支持一系列注解,用于标记哪些类应被自动扫描并作为 Bean 管理。这些注解通常包含:

  • @Component:基础注解,标记一个类作为 Spring 组件。所有其他特殊用途的组件注解都继承自此注解。
  • @Repository:用于标注 DAO(Data Access Object)类,除了具备 @Component 的功能外,还为数据访问异常提供了特殊的翻译机制。
  • @Service:用于标注业务层(Service)类,强调这是一个业务相关的组件。
  • @Controller:用于标注 MVC 架构中的控制器类,通常在 Spring MVC 中使用,与 Spring Web 相关的请求处理逻辑相关联。
  • @Configuration:用于标注配置类,这类类通常包含 @Bean 方法,用于定义其他 Bean。
  • @RestController:结合了 @Controller 和 @ResponseBody,适用于构建 RESTful Web 服务的控制器。

例子相关实体类

  • 控制层(例子中没引入spring-web包,注解先使用@Component替代)
    在这里插入图片描述

  • 服务层
    在这里插入图片描述

  • 数据访问层
    在这里插入图片描述

2. 基于xml

2.1 使用

在 Spring 的XML配置文件中,通过 <context:component-scan> 标签开启自动扫描功能。我们可以配置 base-package 指定扫描路径。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

输出信息说明每个实体类都已加载到容器。

<context:component-scan> 其他配置元素:

  • resource-pattern:可选属性,用于指定在扫描包路径下应匹配的资源模式。默认值为 “**/*.class”,即扫描所有 .class 文件。可以根据需要修改此模式,例如,仅扫描特定目录下的类。
  • use-default-filters:布尔属性,默认值为 true。决定是否启用默认的过滤规则,即查找带有 @Component、@Repository、@Service、@Controller、@Configuration 等注解的类。如果设置为 false,则需要手动配置 和 来指定扫描规则。
  • scope-resolver:指定一个自定义的 ScopeMetadataResolver 实现类,用于确定扫描到的 Bean 的作用域。默认情况下,Spring 使用 AnnotationScopeMetadataResolver,根据类上的 @Scope 注解来确定作用域。
  • scoped-proxy:指定是否为扫描到的带有作用域注解的 Bean 创建代理。可能的值包括:
    • no(默认):不创建代理。
    • interfaces:为实现了接口的 Bean 创建 JDK 动态代理。
    • targetClass:为未实现接口或需要保留原始类类型的 Bean 创建 CGLIB 代理。
  • name-generator:指定一个自定义的 BeanNameGenerator 实现类,用于生成扫描到的 Bean 的名称。默认使用 AnnotationBeanNameGenerator,根据类上的 @Component 等注解的 value 属性或类名生成 Bean 名称。
  • include-filterexclude-filter:这两个元素属于子标签,用于指定更细粒度的扫描规则。可以按注解类型、注解属性、全类名模式等进行包含或排除。

2.2 扫描原理

  1. 当我们使用ClassPathXmlApplicationContext作为IOC容器,在refresh方法的obtainFreshBeanFactory阶段会创建一个bean工厂DefaultListableBeanFactory。
  2. 当前上下文调用loadBeanDefinitions方法,根据容器的配置文件加载bean定义。
  3. 配置文件中通过 <context:component-scan> 标签开启自动扫描功能,属于其他命名空间,托管给 ComponentScanBeanDefinitionParser处理。
  4. ComponentScanBeanDefinitionParser#parse方法中创建一个ClassPathBeanDefinitionScanner对象,调用其scan方法扫描组件并加载到bean工厂中。

在这里插入图片描述

其他命名空间

Spring框架除了核心的XML命名空间外,还提供了多个扩展命名空间(除beans以外的命名空间),以支持特定的功能模块和简化配置。

  1. http://www.springframework.org/schema/beans:核心命名空间,这是最基础且最常用的命名空间,用于定义bean、设置属性、注入依赖、配置构造函数参数、初始化方法、销毁方法、自动装配等基本IoC容器功能。
  2. http://www.springframework.org/schema/context:
    context命名空间,提供了对Java配置类、自动扫描、注解驱动的bean定义、属性占位符替换、资源加载等上下文相关的高级功能的支持。通过此命名空间,可以简化基于注解的配置,如@Component、@Autowired等。

在实例化XmlReaderContext时,DefaultBeanDefinitionDocumentReader会创建一个命名空间解析器DefaultNamespaceHandlerResolver,缓存到XmlReaderContext,当Reader解析配置文件时发现存在其他命名空间时,通过DefaultNamespaceHandlerResolver加载 META-INF/spring.handlers 路径下的其他命名空间处理器,获取对应的处理器处理。

XmlReaderContext 是Spring框架中用于处理XML配置文件解析过程中的上下文对象,为DefaultBeanDefinitionDocumentReader在解析和处理XML配置文件时提供了必要的环境支持。

在这里插入图片描述

context命名空间处理器(ComponentScanBeanDefinitionParser)

  1. 获取基础包名basePackages。
  2. 创建类路径 Bean 定义扫描程序ClassPathBeanDefinitionScanner。
  3. 遍历扫描包名,查找被@Component及其派生注解修饰的类。将它们封装为BeanDefinitionHolder对象。
  4. 往bean工厂加载特定注解后置处理器的bean定义(internalConfigurationAnnotationProcessor、internalAutowiredAnnotationProcess、internalCommonAnnotationProcessor、internalEventListenerProcessor、internalEventListenerFactory),触发组件注册事件.
获取基础包名basePackages
  • element.getAttribute(BASE_PACKAGE_ATTRIBUTE)方法从对象中获取base-package的属性值。

  • parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage)方法解析占位符,获取解析值。

  • StringUtils.tokenizeToStringArray(basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)方法将基础包名按逗号、分号或空格等常见分隔符拆分为一个字符串数组basePackages。

在这里插入图片描述

创建组件扫描器

ComponentScanBeanDefinitionParser#configureScanner方法通过对XML元素element的各项属性(use-default-filters、resource-pattern、name-generator、scope-resolver、scoped-proxy)和子元素(include-filter、exclude-filter)进行解析,创建一个定制化的扫描器ClassPathBeanDefinitionScanner。
在这里插入图片描述

另外通过ClassPathBeanDefinitionScanner构造函数实例化时,会创建一个Component类型的注解类型过滤器AnnotationTypeFilter添加到includeFilters属性中,用于将类与@Component进行匹配。
在这里插入图片描述

扫描组件

ClassPathBeanDefinitionScanner#doScan扫描指定的basePackages包及其子包,查找并处理符合要求的bean定义,最终将它们封装为BeanDefinitionHolder对象并返回,bean定义类型是ScannedGenericBeanDefinition。
在这里插入图片描述

扫描核心方法ClassPathScanningCandidateComponentProvider#findCandidateComponents用于在指定的basePackage包及其子包下查找符合要求的候选bean组件(BeanDefinition),支持索引查找(效率更高)或按传统方式查找(获取指定包下所有class对象筛选)。
在这里插入图片描述

我们只看传统方式查找方式,ClassPathScanningCandidateComponentProvider#scanCandidateComponents方法用于在给定的basePackage及其子包下通过类路径扫描机制查找候选bean组件(BeanDefinition).
在这里插入图片描述

ClassPathScanningCandidateComponentProvider#isCandidateComponent方法用于判断给定的MetadataReader所代表的类是否满足候选bean组件的条件,在不添加其他排除过滤器、包含过滤器或配置,上面创建组件扫描器过程中,已知添加了一个AnnotationTypeFilter包含过滤器,在此处使用,匹配被@Component注解的类。
在这里插入图片描述

触发组件注册事件

ComponentScanBeanDefinitionParser#registerComponents负责将一组BeanDefinition注册到Spring IoC容器中,并处理与之相关的XML元素及注解配置处理器。实际上做了两件事

  • 往bean工厂加载特定注解后置处理器的bean定义
  • 触发组件注册事件,一般情况下是EmptyReaderEventListener,空方法。

在这里插入图片描述

3. 全注解开发

AnnotationConfigApplicationContext 是 Spring Framework 中的一个核心类,用于创建和管理基于 Java 注解的 Spring 应用程序上下文。AnnotationConfigApplicationContext 有两种开启组件扫描的方式

  • 直接指定包路径
  • 通过@ComponentScan注解

3.1 通过指定包路径开启扫描

使用

在这里插入图片描述
在这里插入图片描述

扫描原理

接受一个字符串数组参数 basePackages的有参构造逻辑分成三部分:

  • this(); 调用无参构造函数
  • scan(basePackages); 执行类路径扫描
  • refresh(); 启动应用上下文
    在这里插入图片描述

无参构造函数实例化组件扫描器 ClassPathBeanDefinitionScanner,用于在类路径中扫描并注册基于注解的 Bean 定义(如 @Component、@Service、@Repository、@Controller 等)。
在这里插入图片描述

调用ClassPathBeanDefinitionScanner#scan方法扫描组件,与基于xml扫描逻辑一样,调用的是ClassPathBeanDefinitionScanner#scan方法,所以bean定义创建的类型也是ScannedGenericBeanDefinition类。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2 通过Java配置类开启扫描

使用

在这里插入图片描述c
在这里插入图片描述
在这里插入图片描述

扫描原理

接受一个类型为 Class<?> 的可变参数数组 componentClasses的有参构造逻辑分成三部分:

  • this(); 调用无参构造函数
  • register(componentClasses); 注册指定的组件
  • refresh(); 启动应用上下文

在这里插入图片描述

第一部分虽然与接受字符串的有参构造(指定扫描路径)一样调用了无参构造,但我们这里只关注 带注释的 Bean 定义读取器AnnotatedBeanDefinitionReader,而不是类路径 Bean 定义扫描程器ClassPathBeanDefinitionScanner。
在这里插入图片描述

实例化AnnotatedBeanDefinitionReader过程中,会调用AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)方法,往bean工厂注册一个bean定义后置处理器ConfigurationClassPostProcessor。
在这里插入图片描述
在这里插入图片描述

第二部分注册Java配置类ScanConfig,实例化并初始化bean定义类AnnotatedGenericBeanDefinition,并加载到bean工厂中。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

第三部分在refresh方法invokeBeanFactoryPostProcessors阶段,会调用在第一部分注册的ConfigurationClassPostProcessor(在当前阶段会调用getBean方法实例化)后置处理。
在这里插入图片描述

ConfigurationClassPostProcessor 是 Spring 框架中一个重要的 BeanDefinitionRegistryPostProcessor 实现,主要负责处理带有 @Configuration 注解的类以及它们内部定义的 @Bean 方法和其他相关注解。postProcessBeanDefinitionRegistry方法会筛选出有效的@configuration类,调用配置类解析器ConfigurationClassParser的parse方法解析。
在这里插入图片描述
在这里插入图片描述

从第二部分知道,ScanConfig生成的bean定义类是AnnotatedGenericBeanDefinition,是AnnotatedBeanDefinition的子类。debug源码到处理@ComponentScan注解(this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());),说明ConfigurationClassParser处理逻辑委托给componentScanParser,调用其parse方法处理。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里的this.componentScanParser是ComponentScanAnnotationParser类,在调用ConfigurationClassParser构造函数实例化时创建。
在这里插入图片描述

ComponentScanAnnotationParser#parse方法中的逻辑就很熟悉了,可以对标context命名空间处理各个属性。在方法中是新建了一个ClassPathBeanDefinitionScanner类,而不是使用AnnotationConfigApplicationContext无参构造中实例化的ClassPathBeanDefinitionScanner,最后执行doscan方法扫描组件。
在这里插入图片描述

总结

总结一下通过@ComponentScan开启扫描流程.

  • this();
    • 在实例化AnnotatedBeanDefinitionReader过程中,往bean工厂加载了ConfigurationClassPostProcessor的bean定义
  • register(componentClasses);
    • 往bean工厂加载了包含@Configuration、@ComponentScan注解的ScanConfig配置类 的bean定义。
  • refresh();
    • 在invokeBeanFactoryPostProcessors阶段实例化了ConfigurationClassPostProcessor,并调用其postProcessBeanDefinitionRegistry方法处理@Configuration配置类。
    • ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中,获取并筛选有效的 @Configuration类,实例化了配置类解析器ConfigurationClassParser,调用其parse方法,解析配置类(ScanConfig.class)。
    • ConfigurationClassParser#parse包含处理配置类中各种注解,其中就包含@ComponentScan。但解析@ComponentScan,委托给了ComponentScanAnnotationParser类,调用其parse方法.
    • ComponentScanAnnotationParser#parse实例化了类路径 Bean 定义扫描器ClassPathBeanDefinitionScanner,ClassPathBeanDefinitionScanner加载扫描配置后调用doScan扫描组件。

在这里插入图片描述

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

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

相关文章

书生·浦语大模型实战营之Llama 3 高效部署实践(LMDeploy 版)

书生浦语大模型实战营之Llama 3 高效部署实践&#xff08;LMDeploy 版&#xff09; 环境&#xff0c;模型准备LMDeploy chatTurmind和Transformer的速度对比LMDeploy模型量化(lite)LMDeploy服务(serve) 环境&#xff0c;模型准备 InternStudio 可以直接使用 studio-conda -t …

机器学习运用-民宿价格

项目简介 随着旅游业的蓬勃发展&#xff0c;民宿市场迎来了前所未有的增长机遇。正好最近在参加拓尔思数据挖掘公益实习活动&#xff0c;我的项目将应用机器学习技术开发一个价格预测模型。可以达到更好地理解和预测民宿价格的目的&#xff0c;该模型综合考虑了从容纳人数、便…

【Python】Python函数的黑魔法:递归,嵌套函数与装饰器

欢迎来到CILMY23的博客 本篇主题为&#xff1a; Python函数的黑魔法&#xff1a;递归&#xff0c;嵌套函数与装饰器 个人主页&#xff1a;CILMY23-CSDN博客 系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 感谢观看&#xff0c;支持的可以给个一键三连&#xff…

基于51单片机的矩阵按键扫描的proteus仿真

文章目录 一、按键按键按键消抖 二、独立按键仿真图仿真程序 三、矩阵按键仿真图仿真程序 四、总结 一、按键 按键 按键通常指的是电子设备上的一种输入装置&#xff0c;用于在按下时发送信号&#xff0c;以便设备执行相应的操作。按键可以分为独立按键和矩阵按键两种类型。 …

TRIZ理论下攀爬机器人的创新设计与研究

随着科技的飞速发展&#xff0c;机器人技术已广泛应用于各个领域。特别是在复杂环境下的作业&#xff0c;如灾难救援、太空探测等&#xff0c;对机器人的移动能力和适应性提出了更高要求。在这样的背景下&#xff0c;基于TRIZ理论的攀爬机器人设计与研究应运而生&#xff0c;它…

简单的图像处理算法

本笔记参考crazy_Bingo 基础&#xff1a; 图像处理都是用卷积矩阵对图像卷积计算&#xff0c;如3X3 的矩阵对640 X 480分辨率的图像卷积&#xff0c;最终会得到638 X 478 的图像。卷积过程是这样的&#xff1a; 一、中值滤波 &#xff1a; 找出矩阵中的最中间值作为像素点 中…

Flink的安装、项目创建、任务打包和部署完整实现,任务实现使用JAVA语言

Flink资源下载地址 Flink安装包下载地址 一、本地模式安装Flink 1、在Linux服务上&#xff0c;创建flink文件夹 mkdir flink 2、上传文件并解压 tar -zxvf flink-1.14.6-bin-scala_2.11.tgz 解压完成后&#xff0c;如图&#xff1a; 3、启动Flink 进入到解压目录下&#x…

YOLOv9训练损失、精度、mAP绘图功能 | 支持多结果对比,多结果绘在一个图片(消融实验、科研必备)

一、本文介绍 本文给大家带来的是YOLOv9系列的绘图功能&#xff0c;我将向大家介绍YOLO系列的绘图功能。我们在进行实验时&#xff0c;经常需要比较多个结果&#xff0c;针对这一问题&#xff0c;我写了点代码来解决这个问题&#xff0c;它可以根据训练结果绘制损失(loss)和mA…

Java的八大基本数据类型和 println 的介绍

前言 如果你有C语言的基础&#xff0c;这部分内容就会很简单&#xff0c;但是会有所不同~~ 这是我将要提到的八大基本数据类型&#xff1a; 注意&#xff0c;Java的数据类型是有符号的&#xff01;&#xff01;&#xff01;和C语言不同&#xff0c;Java不存在无符号的数据。 整…

Day:动态规划 LeedCode 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

123. 买卖股票的最佳时机 III 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意&#xff1a;你不能同时参与多笔交易&#xff08;你必须在再次购买前出售掉之前的股票&a…

安全开发实战(2)---域名反查IP

目录 安全开发专栏 前言 域名与ip的关系 域名反查ip的作用 1.2.1 One 1.2.2 Two 1.2.3 批量监测 ​总结 安全开发专栏 安全开发实战http://t.csdnimg.cn/25N7H 这步是比较关键的一步,一般进行cdn监测后,获取到真实ip地址后,或是域名时,然后进行域名反查IP地址,进行进…

基于Springboot的职称评审管理系统

基于SpringbootVue的职称评审管理系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 评审条件 论坛信息 系统公告 后台登录页面 用户管理 评审员管理 省份…

再谈C语言——理解指针(四)

assert断⾔ assert.h 头⽂件定义了宏 assert() &#xff0c;⽤于在运⾏时确保程序符合指定条件&#xff0c;如果不符合&#xff0c;就报错终⽌运⾏。这个宏常常被称为“断⾔”。 assert(p ! NULL); 上⾯代码在程序运⾏到这⼀⾏语句时&#xff0c;验证变量 p 是否等于 NULL 。…

​LeetCode解法汇总2385. 感染二叉树需要的总时间

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一棵二叉树的根节点 root &#xff0…

创建型设计模式

七大原则 1. 开闭原则&#xff08;Open-Closed Principle, OCP&#xff09; 详解&#xff1a;软件实体&#xff08;类、模块、函数等&#xff09;应该易于扩展&#xff0c;但是不易于修改。换句话说&#xff0c;当软件需求变化时&#xff0c;应该通过添加新代码来实现变化&am…

销冠必备:高效跟进客户的四个技巧

作为一名销售&#xff0c;高效而精准地跟进客户是取得成功的关键。今天&#xff0c;我将分享四个技巧&#xff0c;让你也能够高效的跟进客户。 1、善于发问 通过多询问客户&#xff0c;你可以更好地了解客户的需求和痛点。在与客户交流时&#xff0c;不要只是简单地回答问题&…

业务复习知识点Oracle查询

业务数据查询-1 单表查询 数据准备 自来水收费系统建表语句.sql 简单条件查询 精确查询 需求 &#xff1a;查询水表编号为 30408 的业主记录 查询语句 &#xff1a; select * from t_owners where watermeter 30408; 查询结果 &#xff1a; 模糊查询 需求 &#xff1a;查询业…

毕业设计注意事项(2024届更新中)

1.开题 根据学院发的开题报告模板完成&#xff0c;其中大纲部分可参考资料 2.毕设 根据资料中的毕设评价标准&#xff0c;对照工作量 3.论文 3.1 格式问题 非常重要&#xff0c;认真对比资料中我发的模板&#xff0c;格式有问题&#xff0c;答辩输一半&#xff01; 以word…

W801学习笔记十四:掌机系统——菜单——尝试打造自己的UI

未来将会有诸多应用&#xff0c;这些应用将通过菜单进行有序组织和管理。因此&#xff0c;我们需要率先打造好菜单。 LCD 驱动通常是直接写屏的&#xff0c;虽然速度较快&#xff0c;但用于界面制作则不太适宜。所以&#xff0c;最好能拥有一套 UI 框架。如前所述&#xff0c;…

【linux】编译器使用

目录 1. gcc &#xff0c;g 编译器使用 a. 有关gcc的指令&#xff08;g同理&#xff09; 2. .o 文件和库的链接方式 a. 链接方式 b. 动态库 和 静态库 优缺点对比 c. debug 版本 和 release 版本 1. gcc &#xff0c;g 编译器使用 a. 有关gcc的指令&#xff08;g同理&…