热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

SpringBean的包扫描的实现方法

这篇文章主要介绍了SpringBean的包扫描的实现方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

我们知道,Spring可以通过包扫描将使用@Component注解定义的Bean定义到容器中。今天就来探究下他实现的原理。

首先,找到@Component注解的处理类

注解的定义,一般都需要配套的对注解的处理才能完成注解所代表的功能。所以我们通过@Component注解的用到的地方,来查找可能的处理逻辑;
我们先进入Spring的项目,在IDEA里面用Ctrl和鼠标左键点击Component注解的名称,IDEA会显示出使用到这个类的位置,我们从弹出的列表中找到一个名称像的类,去看类上面的注释说明,如图:

在这里插入图片描述

我们点进类中,可以看到第一行就说了这个类是为了从classpath里面找到定义的Bean

在这里插入图片描述

分析具体方法

一般Spring的类都是经过设计的,职责清晰。所以一般都是有简单直接的接口暴露,我们打开类的公开API可以看到有个很直接的方法就叫做扫描,看看注释说“从指定的包中扫描Bean”,那就是它了。

在这里插入图片描述

然后,我们为了确认,实现确实是通过这个方法,可以启动程序,打个断点看看是否经过这里(但是这这里,没有调用scan()方法,而是更深一层的doScan方法,也确实费解)。

我们进入doScan() 方法看看实现:

protected Set doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set beanDefinitiOns= new LinkedHashSet<>();
		// 可以指定多个basePackage,这里就对每个都处理
		for (String basePackage : basePackages) {
		  // 这个方法是真正的查找候选Bean的地方
			Set candidates = findCandidateComponents(basePackage);
			// 对于每个查找出的候选Bean,进行处理
			for (BeanDefinition candidate : candidates) {
			  // 解析@Scope的元数据
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				// 为候选的Bean生成一个名称
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				// 应用后置处理器
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				// 
				// 处理一些其它通用的注解的元数据
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				// 校验通过后,注册到 BeanFactory
				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;
	}

从方法中我们可以明显的看到,核心代码还在findCandidateComponents方法里面,我们进入这个方法后再通过调试一直找到核心代码scanCandidateComponents。如下图,第一处是找到指定包路径所代表的classpath中的资源对象, 但是这里只是找到了包下面有什么,但是还不知道包下面的类是不是一个候选的Bean(可以看到将DTO类也扫描到了)。如下:

在这里插入图片描述

正常思路,拿到了有哪些资源就该进一步去筛选,看看这些资源有哪些是真正的Bean的定义类。

现在我们还不清楚的是,Spring通过什么方式知道一个类是否是真正的Bean的。我们继续调试,到上图的430行debug进去看看,可以走到org.springframework.core.type.classreading.SimpleMetadataReader这个类的构造器中,如下:

SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
    // 通过流读取资源的内容,现在这个资源可以认为是我们的类
		InputStream is = new BufferedInputStream(resource.getInputStream());
		ClassReader classReader;
		try {
		  // 这个Reader的构造器中就将流读取完毕了
			classReader = new ClassReader(is);
		}
		catch (IllegalArgumentException ex) {
		  // 通过这个异常的信息,可以推测出,其实这里是通过ASM读取Class文件的定义了
			throw new NestedIOException("ASM ClassReader failed to parse class file - " +
					"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
		}
		finally {
			is.close();
		}

    // 这里根据命名可以推测是访问者模式来暴露注解的元数据
		AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);

		// 这个accpect方法也是访问者模式中的典型方法,在这里面,是数据的解析逻辑
		classReader.accept(visitor, ClassReader.SKIP_DEBUG);

		this.annotatiOnMetadata= visitor;
		// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
		this.classMetadata = visitor;
		this.resource = resource;
	}

我们在进入classReader.accept方法,这里面可以看到reader对于Class文件的的按字节解析。

在这里插入图片描述

例如,下面读取的类声明,类注解都是包扫描需要的类元数据:

在这里插入图片描述

拿到这些元数据之后,就按照包扫描的过滤器就过滤出真正需要的类,作为候选的Bean

在这里插入图片描述

获取到元数据之后,就可以按部就班对Bean进行注册、初始化等一系列逻辑啦~

总结

  • 包扫描是通过读取包对应的类路径下的class文件后,对class文件进行解析元数据的方式,确定了Bean的定义的;
  • 本地IDEA的启动方式可能和Jar包方式寻找资源的方式略有不同,但是思路是一致的,都是按照第一点查找;

到此这篇关于Spring Bean的包扫描的实现方法的文章就介绍到这了,更多相关Spring Bean扫描包内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文详细介绍了如何使用Spring Boot进行高效开发,涵盖了配置、实例化容器以及核心注解的使用方法。 ... [详细]
  • 本文详细探讨了Java中StringBuffer类在不同情况下的扩容规则,包括空参构造、带初始字符串和指定初始容量的构造方法。通过实例代码和理论分析,帮助读者更好地理解StringBuffer的内部工作原理。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 深入了解 Windows 窗体中的 SplitContainer 控件
    SplitContainer 控件是 Windows 窗体中的一种复合控件,由两个可调整大小的面板和一个可移动的拆分条组成。本文将详细介绍其功能、属性以及如何通过编程方式创建复杂的用户界面。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文探讨了在Linux系统上使用Docker时,通过volume将主机上的HTML5文件挂载到容器内部指定目录时遇到的403错误,并提供了解决方案和详细的操作步骤。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 作为一名专业的Web前端工程师,掌握HTML和CSS的命名规范是至关重要的。良好的命名习惯不仅有助于提高代码的可读性和维护性,还能促进团队协作。本文将详细介绍Web前端开发中常用的HTML和CSS命名规范,并提供实用的建议。 ... [详细]
  • 本文探讨了在 ASP.NET MVC 5 中实现松耦合组件的方法。通过分离关注点,应用程序的各个组件可以更加独立且易于维护和测试。文中详细介绍了依赖项注入(DI)及其在实现松耦合中的作用。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
  • 本文将深入探讨如何在不依赖第三方库的情况下,使用 React 处理表单输入和验证。我们将介绍一种高效且灵活的方法,涵盖表单提交、输入验证及错误处理等关键功能。 ... [详细]
author-avatar
书友33531938
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有