Spring IoC 源码分析 (基于注解) 之 包扫描
在上篇文章Spring IoC 源码分析 (基于注解) 一我们分析到,我们通过AnnotationConfigApplicationContext类传入一个包路径启动Spring之后,会首先初始化包扫描的过滤规则。那我们今天就来看下包扫描的具体过程。
还是先看下面的代码:
AnnotationConfigApplicationContext类
//该构造函数会自动扫描以给定的包及其子包下的所有类,并自动识别所有的Spring Bean,将其注册到容器中
public AnnotationConfigApplicationContext(String... basePackages) {
//初始化
this();
//扫描包、注册bean
scan(basePackages);
refresh();
}
上文我们分析了this()方法,会去初始化AnnotatedBeanDefinitionReader读取器和ClassPathBeanDefinitionScanner扫描器,并初始化扫描过滤规则。
接下来我们看一下scan(basePackages)方法:
一直跟踪下去,发现调用了ClassPathBeanDefinitionScanner类中的scan()方法
//调用类路径Bean定义扫描器入口方法
public int scan(String... basePackages) {
//获取容器中已经注册的Bean个数
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//启动扫描器扫描给定包
doScan(basePackages);
// Register annotation config processors, if necessary.
//注册注解配置(Annotation config)处理器
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
//返回注册的Bean个数
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
可以看到主要是doScan(basePackages)方法实现了扫描的逻辑,我们继续跟踪进去看下
//类路径Bean定义扫描器扫描给定包及其子包
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
//创建一个集合,存放扫描到Bean定义的封装类
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
//遍历扫描所有给定的包
for (String basePackage : basePackages) {
//调用父类ClassPathScanningCandidateComponentProvider的方法
//扫描给定类路径,获取符合条件的Bean定义
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
//遍历扫描到的Bean
for (BeanDefinition candidate : candidates) {
//获取@Scope注解的值,即获取Bean的作用域
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
//为Bean设置作用域
candidate.setScope(scopeMetadata.getScopeName());
//为Bean生成名称
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
//如果扫描到的Bean不是Spring的注解Bean,则为Bean设置默认值,
//设置Bean的自动依赖注入装配属性等
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
//如果扫描到的Bean是Spring的注解Bean,则处理其通用的Spring注解
if (candidate instanceof AnnotatedBeanDefinition) {
//处理注解Bean中通用的注解,在分析注解Bean定义类读取器时已经分析过
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//根据Bean名称检查指定的Bean是否需要在容器中注册,或者在容器中冲突
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
//根据注解中配置的作用域,为Bean应用相应的代理模式
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//向容器注册扫描到的Bean
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
这一大段代码基本上就是spring扫描识别注解,并注册Bean到IOC容器中的代码。
在第10行有一个findCandidateComponents(basePackage)方法,这个方法里就是具体的扫描逻辑。
继续跟踪:
ClassPathScanningCandidateComponentProvider类
//扫描给定类路径的包
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
//spring5.0开始 索引 开启的话生成文件META-INF/spring.components 后面加载直接从本地文件读取(一般不建议开启 spring.index.ignore=true)
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
这里有一个if判断,我们默认走的是else里的分支,即scanCandidateComponents(basePackage)方法。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
//补全扫描路径,扫描所有.class文件 classpath*:com/mydemo/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//定位资源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
//通过ASM获取class元数据,并封装在MetadataReader元数据读取器中
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//判断该类是否符合@CompoentScan的过滤规则
//过滤匹配排除excludeFilters排除过滤器(可以没有),包含includeFilter中的包含过滤器(至少包含一个)。
if (isCandidateComponent(metadataReader)) {
//把元数据转化为 BeanDefinition
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
//判断是否是合格的bean定义
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 {
//不符@CompoentScan过滤规则
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
这里就是主要的扫描逻辑,代码中的注释已经说的很清楚了。
主要过程:
根据包路径,扫描所有.class文件
根据包路径,生成.class对应的Resource对象
通过ASM获取class元数据,并封装在MetadataReader元数据读取器中
判断该类是否符合过滤规则
判断该类是否为独立的类、具体的类
加入到集合中
我们来详细看下过滤的方法 isCandidateComponent(metadataReader)
//判断元信息读取器读取的类是否符合容器定义的注解过滤规则
//@CompoentScan的过滤规则支持5种 (注解、类、正则、aop、自定义)
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//如果读取的类的注解在排除注解过滤规则中,返回false
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
//如果读取的类的注解在包含的注解的过滤规则中,则返回ture
for (TypeFilter tf : this.includeFilters) {
//判断当前类的注解是否match规则
if (tf.match(metadataReader, getMetadataReaderFactory())) {
//是否有@Conditional注解,进行相关处理
return isConditionMatch(metadataReader);
}
}
//如果读取的类的注解既不在排除规则,也不在包含规则中,则返回false
return false;
}
接着跟踪 tf.match()方法
AbstractTypeHierarchyTraversingFilter类
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// This method optimizes avoiding unnecessary creation of ClassReaders
// as well as visiting over those readers.
//检查当前类的注解是否符合规律规则
if (matchSelf(metadataReader)) {
return true;
}
//check 类名是否符合规则
ClassMetadata metadata = metadataReader.getClassMetadata();
if (matchClassName(metadata.getClassName())) {
return true;
}
//如果有继承父类
if (this.considerInherited) {
String superClassName = metadata.getSuperClassName();
if (superClassName != null) {
// Optimization to avoid creating ClassReader for super class.
Boolean superClassMatch = matchSuperClass(superClassName);
if (superClassMatch != null) {
if (superClassMatch.booleanValue()) {
return true;
}
}
else {
// Need to read super class to determine a match...
try {
if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
return true;
}
}
catch (IOException ex) {
logger.debug("Could not read super class [" + metadata.getSuperClassName() +
"] of type-filtered class [" + metadata.getClassName() + "]");
}
}
}
}
//如果有实现接口
if (this.considerInterfaces) {
for (String ifc : metadata.getInterfaceNames()) {
// Optimization to avoid creating ClassReader for super class
Boolean interfaceMatch = matchInterface(ifc);
if (interfaceMatch != null) {
if (interfaceMatch.booleanValue()) {
return true;
}
}
else {
// Need to read interface to determine a match...
try {
if (match(ifc, metadataReaderFactory)) {
return true;
}
}
catch (IOException ex) {
logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +
metadata.getClassName() + "]");
}
}
}
}
return false;
}
这里面最主要的是 matchSelf(metadataReader) 方法
AnnotationTypeFilter类
protected boolean matchSelf(MetadataReader metadataReader) {
//获取注解元数据
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
//check 注解及其派生注解中是否包含@Component
//获取当前类的注解 metadata.hasAnnotation @Controller
//获取当前类的注解及其派生注解 metadata.hasAnnotation @Controller包含的@Component\@Documented等等
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
在这段代码代码中,可以解决我们之前的疑惑“Spring是怎么发现@Configuration、@Controller、@Service这些注解修饰的类的?”
原来@Configuration、@Controller、@Service这些注解其实都是@Component的派生注解,我们看这些注解的代码会发现,都有@Component注解修饰。而spring通过metadata.hasMetaAnnotation()方法获取到这些注解包含@Component,所以都可以扫描到。如下:
然后我们再看回 scanCandidateComponents(basePackage)方法,接下来有一个 isCandidateComponent(sbd)方法,如下:
//是否是独立的类、具体的类
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
这个方法的作用是,判断该类是否为
顶层的类(没有父类或静态内部类)
具体的类(不是抽象类或接口)
至此,ClassPathBeanDefinitionScanner类中的doScan(basePackages)方法中的findCandidateComponents(basePackage)方法已经结束了,即我们的包扫描也结束了,已经把扫描到的类存入到了集合中,结下来就是解析注册Bean的过程了。
总结
通过这篇文章,我们可以回答之前的一些问题了:
Spring是怎么发现@Bean、@Controller、@Service这些注解修饰的类的?
通过 matchSelf(metadataReader)方法,判断这些注解中是否包含@Component@CompoentScan注解是怎么起作用的?
通过 isCandidateComponent(metadataReader)方法过滤
Spring IoC 源码分析 (基于注解) 之 包扫描的更多相关文章
- Spring Ioc源码分析系列--@Autowired注解的实现原理
Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...
- Spring IOC 源码分析
Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文 ...
- Spring Ioc源码分析系列--Ioc的基础知识准备
Spring Ioc源码分析系列--Ioc的基础知识准备 本系列文章代码基于Spring Framework 5.2.x Ioc的概念 在Spring里,Ioc的定义为The IoC Containe ...
- Spring Ioc源码分析系列--前言
Spring Ioc源码分析系列--前言 为什么要写这个系列文章 首先这是我个人很久之前的一个计划,拖了很久没有实施,现在算是填坑了.其次,作为一个Java开发者,Spring是绕不开的课题.在Spr ...
- Spring Ioc源码分析系列--Ioc源码入口分析
Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...
- Spring Ioc源码分析系列--容器实例化Bean的四种方法
Spring Ioc源码分析系列--实例化Bean的几种方法 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到bean真正通过那些方式实例化出来的时候,并没有继续分 ...
- Spring Ioc源码分析系列--Bean实例化过程(一)
Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...
- Spring Ioc源码分析系列--Bean实例化过程(二)
Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...
- Spring Ioc源码分析系列--自动注入循环依赖的处理
Spring Ioc源码分析系列--自动注入循环依赖的处理 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到Spring创建bean出现循环依赖的时候并没有深入去分 ...
随机推荐
- 关于EasyUI DataGrid行编辑时嵌入时间控件
本人做一个名为“安徽中控”项目时,为快速开发基础数据增删改模块,遂采用EasyUIDatagrid将所有增删改查的操作都集中于表格中,并且所有增删改查操作都集中于泛型对象,从而不必为每个表写具体的增删 ...
- blob - 二进制文件流下载
/** * 返回值文件类型为 blob 二进制流文件 * responseType: 'blob' * params 接口所需参数 * 命名文件名:依据时间戳命名文件名 * (导出时需要延迟,否则导出 ...
- JS高级---沙箱
沙箱 沙箱: 环境, 黑盒, 在一个虚拟的环境中模拟真实世界, 做实验, 实验结果和真实世界的结果是一样, 但是不会影响真实世界 全局变量 var num=10; console.log(nu ...
- js-时间相关的转换
毫秒值 -> 时间 var date = new Date(1477386005*1000);
- [Luogu]中位数
Description Luogu1168 Solution 一种神奇的做法:开一个大根堆和小根堆,保证大根堆比小根堆多1个元素,且大根堆堆顶元素比小根堆堆顶元素小,那么大根堆堆顶就是中位数.插入的时 ...
- python3练习100题——005
继续做题-答案都会经过py3测试原题网址:http://www.runoob.com/python/python-exercise-example5.html 题目:输入三个整数x,y,z,请把这三个 ...
- 解决使用git出现 The file will have its original line endings in your working directory
执行以下命令即可解决 git rm -r --cached . git config core.autocrlf false git add . . 代表当前目录
- 01-Java基本语法【前言、入门程序、常量、变量】
重点知识记录: 1.java语言是美国Sun公司在1995年推出的高级编程语言. 2.java语言主要应用在互联网程序的开发领域. 3.二进制转换 1)十进制数据转换成二进制数据:使用除以2获取余数的 ...
- Vue中父组件向子组件echarts传值问题
原文链接:https://blog.csdn.net/Uookic/article/details/80638883?utm_source=copy 问题:当父组件传值给子组件echarts时,发现子 ...
- python网络编程(通过tcp或者udp协议通信)
1.基于tcp协议传送文件: 客户端: import socketimport osimport jsonimport structclient = socket.socket()client.con ...