Spring Bean的包扫描的实现方法
作者:qq1984654893 发布时间:2021-10-21 12:40:07
我们知道,Spring
可以通过包扫描将使用@Component
注解定义的Bean
定义到容器中。今天就来探究下他实现的原理。
首先,找到@Component注解的处理类
注解的定义,一般都需要配套的对注解的处理才能完成注解所代表的功能。所以我们通过@Component
注解的用到的地方,来查找可能的处理逻辑;
我们先进入Spring
的项目,在IDEA
里面用Ctrl
和鼠标左键点击Component
注解的名称,IDEA
会显示出使用到这个类的位置,我们从弹出的列表中找到一个名称像的类,去看类上面的注释说明,如图:
我们点进类中,可以看到第一行就说了这个类是为了从classpath
里面找到定义的Bean
:
分析具体方法
一般Spring
的类都是经过设计的,职责清晰。所以一般都是有简单直接的接口暴露,我们打开类的公开API
可以看到有个很直接的方法就叫做扫描,看看注释说“从指定的包中扫描Bean”,那就是它了。
然后,我们为了确认,实现确实是通过这个方法,可以启动程序,打个断点看看是否经过这里(但是这这里,没有调用scan()
方法,而是更深一层的doScan
方法,也确实费解)。
我们进入doScan()
方法看看实现:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 可以指定多个basePackage,这里就对每个都处理
for (String basePackage : basePackages) {
// 这个方法是真正的查找候选Bean的地方
Set<BeanDefinition> 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
包方式寻找资源的方式略有不同,但是思路是一致的,都是按照第一点查找;
来源:https://blog.csdn.net/qq1984654893/article/details/112441000
猜你喜欢
- 一、async和await特性的结构1. 异步和同步同步方法:如果一个方法被调用了,等待其执行所有处理后调用方法才继续执行的方法。异步方法:
- 返回集合为null还是空集合及空集合的三种写法个人认为在自己写接口时,需要返回集合时返回一个空集合,比如mybatis查询如果返回一个集合,
- 目录为什么要用异步框架,它解决什么问题?SpringBoot如何实现异步调用?实现异步调用为什么要给@Async自定义线程池?多个线程池处理
- List集合相信大家在开发过程中几乎都会用到。有时候难免会遇到集合里的数据是重复的,需要进行去除。然而,去重方式有好几种方式,你用的是哪种方
- 开发环境使用jdk1.8.0_60,把springboot 项目打成war包后,部署到apache-tomcat-7.0.68时报错如下,换
- 大家在银行交易某些业务时,都可以看到无论是身份证、银行账号中间部分都是用*号替换的,下面小编把代码整理如下:/// <summary&
- lombok插件使用引入依赖,在项目中使用Lombok可以减少很多重复代码的书写。比如说getter/setter/toString等方法的
- 背景:当我们有需求将HashMap转为Json格式的String时,切记不要使用HashMap的toString()方法,需要使用FastJ
- zookeeper集群配置多个实例共同构成一个集群对外提供服务以达到水平扩展的目的,每个服务器上的数据是相同的,每一个服务器均可以对外提供读
- 在Java编程中,使用private关键字修饰了某个成员,只有这个成员所在的类和这个类的方法可以使用,其他的类都无法访问到这个private
- 简介最近几年,各种新的高效序列化方式层出不穷,不断刷新序列化性能的上限,最典型的包括:专门针对Java语言的:Kryo,FST等等跨语言的:
- Java枚举类型enum的详解及使用最近跟同事讨论问题的时候,突然同事提到我们为什么Java 中定义的常量值不采用enmu 枚举
- C#中,有些类型是可以隐式转换的,我整理了这些可以隐式转换的类型,供大家参考 static
- 一、前言最近写了个项目,前端还没写,需要部署到服务器给女朋友实现前端,可是不熟悉Linux的我,蹑手蹑脚,真的是每一步都是bug,可谓是步步
- Struts2是流行和成熟的基于MVC设计模式的Web应用程序框架。 Struts2不只是Struts1下一个版本,它是一个完全重写的Str
- 由于在项目中要实现用户注册的邮箱激活以及忘记密码重置密码功能,所以通过查阅资料做了一个简单的设计和实现。邮箱激活背景:几乎每个网站或论坛之类
- Java自定义注解一般使用场景为:自定义注解+ * 或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。本文将先从自定义注解的
- 纯Java代码模拟Hibernate一级缓存原理,简单易懂。import java.util.ArrayList;import java.u
- 最近工作遇到一个需求,需要下载excel模板,编辑后上传解析存储到数据库。因此为了更好的理解公司框架,我就自己先用spring mvc实现了
- 概念final 具有“不可改变的”的含义,可以修饰 非抽象类、非抽象成员方法和变量。用 final 修饰的类不能被继承,没有子类。用 fin