Spring扫描类的原理

2023-11-15

作为Java的开发者Spring可以称之为神一样的存在框架,好处太多无法用言语表达只能称之为Java排名的number one 框架。我们使用Spring它帮助我们实例化了很多Bean对象,但是这些Bean是怎样加载到Spring容器中的呢?相信很多人都不知道。现在就讲一下,还是以Spring Boot项目作为例子来讲,因为它的底层还是Spring。

Spring扫描类主要是依赖这个注解:

@ComponentScan

这个注解Spring boot项目已经自动给我们配置了,有了这个注解项目启动的时候就会找到这个注解然后进行类的加载。主要的代码就是在下面类的方法中完成:

ConfigurationClassParser.doProcessConfigurationClass()

@Nullable
  protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
    // Process any @ComponentScan annotations
    // 这个Set集合中就存放了我们项目中所以加了@ComponentScan类的相关信息
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
        // The config class is annotated with @ComponentScan -> perform the scan immediately
        // 这里就会进行扫描,得到BeanDefinition注册到Spring容器中。
        // 这里也就是扫描类的主要的方法
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
            this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
          BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
          if (bdCand == null) {
            bdCand = holder.getBeanDefinition();
          }
          // 这里会判断扫描出来的Bean是不是配置类【full 或者 lite模式的配置】
          if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
            parse(bdCand.getBeanClassName(), holder.getBeanName());
          }
        }
      }
    }
    // 扫描我们写的那些业务类主要是上面的方法,下面这些方法也是很重要比较难
    // 不是本文要讲的重点,当面下面这些真的很重要,很重要,很重要。
    // 因为不是主要讲的我就把后面代码删除了,留下英文注释,后面其他文章可能会讲。
    // Process any @Import annotations
    // Process any @ImportResource annotations
    // Process individual @Bean methods
    // Process default methods on interfaces
    processInterfaces(configClass, sourceClass);
    return null;
  }

 然后就会到这个方法中。

ComponentScanAnnotationParser. parse()

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
        componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    // 设置BeanName的默认生成器
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
        BeanUtils.instantiateClass(generatorClass));
   // 获取要扫描哪些包下的类
    Set<String> basePackages = new LinkedHashSet<>();
    String[] basePackagesArray = componentScan.getStringArray("basePackages");
    for (String pkg : basePackagesArray) {
      String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Collections.addAll(basePackages, tokenized);
    }
    for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 如果上面获取到的包为空,默认取配置类所在的包
    if (basePackages.isEmpty()) {
      basePackages.add(ClassUtils.getPackageName(declaringClass));
    }
    return scanner.doScan(StringUtils.toStringArray(basePackages));
  }

如下我们没有配置扫描的包,默认取的包名,后面也就会扫描这个包下及其子包下符合条件的类。

然后就会到如下的方法中。

 ClassPathBeanDefinitionScanner.doScan()

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) {
    // 这个就是获取我们目标组件的方法。也就是获取BeanDefination
      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);
          registerBeanDefinition(definitionHolder, this.registry);
        }
      }
    }
    return beanDefinitions;
  }
 
 //********************************重要***************//
   private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
    // 获取实际的路径。【classpath*:io/renren/**/*.class】
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
          resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
    // 获取对应包下的所有的资源类【所有的类spring封装成了一个资源类】
      for (Resource resource : resources) {
        if (resource.isReadable()) {
          try {
          // 这个使用的ASM技术生成了元素读取器【这个里面有类的各种信息,注解信息也有】,
         // 一般我们可能会使用反射,但是Spring这里使用了一个高级的技术ASM
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            if (isCandidateComponent(metadataReader)) {
              ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
              sbd.setResource(resource);
              sbd.setSource(resource);
              // 这里还有一个判断就是如果一个接口它根本无法实例化,
              // 如果接口也加了Component注解也会把它排除。
              if (isCandidateComponent(sbd)) {
                candidates.add(sbd);
              }
                 
    return candidates;
  } 
  
  // 判断是否是Spring的组件
  protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    // 1:判断是否是要排除的类
    for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        return false;
      }
    }
    // 2:符合includeFilters的会进行条件的匹配,通过了才是Bean
    // 也就是看有没有@Component这个注解
    for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        //这里还有一个条件注解的判断spring boot用的比较多
        return isConditionMatch(metadataReader);
      }
    }
    return false;
  }

 注意,我图片中的描述文字也是很重要的。获取的符合条件的类了,就把这些类封装成BeanDefination。然后Spring在根据这些BeanDefination去实例化Bean。【当然里面还有很多细节我没讲,因为我感觉了解到这些开发中遇到的相关的问题应该就很容易解决了。】具体什么是BeanDefination,可以仔细阅读我的这篇文章。

深入理解Spring的Bean定义对象BeanDefinition-面试重点_程序员xiaozhang的博客-CSDN博客

看了什么是BeanDefinition,后面就是实例化Bean了可以看我的这篇文章

Spring中Bean的实例化详细流程_bean的实例化过程_程序员xiaozhang的博客-CSDN博客

好了读完这三篇文章,我感觉你应该清楚的知道了我们写的一个类Spring是怎样给我们实例化成一个Bean的了。类的初始化主要就是先加载,转换为BeanDefinition,然后再实例化为Bean对象。希望这三篇文章对你理解Spring有所帮助。也欢迎你把这三篇文章分享一下,让更多的人看到。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring扫描类的原理 的相关文章

  • 为什么 hibernate 在一张表中保存两个 @OneToMany 列表?

    想象一下使用 Hibernate 和 JPA 的简化代码如下 Entity class C Id GeneratedValue public long id MappedSuperclass abstract class A Id Gene
  • 如何避免 Java 中的忙旋转

    我有一个多线程应用程序 其中一个线程向另一个线程发送消息 等待线程轮询消息并做出反应 处理锁 像这样 等待线程代码 while true if helloArrived System out println Got hello if bye
  • Maven 2 未运行 Junit 4 测试

    我在确保运行 Junit4 测试时遇到问题 同样的问题也被报告在https stackoverflow com questions 2021771 sort newest sort top https stackoverflow com q
  • JFreeChart - 创建移动图表时出现问题

    我在我的 java 应用程序中使用 JFreeChart Problem 我想绘制一个XY面积图 whose 域轴 x 轴 当我们开始绘制数据时应该自动水平滚动 我在中看到了同样的事情时间序列图表但我不想要任何时间系列图表 我只想要滚动的
  • 到底什么是哈希冲突

    HashMap 中的哈希冲突或哈希冲突并不是一个新主题 我遇到过几个博客和讨论板 以模糊且详细的方式解释如何产生哈希冲突或如何避免它 我最近在一次采访中遇到了这个问题 我有很多事情要解释 但我认为很难准确地给出正确的解释 抱歉 如果我的问题
  • IntSummaryStatistics的summaryStatistics方法

    为什么空 IntStream 上的 summaryStatistics 方法返回整数的最大和最小值作为流中存在的最大和最小 int 值 IntStream intStream IntStream of IntSummaryStatistic
  • 如何使 ScheduledExecutorService 在计划任务取消时自动终止

    我正在使用一个ScheduledExecutorService如果网络连接已打开超过几个小时 则关闭该连接 然而 在大多数情况下 网络连接在超时之前就关闭了 所以我取消了ScheduledFuture 在这种情况下 我还希望执行程序服务终止
  • FXML 文件中的 getHostServices().showDocument()

    有没有简单的方法可以将 getHostServices showDocument 命令放入 toHomepage 方法中 而不是执行一行又一行的代码 这样代码应该看起来干净简单 package sample import javafx ap
  • 在 Java Swing 元素中使用 HTML 样式是不好的做法吗?

    使用 HTML 设置 Swing 元素的样式被认为是不好的做法吗 举个例子 如果我想让标签变大并变红一次 我有两个选择 使用 API 调用 JLabel label new JLabel This is a title label setF
  • Java检测鼠标长按

    如果用户按下 JList 组件超过 3 秒 有什么方法可以捕获事件吗 我发现困难的部分是即使在用户松开鼠标左键之前也需要触发事件 这可以通过 mousePressed 和 mouseReleased 组合轻松完成 您可以在 mouseDow
  • java.lang.ClassCastException: [B 无法转换为 java.lang.String

    我编写了一个带有字段 LoginId 和密码的实体类 我使用 AES ENCRYPT 加密密码并将其存储在数据库中 我只想检索已解密的密码 所以 我使用 AES DECRYPT 使用本机查询是在 OPen JPA 2 0 中 我写的查询是
  • iText7 将 SVG 添加到 PdfDocument 中以及可能出现的问题

    关于问题的答案 如何使用 iText7 将 SVG 添加到 PDF 这是一个链接点击这里 https stackoverflow com questions 50059456 how to add an svg to a pdf using
  • Java 空值检查

    我有一个thread1 if object null object play 和另一个thread2可以写null into object随时参考 我将同时运行这些线程 我知道thread2可以重写object后参考null检查并会抛出Nu
  • logcat 信息出现在 Android Studio 的“运行”选项卡中

    我的 android studio 运行选项卡很简单 然后它变得更难并给我更多信息 例如 logcat 中的信息 如何禁用或删除第二张图片中出现的更多信息并返回到第一张图片中的第一个外观 我只需要正在运行的 flutter 应用程序的日志输
  • 通过 ssh 发送命令并读取输出结果

    我有代码通过 ssh 连接到远程服务器并向其发送 2 个或更多命令 例如 cd export home ops bin和 viewlinkload time 20131205 19 但我没有看到命令执行 也没有收到结果 我需要获取服务器返回
  • 使用 InputStream 通过 TCP 套接字接收多个图像

    每次我从相机捕获图像时 我试图将多个图像自动从我的 Android 手机一张一张地发送到服务器 PC 问题是read 函数仅在第一次时阻塞 因此 从技术上讲 只有一张图像被接收并完美显示 但在那之后当is read 回报 1 该功能不阻塞
  • 有没有办法处理Java堆空间异常[重复]

    这个问题在这里已经有答案了 我正在寻找将文件输入流转换为大文件 文件大小为 100MB 并且抛出 java lang OutOfMemoryError Java Heap space import java io FileInputStre
  • Java 中 .NET 的 Lambda 表达式

    我最近 再次 从 C 迁移到 Java 但我非常怀念 lambda 表达式和 C 的 IEnumerable Foreach 之类的东西 所以我正在寻找Java中的lambda表达式库 有比这更好的图书馆吗LambdaJ http code
  • 删除Java中重载的方法

    有2个重载方法 这些方法中的每一个都将一种类型的列表转换为不同类型的列表 但第一种方法使用比较器 class SomeClass public static
  • 将 JSON 发送到 Spring MVC 控制器

    我正在尝试将 JSON 发送到 Spring MVC 控制器 在 Spring MVC 方面 一切都配置正确 下面是代码 但似乎没有运行

随机推荐

  • PC微信逆向:破解聊天记录文件!

    作者 newx 链接 https bbs pediy com thread 251303 htm 在电子取证过程中 也会遇到提取PC版微信数据的情况 看雪 52破解和CSDN等网上的PC版微信数据库破解文章实在是太简略了 大多数只有结果没有
  • MySQL中通过一条语句来统计符合不同条件的COUNT

    现在有两个表record 和 info 其中表record存放每次通话记录的主动呼出号码与被动呼入号码 表Info存放人名和对应号码 如下 现在的目的是统计每个人的手机号码主动呼出次数与被动呼入次数 就用到下列语句即可 SELECT nam
  • Openlayers 坐标系全面解析

    目录 EPSG 4326 EPSG 3857 EPSG 4326 与 EPSG 3857 的坐标转换 EPSG 4490 Openlayers 自定义坐标系 EPSG 4490 和 EPSG 4525 EPSG 4326 EPSG 3857
  • CTFshow-pwn入门-前置基础pwn23-pwn25

    pwn23 25的题目会涉及到ret2shellcode ret2libc等内容 本篇文章只会侧重研究这几道题目的wp 不会过多涉及到ret2shellcode ret2libc的基本原理 等有时间再来写关于ret2libc ret2she
  • 移动游戏平台的新趋势分享—91-mgas大会

    移动游戏平台的新趋势分享 91 mgas大会 随着智能手机与平板电脑等设备的普及 移动游戏以惊人的速度深入到人们生活当中 玩家的选择范围进一步扩大 包括角色扮演 策略游戏 棋牌游戏 休闲益智 动作冒险 但耐心在降低 而角色扮演和策略游戏的混
  • 【单片机毕业设计】【mcuclub-dz-047】基于单片机的消毒器设计

    最近设计了一个项目基于单片机的消毒器设计 与大家分享一下 一 基本介绍 项目名 基于单片机的消毒器的设计 项目编号 mcuclub dz 047 单片机 STM32F103C8T6 功能简介 1 通过液位传感器检测液位 当液位较低 通过GS
  • java毕业设计——基于JSP+J2EE+sqlserver的超市综合信息管理系统设计与实现(毕业论文+程序源码)——超市综合信息管理系统

    基于JSP J2EE sqlserver的超市综合信息管理系统设计与实现 毕业论文 程序源码 大家好 今天给大家介绍基于JSP J2EE sqlserver的超市综合信息管理系统设计与实现 文章末尾附有本毕业设计的论文和源码下载地址哦 需要
  • 如何在python pyqt窗口中,嵌入notepad、word、计算器

    import sys from PyQt5 QtWidgets import QApplication QWidget from ctypes import 成功了 class App QWidget def init self super
  • GIS状态检测新技术——振动分析法

    提示 唐老师好 我之前因为 阳 了 所以就没有参与汇报 给老师带来不便 请老师见谅 以此篇文章代替课堂汇报 文章目录 前言 一 不同故障对应的振动频谱和故障特征量 二 GIS设备振动特征估计 1 GIS设备状态空间 2 粒子滤波 三 GIS
  • vue+element-ui 项目实战示例详解【目录】

    vue 和 element是两个流行的前端即时 通常用于管理后台 PC等页面 能够快速构建美观的界面 1 vue2 介绍 Vue js是一个流行的JavaScript框架 用于构建用户界面 它的版本分为Vue 2和Vue 3 而Elemen
  • bootstrap 和 ant design css样式 冲突 导致图标移位

    bootstrap 和 ant design 冲突 导致图标移位 body anticon transform translate 0 5px 3px
  • 命令模式代码示例

    package com example mingling 执行命令的接口 author Administrator public interface Command void execute package com example ming
  • 【马士兵】Python基础--08(字典)

    Python基础 08 文章目录 Python基础 08 基础知识 字典的组成及原理 字典的创建方式 字典元素的获取 字典元素的增删改操作 获取字典视图 字典元素的遍历 字典生成式 基础知识 可变序列 目前包括字典 列表 不可变序列 目前包
  • 数电学习笔记

    数电学习笔记 背景 笔记正文 背景 在刚开学那段时间把清华大学阎石老师的 数字电子技术基础 第五版又看了一遍 记了点笔记 刚好实验室的打印机有扫描功能 于是把笔记分享一下 笔记正文 以上
  • jni中如何查看函数签名

    操作步骤 第一步 找到 build 文件夹 第二步 找到 javac 文件夹 第三步 找到自己写的 xxx class文件 第四步 右键 xxx class 文件 在 Terminal 中打开 第五步 执行 javap s xxx clas
  • 飞浆(paddle)实现机器学习

    一 飞浆 paddle 介绍 飞桨是国内唯一功能完备的端到端开源深度学习平台 集深度学习训练和预测框架 模型库 工具组件和服务平台为一体 拥有兼顾灵活性和高性能的开发机制 工业级应用效果的模型 超大规模并行深度学习能力 推理引擎一体化设计以
  • [机缘参悟-88]:什么是平台?国家、公司、家庭、硬件、软件、应用?

    目录 前言 1 什么是平台 1 1 英文是platform 1 2 百度百科 1 3 平台的现实案例 2 平台的特征 2 1 相对性 2 2 层次性 2 3 广泛性 第3章 三大系统 3 1 软硬件系统中的平台 3 2 人类社会的平台 3
  • linux网卡team0,team

    1 安装teamd root web01 yum y install teamd 2 停止NetworkManager什么是NetworkManager呢 NetworkManager服务是管理和监控网络设置的守护进程 CentOS7更加注
  • 直方图均衡化算法、直方图匹配算法 C++ 代码

    这两天一直在研究匀光匀色算法才了解到了直方图匹配算法 想要了解这个算法又要先了解直方图均衡化算法 通过网上查找了很多资料 没有现成C 代码 经过仔细思考和实验后大概复现了该算法 特此记录 以备查阅 参考链接如下 1 匀光匀色 直方图匹配算法
  • Spring扫描类的原理

    作为Java的开发者Spring可以称之为神一样的存在框架 好处太多无法用言语表达只能称之为Java排名的number one 框架 我们使用Spring它帮助我们实例化了很多Bean对象 但是这些Bean是怎样加载到Spring容器中的呢