当前位置: 首页 > news >正文

Spring源码分析のBean扫描流程

文章目录

  • 前言
  • 一、scanCandidateComponents
    • 1.1 isCandidateComponent
      • 1.1.1、排除/包含过滤器
      • 1.1.2、条件装配
      • 1.1.3、重载一
      • 1.1.4、重载二
      • 1.1.5、补充:@Lookup注解
  • 总结


前言

  原生的Spring在构造ApplicationContext时,会调用refresh方法。其中就包含了扫描所有包含@Component及其子注解的类(注解模式)或解析xml配置文件(xml模式)将其注册为BeanDefinition的逻辑。

一、scanCandidateComponents

  scanCandidateComponents是扫描指定路径下的类,并且将符合要求的类进行解析,注册成BeanDefinition的逻辑。
  在该方法中:

  1. 将传入的类路径进行格式转换。
  2. 获取指定类路径下的所有.class文件。
  3. 通过MetadataReader 解析.class的元数据信息。

  获得类的元数据信息,判断类上是否有相关注解的方式有两种,第一是通过JVM的类加载,第二是MetadataReader 。为什么Spring选择的是后者?因为JVM的类是懒加载的,如果在Spring启动时就将所有目标路径下的类全部通过JVM加载,那么就违背了JVM类加载的机制。并且如果目标路径下的类很多,对于性能也有一定的损失。而MetadataReader 使用的是ASM技术 最终。得到的是BeanDefinition对象而不是在JVM中加载.class文件。

	/***	参数:需要扫描的类路径 例:com.itbaima*  返回值:BeanDefinition的集合**/private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//将参数中的类路径进行转换 com.itbaima->classpath*:com/itbaima/**/*.classString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;//得到指定类路径下的所有资源文件(类的.class文件)	//例:file [D:\Idea_workspace\2024\springplus\target\classes\com\itbaima\AppConfig.class]Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();//遍历这些资源文件for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}try {//通过MetadataReader 对某个.class文件的元数据进行解析MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//1.1 isCandidateComponentif (isCandidateComponent(metadataReader)) {//创建BeanDefinitionScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);//设置BeanDefinition的source属性 file [D:\Idea_workspace\2024\springplus\target\classes\com\itbaima\AppConfig.class]sbd.setSource(resource);//再次进行判断,对应的类是不是接口或抽象类(和上面的isCandidateComponent是重载的方法)if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}//将该BeanDefinition放入集合中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;}

1.1 isCandidateComponent

  在ClassPathScanningCandidateComponentProvider中,isCandidateComponent有两个,第一个主要是用于判断类元信息中是否需要排除/包含注解:
在这里插入图片描述ExcludeFilter表示排除过滤器,IncludeFilter表示包含过滤器

1.1.1、排除/包含过滤器

  ExcludeFilter的作用:被排除在外的类,即使类上加入了@Component及其子注解,也不会被扫描到:

@Component
public class OrderService {
}
@Component
public class UserService {
}
@ComponentScan(value = "org.ragdollcat",excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = UserService.class)})
public class AppConfig {
}
public class Demo1 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);System.out.println(context.getBean("orderService"));System.out.println(context.getBean("userService"));}
}

在这里插入图片描述
  IncludeFilter的作用:被包含的类,即使类上没有加入@Component及其子注解,也会被扫描到:

@ComponentScan(value = "org.ragdollcat",includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = UserService.class)})
public class AppConfig {
}
public class UserService {
}

在这里插入图片描述

1.1.2、条件装配

  这里还有一个条件装配的概念,我们可以自定义一个类,实现Condition 接口,重写matches方法,自定义匹配的逻辑

@Component
public class MyConditional implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return false;}
}

  并且在需要条件装配的类上,加入@Conditional注解:

@Component
@Conditional(MyConditional.class)
public class UserService {
}

1.1.3、重载一

  在isCandidateComponent方法中:

  1. 判断判断类元信息中是否需要排除/包含注解。
  2. 如果类元信息中需要包含某个注解,能匹配的上,如果还有@Conditional注解,则需要再次判断是否符合条件。
	/***	判断类元信息中是否需要排除/包含注解*	参数:类元信息*/protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {//如果当前元数据中的注解 有符合需要排除的注解 则返回falsefor (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;}/***  主要用于判断条件装配**/private boolean isConditionMatch(MetadataReader metadataReader) {if (this.conditionEvaluator == null) {this.conditionEvaluator =new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);}return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());}public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {//元数据为空 或者类上没有加@Conditional注解 无需判断 直接返回falseif (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {return false;}if (phase == null) {if (metadata instanceof AnnotationMetadata &&ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);}return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);}List<Condition> conditions = new ArrayList<>();//得到所有实现了Condition接口的类for (String[] conditionClasses : getConditionClasses(metadata)) {for (String conditionClass : conditionClasses) {//转换为Condition 对象Condition condition = getCondition(conditionClass, this.context.getClassLoader());//加入到集合中conditions.add(condition);}}AnnotationAwareOrderComparator.sort(conditions);for (Condition condition : conditions) {ConfigurationPhase requiredPhase = null;if (condition instanceof ConfigurationCondition) {requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();}//关键点:调用自定义实现了Condition接口的类 的match方法 查看返回结果if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {//如果自定义实现了Condition接口的类 的match方法 返回的是false 则 这里返回的true 表示需要跳过加上了@Condition注解的类的扫描return true;}}return false;}

  在进行匹配时,调用的核心方法:

	@Overrideprotected boolean matchSelf(MetadataReader metadataReader) {//首先获取类元数据上的注解信息AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();//返回判断的结果://1、类上有@Component及其子注解 或 2、considerMetaAnnotations 为true 并且类上有@Component及其子注解return metadata.hasAnnotation(this.annotationType.getName()) ||(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));}

1.1.4、重载二

  第二个isCandidateComponent方法,主要是判断当前类是否是接口或者抽象类,有一种特殊情况,即该类是抽象类,但是有@Lookup注解,也会被装配。

	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {AnnotationMetadata metadata = beanDefinition.getMetadata();return (metadata.isIndependent() && (metadata.isConcrete() ||(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));}default boolean isConcrete() {//如果是接口或者抽象类 则返回falsereturn !(isInterface() || isAbstract());}

1.1.5、补充:@Lookup注解

  如果某个单例bean中有个属性是多例的,在初始化单例后,每次获取到的属性的地址值都是一样的:

@Component
@Scope("prototype")
public class User {
}
@Component
public class OrderService {@Autowiredprivate User user;public void test(){System.out.println(user);}//    @Lookup("user")
//    public User m1(){
//        return null;
//    }
}
public class Demo1 {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);OrderService orderService = (OrderService) context.getBean("orderService");orderService.test();orderService.test();orderService.test();}}

在这里插入图片描述  如果要每次获取不同的属性,可以使用@Lookup注解实现:

@Component
public class OrderService {@Autowiredprivate User user;public void test(){System.out.println(m1());}@Lookup("user")public User m1(){return null;}
}

在这里插入图片描述

总结

  在Scan方法中主要做了:

  1. 将传入参数的路径进行转换,转换为classpath的格式。
  2. 获取路径下的所有资源文件(.class)。
  3. 通过MetadataReader 解析.class的元数据信息。
  4. 判断被扫描到的类上是否存在@Component及其子注解,并且有无需要排除某个类的情况。还需要判断类上是否加入了@Conditional注解。如果有,调用@Conditional注解value中的类的.matches方法,判断是否需要跳过该类。
  5. 将被扫描的类包装成BeanDefinition对象。
  6. 再次判断被扫描的类是否是接口/抽象类。如果是则不将其创建为BeanDefinition。有加入了@Lookup的抽象类的特殊情况。
  7. 将BeanDefinition对象加入集合。

相关文章:

Spring源码分析のBean扫描流程

文章目录 前言一、scanCandidateComponents1.1 isCandidateComponent1.1.1、排除/包含过滤器1.1.2、条件装配1.1.3、重载一1.1.4、重载二1.1.5、补充&#xff1a;Lookup注解 总结 前言 原生的Spring在构造ApplicationContext时&#xff0c;会调用refresh方法。其中就包含了扫描…...

Ubuntu安装docker:docker-desktop : 依赖: docker-ce-cli 但无法安装它、无法定位软件包 docker-ce-cli

具体错误 sudo apt-get install ./docker-desktop-amd64.deb [sudo] password for weiyu: 正在读取软件包列表... 完成 正在分析软件包的依赖关系树... 完成 正在读取状态信息... 完成 注意&#xff0c;选中 docker-desktop 而非 ./docker-desktop-amd64.de…...

基于大数据的奥运会获奖数据分析系统设计与实现

【大数据】基于大数据的奥运会获奖数据分析系统设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统通过集成先进的数据抓取、处理、存储与可视化技术&#xff0c;为深入理解奥运会…...

数据结构 堆和priority_queue

一、堆的定义 堆&#xff08;heap&#xff09;&#xff0c;是⼀棵有着特殊性质的完全⼆叉树&#xff0c;可以⽤来实现优先级队列&#xff08;priorityqueue&#xff09;。 堆需要满⾜以下性质&#xff1a; 1. 是⼀棵完全⼆叉树&#xff1b; 2. 对于树中每个结点&#xff0c;如…...

Dockerfile 编写推荐

一、导读 本文主要介绍在编写 docker 镜像的时候一些需要注意的事项和推荐的做法。 虽然 Dockerfile 简化了镜像构建的过程&#xff0c;并且把这个过程可以进行版本控制&#xff0c;但是不正当的 Dockerfile 使用也会导致很多问题。 docker 镜像太大。如果你经常使用镜像或者…...

【抽象代数】1.2. 半群与群

群的定义 群非空集合二元运算性质 定义1. 设 为一个非空集合&#xff0c;上有二元运算&#xff0c;满足结合律&#xff0c;则称或为一个半群。 定义2. 设 为半群&#xff0c;若元素 满足 &#xff0c;则称 为 的左幺元&#xff08;右幺元&#xff1a;&#xff09;&#…...

Django中实现简单易用的分页工具

如何在Django中实现简单易用的分页工具&#xff1f;&#x1f4da; 嗨&#xff0c;小伙伴们&#xff01;今天我们来看看如何在 Django 中实现一个超简单的分页工具。无论你是在处理博客文章、产品列表&#xff0c;还是用户评论&#xff0c;当数据量一大时&#xff0c;分页显得尤…...

「软件设计模式」装饰者模式(Decorator)

深入解析装饰者模式&#xff1a;动态扩展功能的艺术&#xff08;C实现&#xff09; 一、模式思想与应用场景 1.1 模式定义 装饰者模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过将对象放入包含行为的特殊封装对象中&#xff0c;动态地…...

CI/CD(二)docker-compose安装Jenkins

1、docker-compose.yml version: 3.8services:jenkins:image: jenkins/jenkins:lts # 使用官方的 Jenkins LTS 镜像container_name: jenkinsuser: root # 如果需要以 root 用户运行ports:- "8080:8080" # Jenkins Web 界面端口- "50000:50000" # 用于 Jen…...

OpenCV机器学习(1)人工神经网络 - 多层感知器类cv::ml::ANN_MLP

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::ml::ANN_MLP 是 OpenCV 库中的一部分&#xff0c;用于实现人工神经网络 - 多层感知器&#xff08;Artificial Neural Network - Multi-Layer…...

ProxySQL构建PolarDB-X标准版高可用路由服务三节点集群

ProxySQL构建PolarDB-X标准版高可用路由服务三节点集群 一、PolarDB-X标准版主备集群搭建 三台机器上传 polardbx 包&#xff0c;包可以从官网https://openpolardb.com/download获取&#xff0c;这里提供离线rpm。 1、上传 polardbx 安装包 到 /opt目录下 rpm -ivh t-pol…...

15.1 Process(进程)类

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 通常开发时想要获得进程是比较困难的事&#xff0c;必须要调用CreateToolhelpSnapshot、ProcessFirst、ProcessNext等API或者诸如 Zw…...

elasticsearch8 linux版以服务的方式启动

1.创建系统服务文件 对于使用 systemd 作为系统初始化系统的 Linux 发行版&#xff08;如 CentOS 7 及以上、Ubuntu 16.04 及以上&#xff09;&#xff0c;需要创建一个 systemd 服务文件。以 root 用户或具有 sudo 权限的用户身份执行以下操作&#xff1a; sudo vim /etc/sy…...

小米 R3G 路由器刷机教程(Pandavan)

小米 R3G 路由器刷机教程&#xff08;Pandavan&#xff09; 一、前言 小米 R3G 路由器以其高性价比和稳定的性能备受用户青睐。然而&#xff0c;原厂固件的功能相对有限&#xff0c;难以满足高级用户的个性化需求。刷机不仅可以解锁路由器的潜能&#xff0c;还能通过第三方固…...

某大型业务系统技术栈介绍【应对面试】

微服务架构【图】 微服务架构【概念】 微服务架构&#xff0c;是一种架构模式&#xff0c;它提倡将单一应用程序划分成一组小的服务&#xff0c;服务之间互相协调、互相配合&#xff0c;为用户提供最终价值。在微服务架构中&#xff0c;服务与服务之间通信时&#xff0c;通常是…...

【区块链】零知识证明基础概念详解

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 零知识证明基础概念详解引言1. 零知识证明的定义与特性1.1 基本定义1.2 三个核心…...

建筑行业安全技能竞赛流程方案

一、比赛时间&#xff1a; 6月23日8&#xff1a;30分准时到场&#xff1b;9&#xff1a;00&#xff0d;10&#xff1a;00理论考试&#xff1b;10&#xff1a;10-12:00现场隐患答疑&#xff1b;12:00-13&#xff1a;30午餐&#xff1b;下午13&#xff1a;30-15&#xff1a;30现场…...

数据结构:图;邻接矩阵和邻接表

邻接矩阵&#xff1a; 1.概念&#xff1a; 邻接矩阵是图的存储结构之一&#xff0c;通过二维数组表示顶点间的连接关系。 2.具体例子 &#xff1a; 一.无向图邻接矩阵示例&#xff1a; 示例图&#xff08;顶点&#xff1a;A、B、C&#xff0c;边&#xff1a;A-B、B-C&…...

DeepSeek-R1论文阅读及蒸馏模型部署

DeepSeek-R1论文阅读及蒸馏模型部署 文章目录 DeepSeek-R1论文阅读及蒸馏模型部署摘要Abstract一、DeepSeek-R1论文1. 论文摘要2. 引言3. DeepSeek-R1-Zero的方法3.1 强化学习算法3.2 奖励建模3.3 训练模版3.4 DeepSeek-R1-Zero的性能、自进化过程和顿悟时刻 4. DeepSeek-R1&am…...

OpenEuler学习笔记(三十三):在 OpenEuler 上搭建 OpenGauss 数据库环境

在 OpenEuler 上搭建 OpenGauss 数据库环境需要按照以下步骤进行。OpenGauss 是华为开源的一款高性能关系型数据库&#xff0c;支持高并发、高可用性和分布式部署。 1. 环境准备 确保你的 OpenEuler 系统满足以下要求&#xff1a; 操作系统&#xff1a;OpenEuler 20.03 LTS 或…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

tomcat入门

1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效&#xff0c;稳定&#xff0c;易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...