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

深入探究 Spring 的扫描原理

 在 Spring 框架中,扫描机制是其核心功能之一,它负责在应用程序启动时自动发现和注册相关的组件。本文将深入探讨 Spring 的扫描原理,包括扫描过程以及相关源码的分析。

      spring提供了两种扫描方式来进行扫描:1.我们可以通过AnnotationConfigApplicationContext的scan方法中进行设置固定的包名路径;2.我们可以通过@ComponentScan注解来进行配置要进行扫描的包路径。这两种方式底层都是进行执行的是ClassPathBeanDefinitionScanner类来进行扫描的操作。这两种方式的主要区别在于执行的时机不同,本文介绍@ComponentScan注解来进行配置的扫描方式

1.spring的扫描时机

       spring 容器初始化的时候,会进行执行invokeBeanFactoryPostProcessors方法,内部会进行调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法的后完成了bean的扫描操作。

2.spring 扫描流程

  通过我们对代码的分析,我们发现spring进行扫描的入口为org.springframework.context.annotation.ComponentScanAnnotationParser#parse。

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {//扫描器的初始化操作	ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);//获取名字生成器的策略 (针对的是bean的名字)Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :BeanUtils.instantiateClass(generatorClass));ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");if (scopedProxyMode != ScopedProxyMode.DEFAULT) {scanner.setScopedProxyMode(scopedProxyMode);}else {Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));}//获取扫描的包路径scanner.setResourcePattern(componentScan.getString("resourcePattern"));//可以自己添加一些include的过滤器//mybatis 框架对此有一些自己的扩展for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addIncludeFilter(typeFilter);}}// 添加排除扫描的过滤器for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addExcludeFilter(typeFilter);}}boolean lazyInit = componentScan.getBoolean("lazyInit");if (lazyInit) {scanner.getBeanDefinitionDefaults().setLazyInit(true);}// 扫描的包路径的配置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));}scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {@Overrideprotected boolean matchClassName(String className) {return declaringClass.equals(className);}});// 开始进行扫描return scanner.doScan(StringUtils.toStringArray(basePackages));}

2.1 扫描器的初始化操作

通过源码,我们可以看到spring的扫描器初始化的时候默认进行注册了三个include过滤器。

	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,Environment environment, @Nullable ResourceLoader resourceLoader) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");this.registry = registry;// 针对@CompentScan注解这个值默认为trueif (useDefaultFilters) {registerDefaultFilters();}setEnvironment(environment);setResourceLoader(resourceLoader);}

进行构建ClassPathBeanDefinitionScanner类对象的时候会进行注册三个默认的include过滤器。从代码中我们可以看出这三种过滤器都是AnnotationTypeFilter类型。这三种过滤器分别是针对@Component,@ManagedBean,@Named三种注解进行扫描操作。这块有一个小问题为啥@Component注解和@ManagedBean,@Named的写法不一致呢,有大神知道的话,可以告诉我一下

@SuppressWarnings("unchecked")protected void registerDefaultFilters() {this.includeFilters.add(new AnnotationTypeFilter(Component.class));ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.}try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.}}

2.2 根据包名进行解析

     解析@CompentScan注解得到包名, 然后根据包名扫描包下所有的文件, 遍历这些文件,通过ASM字节码技术读取这些文件信息,封装成一个metadataReader对象。调用isCandidateComponent方法,进行判断是否排除,是否添加。如果通过include的filter 则进行实例化成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对象			
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}try {//将resource对象 封装成metaDataReaderMetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// includeFilter和excludeFilter的双重过滤if (isCandidateComponent(metadataReader)) {//将metadataReader对象封装成一个ScannedGenericBeanDefinition 对象ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);// 进行判断ScannedGenericBeanDefinition 对象是否是接口// 是否是抽象类,是否加了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;}
 
 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 探索国产编程工具:如何实现工作效率翻倍
  • VMware安装Ubuntu虚拟机
  • linux 安装redis
  • 以太网--TCP/IP协议(一)
  • “阡陌云旅”黄河九省文化旅游平台
  • LabVIEW FIFO详解
  • docker容器高效连接 Redis 的方式
  • LabVIEW灵活集成与调试的方法
  • 【Hot100】LeetCode—763. 划分字母区间
  • 线程的四种操作
  • python内置模块datetime.datetime类详细介绍
  • 2024.9.8
  • Android Framework(四)WMS-窗口显示流程——窗口创建与添加
  • 搭建一个本地 SMTP 服务器
  • 08-图8 How Long Does It Take(C)
  • 03Go 类型总结
  • Bootstrap JS插件Alert源码分析
  • gitlab-ci配置详解(一)
  • js 实现textarea输入字数提示
  • React-Native - 收藏集 - 掘金
  • Webpack入门之遇到的那些坑,系列示例Demo
  • Zepto.js源码学习之二
  • 阿里云购买磁盘后挂载
  • 成为一名优秀的Developer的书单
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 十年未变!安全,谁之责?(下)
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 消息队列系列二(IOT中消息队列的应用)
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • RDS-Mysql 物理备份恢复到本地数据库上
  • 通过调用文摘列表API获取文摘
  • ​卜东波研究员:高观点下的少儿计算思维
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • #### go map 底层结构 ####
  • (1)Hilt的基本概念和使用
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (第二周)效能测试
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (分享)一个图片添加水印的小demo的页面,可自定义样式
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (南京观海微电子)——I3C协议介绍
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (五)c52学习之旅-静态数码管
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (转)程序员技术练级攻略
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .Net core 6.0 升8.0
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET构架之我见
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • /usr/bin/perl:bad interpreter:No such file or directory 的解决办法
  • [16/N]论得趣