深度讲解Spring Bean扫描类:源码深度剖析与实战策略

1. 引言

在Spring框架中,Bean的扫描是一个至关重要的过程,它决定了哪些类会被Spring容器管理并作为Bean实例化。对于高级Java工程师而言,深入理解这一过程不仅有助于提升对Spring框架的掌握程度,还能在实际开发中更加灵活地运用Spring的各项特性。本文将对Spring Bean的扫描类进行深度讲解,并结合源码分析,带您领略其幕后的魔法。


2. Spring Bean扫描的基本概念

Spring Bean扫描是Spring框架在启动时自动扫描指定包路径下的类,并根据类上的注解(如@Component、@Service、@Repository、@Controller等)将其实例化为Spring Bean的过程。这一过程由Spring的ClassPathBeanDefinitionScanner类负责实现。


3. 初始化扫描器

ClassPathBeanDefinitionScanner的初始化过程中,会设置一些关键属性,包括resourceLoader(资源加载器)、environment(环境)、registry(注册中心)和includeFilters/excludeFilters(包含/排除过滤器)。

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,  Environment environment, @Nullable ResourceLoader resourceLoader) {  // ...  this.registry = registry;  if (useDefaultFilters) {  // 默认会添加@Component, @Repository, @Service, @Controller等注解的过滤器  registerDefaultFilters();  }  // ...  this.resourceLoader = resourceLoader;  this.environment = environment;  
}

4. 扫描包路径

scan方法是扫描过程的入口,它首先会确定要扫描的包路径,然后调用doScan方法执行实际的扫描。doScan方法是执行扫描的核心方法。它接收一个或多个基础包路径作为参数,并找到这些包下所有的类。

public int scan(String... basePackages) {  int beanCountAtScanStart = this.registry.getBeanDefinitionCount();  doScan(basePackages);  // Post-process after scanning to resolve any placeholder values in bean definitions.  if (this.includeAnnotationConfig) {  AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry, this.environment);  }  return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);  
}  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {  // ... 省略了部分代码 ...  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();  for (String basePackage : basePackages) {  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 (!isCandidateComponent(candidate, beanName, candidates)) {  continue;  }  // 检查Bean是否已经存在  if (!checkCandidate(beanName, candidate)) {  continue;  }  BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);  definitionHolder =  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);  beanDefinitions.add(definitionHolder);  // 注册BeanDefinition  registerBeanDefinition(definitionHolder, this.registry);  }  }  return beanDefinitions;  
}

在上面的代码中,findCandidateComponents方法负责查找指定包下的所有类,并应用TypeFilter进行过滤。然后,对于每个候选组件,它会生成Bean的名称,并检查是否已经存在同名的Bean。如果不存在,则将其注册到BeanDefinitionRegistry中。


5. 加载类并检查注解

findCandidateComponents方法是扫描包路径并找到带有指定注解的类的关键方法。它使用了ClassPathScanningCandidateComponentProviderfindCandidateComponents方法,该方法会遍历包路径下的所有类,并使用isCandidateComponent方法检查每个类是否满足条件。

protected Set<BeanDefinition> findCandidateComponents(String basePackage) {  // ...  Set<BeanDefinition> candidates = new LinkedHashSet<>();  try {  String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +  resolveBasePackage(basePackage) + '/' + this.resourcePattern;  Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);  for (Resource resource : resources) {  if (resource.isReadable()) {  try {  MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);  if (isCandidateComponent(metadataReader)) {  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);  sbd.setSource(resource);  if (isCandidateComponent(sbd)) {  candidates.add(sbd);  }  }  } catch (Throwable ex) {  // ...  }  }  }  } catch (IOException ex) {  // ...  }  return candidates;  
}  protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {  // 检查类上是否有指定的注解  for (TypeFilter tf : this.includeFilters) {  if (tf.match(metadataReader, getMetadataReaderFactory())) {  return true;  }  }  // ...  return false;  
}

6. 创建并注册Bean定义

一旦找到了满足条件的类,ClassPathBeanDefinitionScanner会为其创建一个BeanDefinition对象,并将其注册到BeanDefinitionRegistry中。这通常是通过调用registry.registerBeanDefinition方法完成的。

protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {  String beanName = definitionHolder.getBeanName();  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());  
}

7. 扫描过程中的过滤器

在Spring Bean扫描过程中,TypeFilter起着关键作用。它允许我们定义哪些类应该被包括在扫描结果中,哪些类应该被排除。Spring提供了几种默认的TypeFilter实现,比如AnnotationTypeFilter,它可以基于类上的特定注解来过滤类。

ClassPathBeanDefinitionScanner的初始化过程中,如果设置了useDefaultFilterstrue,则会注册几个默认的TypeFilter,这些过滤器会包含带有@Component@Repository@Service@Controller等注解的类。


8. 扫描路径的解析

scan方法中,传入的basePackages参数指定了要扫描的包路径。ClassPathBeanDefinitionScanner会使用PathMatchingResourcePatternResolver来解析这些路径,并找到对应的资源(即类文件)。这个过程中,会使用ResourcePattern(默认为**/*.class)来匹配类文件。


9. 处理扫描结果

doScan方法中,扫描得到的候选组件会被封装成BeanDefinitionHolder对象,并存储在Set<BeanDefinitionHolder>集合中。这些BeanDefinitionHolder对象包含了Bean的定义信息以及Bean的名称。

然后,ClassPathBeanDefinitionScanner会遍历这个集合,并为每个BeanDefinitionHolder对象调用registerBeanDefinition方法,将其注册到BeanDefinitionRegistry中。这样,这些Bean就被成功地纳入了Spring容器的管理范围。


10. 自定义扫描行为

除了使用Spring提供的默认扫描行为外,我们还可以通过自定义TypeFilterResourcePattern等方式来扩展扫描过程。比如,我们可以定义一个自定义的TypeFilter来只扫描带有特定注解的类,或者通过修改ResourcePattern来匹配特定模式的类文件。


11. 扫描性能优化

由于扫描过程可能涉及大量的类加载和反射操作,因此性能可能会受到影响。为了优化性能,Spring提供了一些策略,如使用缓存来存储已扫描的类信息,避免重复扫描。此外,你还可以通过限制扫描的包范围、减少不必要的过滤器等方式来减少扫描的工作量。


12. 实战策略与技巧

12.1 优化扫描性能

在实际开发中,如果扫描的包路径过大或包含过多的类,可能会导致扫描过程耗时较长。为了优化性能,可以采取以下策略:

  • 缩小扫描范围:只扫描必要的包路径或目录。
  • 使用排除过滤器:将不需要扫描的类或包路径添加到排除过滤器中。
  • 延迟加载:通过配置lazy-init属性,使Bean在首次使用时才进行实例化,从而减少启动时的加载压力。
12.2 自定义扫描规则

通过实现自定义的TypeFilter接口,可以定义自己的扫描规则,以满足特定的业务需求。例如,可以根据类的命名规范或特定属性来过滤需要扫描的类。

12.3 与其他技术的结合

可以将Spring Bean扫描与其他技术(如AspectJ、JPA等)结合使用,以实现更强大的功能。例如,可以使用AspectJ的切点表达式来定义需要扫描的类范围,或者使用JPA的实体管理器来管理扫描到的实体类。


13. 总结

Spring Bean扫描类是Spring框架中一个非常重要的组件,它负责自动检测并注册类为Spring容器中的Bean实例。通过结合底层源码的分析,我们可以深入理解其工作原理和内部实现。同时,我们也可以通过自定义扫描行为和优化扫描性能来满足项目的实际需求。


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

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

相关文章

归并排序算法(经典、常见)

今天我们不刷力扣了&#xff0c;我们来复习&#xff08;手撕&#xff09;一下数据结构中的八大排序算法之一&#xff0c;归并排序 基本概念&#xff1a; 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&am…

SpringBoot中如何在服务器进行校验?

数据校验就是数据的合法性检查&#xff0c;在服务器端也可以对数据进行校验&#xff0c;一般使用JSR303 校验 JSR303是Java为Bean数据合法性校验提供的标准框架&#xff0c;是一种声明式校验 JSR303通过在Bean属性上标注类似于NotNull、Max等注解来指定校验规则&#xff0c;并…

小苯的排列构造(最大公约数,构造,数学推导)

文章目录 题目描述输入格式输出格式样例输入1样例输出1提交链接提示 解析参考代码 题目描述 格格有一个长度为 n n n 的排列 p p p&#xff0c;但她不记得 p p p 具体的样子&#xff0c;她只记得数组 a a a。 其中&#xff1a; a i g c d ( p 1 , p 2 , . . . , p i ) a…

【网络技术】【Kali Linux】Wireshark嗅探(十五)SSDP(简单服务发现协议)报文捕获及分析

往期 Kali Linux 上的 Wireshark 嗅探实验见博客&#xff1a; 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;一&#xff09;ping 和 ICMP 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;二&#xff09;TCP 协议 【网络技术】【Kali Linux】Wireshark嗅探&…

与MySQL DDL 对比分析OceanBase DDL的实现

本文将简要介绍OceanBase的DDL实现方式&#xff0c;并通过与MySQL DDL实现的对比&#xff0c;帮助大家更加容易理解。 MySQL DDL 的算法 MySQL 的DDL实现算法主要有 copy、inplace和instant。 copy copy算法的实现相对简单&#xff0c;MySQL首先会创建一个临时表&#xff0…

C++:STL

STL 文章目录 STLSTL 绪论迭代器&#xff08;iterators&#xff09;容器&#xff08;Containers&#xff09;vectorset,multisetmap,multimapstackqueuedequepriority_queuebitset 算法&#xff08;Algorithms&#xff09;sort,count,find,lower_bound,upper_bound,binary_sear…

(2024,attention,可并行计算的 RNN,并行前缀扫描)将注意力当作 RNN

Attention as an RNN 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1 注意力作为一种&#xff08;多对一的&#xff09;RNN 3.2 注意力作为&#xff08;多对多&…

多语言印度红绿灯系统源码带三级分销代理功能

前端为2套UI&#xff0c;一套是html写的&#xff0c;一套是编译后的前端 后台功能很完善&#xff0c;带预设、首充返佣、三级分销机制、代理功能。 东西很简单&#xff0c;首页就是红绿灯的下注页面&#xff0c;玩法虽然单一&#xff0c;好在不残缺可以正常跑。

Putty: 随心御剑——远程启动服务工具plink

一、引言:如何远程控制 也许你会有这样的场景,交互程序(以下简称UI程序)跑在windows端,而控制程序跑在Linux上。我们想要通过windows端 UI程序来启动Linux下面的服务,来一场酣畅淋漓的御剑飞行咋办,难道要自己十年磨一剑,在Linux下编写一个受控服务程序么.计算机科技发…

【MATLAB】信号的熵

近似熵、样本熵、模糊熵、排列熵|、功率谱熵、奇异谱熵、能量熵、包络熵 代码内容&#xff1a; 获取代码请关注MATLAB科研小白的个人公众号&#xff08;即文章下方二维码&#xff09;&#xff0c;并回复信号的熵本公众号致力于解决找代码难&#xff0c;写代码怵。各位有什么急需…

FreeRTOS中断中释放信号量

串口接收&#xff1a;中断程序中逆序打印字符串 串口接收&#xff1a;逆序回环实验思路 注&#xff1a;任务优先级较高会自动的切换上下文进行运行 FreeRTOS中的顶半操作和底半操作 顶半操作和底半操作“这种叫法源自与Linux”在嵌入式开发中&#xff0c;为了和Linux操作系统做…

kafka 案例

kafka 案例 目录概述需求&#xff1a; 设计思路实现思路分析1.kafka案例_API 带回调函数的生产者2.kafka案例_API生产者分区策略测试3.kafka案例_自定义分区的生产者4.kafka案例_API同步发送生产者5.kafka案例_API简单消费者5.kafka案例_API消费者重置offset 参考资料和推荐阅读…

Linux网络编程: 网络基础

Linux网络编程: 网络基础 1.网络划分 一.网络通信1.网络通信和本地通信的联系2.网络通信与本地通信最本质的区别及其衍生出的问题 二.网络协议初识1.为何本地通信不行&#xff1f;2.打电话的例子体会分层模型的好处3.OSI七层模型的提出4.OSI七层模型与TCP/IP五层模型1.TCP/IP与…

二十九篇:构建未来:信息系统的核心框架与应用

构建未来&#xff1a;信息系统的核心框架与应用 1. 引言 在这个充满挑战和机遇的信息时代&#xff0c;信息系统已经成为现代组织不可或缺的神经中枢。它们不仅革新了我们处理信息的方式&#xff0c;更是极大地增强了决策制定的效率和质量。在这篇文章中&#xff0c;我将分享我…

php 一个数组中的元素是否在一个字符串中包含

php 一个数组中的元素是否在一个字符串中包含 要检查一个数组中的元素是否在一个字符串中出现&#xff0c;你可以使用strpos()函数。这个函数返回子字符串首次出现的位置索引&#xff0c;如果没有找到&#xff0c;它会返回false。 $array [apple, banana, cherry]; $string …

Java进阶-SpringCloud使用BeanUtil工具类简化对象之间的属性复制和操作

在Java编程中&#xff0c;BeanUtil工具类是一种强大且便捷的工具&#xff0c;用于简化对象之间的属性复制和操作。本文将介绍BeanUtil的基本功能&#xff0c;通过详细的代码示例展示其应用&#xff0c;并与其他类似工具进行对比。本文还将探讨BeanUtil在实际开发中的优势和使用…

有效的字母异位词-力扣

看到题目首先想到的解法是使用unordered_map容器来做&#xff0c;遍历第一个字符串&#xff0c;统计每个char出现的次数&#xff0c;然后遍历第二个字符串&#xff0c;出现一个char&#xff0c;map对应值就减一&#xff0c;最后判断map容器所有键值是否为0&#xff0c;如果全部…

5.26牛客循环结构

1002. 难点&#xff1a; 两层循环条件设置 思路 可以设置三个变量 代码 1003 思路&#xff1a; 与星号双塔差不多&#xff0c;在此基础上加大一点难度 每日练题5.23 &#xff08;EOF用法&#xff09;-CSDN博客 代码 1004 代码

Android Compose 八:常用组件 Switch

Switch 切换按钮 val isChecked remember { mutableStateOf(true) }Switch(checked isChecked.value,onCheckedChange {Log.i("text_compose","onCheckedChange>>"it)isChecked.value it})效果 默认颜色 应该对应 主题色 1.1 thumbContent 按钮…

mapreduce综合应用案例 — 招聘数据清洗

MapReduce是一种编程模型&#xff0c;用于处理和生成大数据集。它通过Map和Reduce两个步骤来实现数据的分布式处理。在招聘数据清洗的场景中&#xff0c;MapReduce可以用来处理大规模的招聘数据集&#xff0c;以提取、清洗和转换数据&#xff0c;为进一步的分析和决策提供支持。…