Spring之Bean生命周期源码分析(一)
一、前言
二、生成、合成BeanDefinition
前置信息
老规矩, 直接通过创建Spring容器的构造方法进去, 可以看到它先执行了无参构造, 在点进去, 在AnnotationConfigApplicationContext() 方法中第一行和第三行不用看, 那个是和JRF相关的, 可以忽略掉
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
...
createAnnotatedBeanDefReader.end();
还是上面的方法, 直接进入 refresh() 刷新方法
进入该方法的详情
在该方法下面找到这行代码, 点进去
里面有一个方法, 是用来实例化非懒加载的单例Bean的
在该方法中, 创建单例Bean的流程大致如下:
- 取出所有的BeanNameList
- 遍历 BeanNameList
- 判断 (不是抽象的BeanDefinition, 是单例, 非懒加载)
- 生成 Bean对象
抽象的BeanDefinition可通过目录快速跳转到 抽象的 BeanDefinition
生成BeanDefinition-Spring包扫描
Spring启动的时候会进行扫描, 会先调用
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents(String basePackage)
扫描某个包路径, 并得到BeanDefinition集合
首先我们看方法, 可以看到入参就是 包扫描地址, 里面调用了一个 doScan(backPackages) 方法, 这个方法就是具体的获取扫描信息的 // 方法地址 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan(String... basePackages)
根据包路径扫描获取 BeanDefinition
doScan(backPackages)方法 在源码里可以看到, 这边有个方法的返回值就是 BeanDefinition 集合, 那么这个就是核心的存储 Bean的容器了, 我们可以在点进去看看里面具体的实现
findCandidateComponents(basePackage)方法 在这个方法里面去做了响应的判断, 上面那个判断在本标题的最后进行简单的讲解, 大部分情况都是else的, 红框部分的方法是相对比较核心的的, 接着往下走
具体的上面的if语句判断可通过目录快捷指向到标题 findCandidateComponents方法的判断
包扫描方法详情
scanCandidateComponents(basePackage) 方法 我把源码在这里贴一下, 一张图截不下, 我放两张, 左侧也有相应的代码行数
第424行是获取basePackage下所有的文件资源, packageSearchPath是获取一个地址, classpath*:扫描包路径/**/*.class 具体如下图所示
获取该路劲路径下的资源文件(.class文件)数组
getResourcePatternResolver().getResources(packageSearchPath); 该方法的作用是获取该路劲路径下的资源文件(.class文件)数组
在往下走, 他会对获取到的所有文件进行一个遍历, 途中红框部分是Spring中的一个元数据读取器, 在上篇文章: Spring之概念和工作流程中标题八有讲到
元数据读取器可以获取到当前注解的信息, 类的名字, 实现的接口, 父类等, 底层用的ASM技术
获取到元数据之后, 我们会对当前类进行判断, 该类是在排除过滤器中还是在包含过滤器中
排除过滤器和包含过滤器在上一篇文章标题九中有讲到 Spring之概念和工作流程
- 注意: 下图中new ScannedGenericBeanDefinition(metadataReader)的注释标注有误, 实际是设置BeanClass的名字
进入这个方法就可以看到其对排除和包含过滤器的判断, 我们主要看第三个红框, 因为我们可以看到, 如果该方法想要返回true只能看第三个框中的方法
我们在该方法中也是一直点, 进入具体的实现方法中, 可以看到下图所示代码
解释一下上面代码中红框部分的判断
// metadata是类上的注解信息
// 如果该类没有注解或者该类上没有 Conditional注解
// 返回FALSE代表不要跳过, 表示它就是一个Bean
if (metadata == null || !metadata.isAnnotated( Conditional.class.getName())) {
return false;
}
通过以上判断确定该类是Bean之后, 通过以下代码设置 Bean的Class名字同时添加 Bean的resource
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 上面方法具体实现如下:
Assert.notNull(metadataReader, "MetadataReader must not be null");
this.metadata = metadataReader.getAnnotationMetadata();
// 这里只是把className设置到BeanDefinition中
setBeanClassName(this.metadata.getClassName());
setResource(metadataReader.getResource());
添加完成之后, 进入以下判断, 大概判断的是: 是不是内部类, 接口, 抽象类
doScan方法内部代码流程
到这里, 我们获取 Set<BeanDefinition> 的具体实现方法就讲解完成了, 我们回到 doScan()方法 接着往下进行
关于Bean名字的生成流程, 具体代码各种跳转就不贴了:
- 判断该类上的注解 @Component 的 value值
- 若该值存在, 则返回
- 若该值不存在, 则调用方法生成
- 若类名前两个字符都是大写字符则直接返回
- 否则将第一个字符小写返回
检查Spring容器中是否已经存在当前 beanName
上图中红框部分接口: 检查Spring容器中是否已存在当前BeanName, 具体方法如下图所示:
findCandidateComponents方法的判断
上图这个判断主要是对 BeanDefinition的生成做一个快捷方式扫描 Bean的方式, 具体方式如下图所示
判断哪些类是由 @Component注解的, 具体源代码就不贴了, 很少用到
抽象的 BeanDefinition
通过以下方法设置的 BeanDefinition就是抽象的
引出下面的父子BeanDefinition
二、实例化非懒加载的单例Bean
首先进入 实例化非懒加载的单例Bean的方法中, 下图中爆红是因为那个方法太长了, 我临时删除了内部一些代码导致的
在这个方法里面, 一共有两个for循环遍历 beanNames, 第一个 for循环会生成所有的非懒加载单例Bean
第一次for循环流程说明:
- 根据 beanName 获取 合并后的 BeanDefinition
- 判断 BeanDefinition 是不是抽象, 单例, 懒加载
- 判断当前Bean是不是 FactoryBean, 不管是不是都会去创建 Bean对象
第二次for循环流程说明:
- 根据 beanName 找出对应的的单例对象
- 判断单例对象是否实现了 SmartInitializingSingleton接口
- 若实现了, 执行 smartSingleton.afterSingletonsInstantiated(); (后面有讲, 可根据目录快速跳转)
我们回到文章开篇创建Bean的那个方法里面, 我们可以看到它内部做了非常多的操作, 接下来我会为大家讲解一些比较核心重要的执行步骤
获取 RootBeanDefinition
获取合并之后的 BeanDefinition
找到当前 BeanDefinition
如果通过当前 beanName能在合并map(mergedBeanDefinitions)中取到 BeanDefinition则返回
没找到当前 BeanDefinition
假设当前 beanName 不是 合并BeanDefinition 继续往下走
最后我们到了这个方法里面, 注意: 这里参数 containingBd 为null
由于 containingBd 为null , 我们会直接执行下面这个方法, 又因为我们进到这个方法, 就是因为当前 beanName 不是 合并BeanDefinition, 所以 mbd = null, 继续往下执行到红框位置
if (containingBd == null) {
mbd = this.mergedBeanDefinitions.get(beanName);
}
当前 BeanDefinition不存在 父BeanDefinition
在红框位置去判断 当前bean有没有父bean, 如果没有, 则判断当前 BeanDefinition有没有实例化 RootBeanDefinition, 然后去生成一个新的 RootBeanDefinition, 不管有没有实例化都生成
父子BeanDefinition具体可以看标题三, 如下图所示
最后会直接进入这个方法, 将当前 BeanDefinition放入 mergedBeanDefinitions 中
当前 BeanDefinition存在 父BeanDefinition
流程如下:
- 获取当前 BeanDefinition 的 父BeanDefinition名字
- 判断当前 beanName是否等于 parentBeanName
- 不相等:
- 递归 继续进行合并
- 相等:
- 获取 父BeanFactory
- 如果 父Bean工厂 实现了 ConfigurableBeanFactory 接口则 递归合并
- 若未实现则抛出异常
- 不相等:
FactoryBean
这边会通过 beanName去判断当前 bean是不是一个 FactoryBean, 具体实现如下图所示
在 isFactoryBean(String name) 方法中, 第一行代码的降价在下面 FactoryBean标题下面
上图中, 我特意用红框圈出来一个方法, 这个方法的作用是: 通过 beanName去单例池中取数据, 但是这个时候是获取不到的, 因为我们还没有去生成 bean对象, 所以继续往下走
在下面代码中, 去做判断, 如果当前 beanName去取不到 BeanDefinition, 就去判断它的 父BeanDefinition有没有实现 FactoryBean
if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
}
最后, 拿到合并之后的 BeanDefinition, 在往下走, 去判断 BeanDefinition的类型, 在判断是不是一个 FactoryBean, 在返回
获取FactoryBean对象
获取FactoryBean对象靠的是 getBean(String name)方法
我们可以看到, 这个方法的入参是在我们的 beanName前面增加了一个符号 &
我们进入这个方法内部去看一下
这里我们看到了熟悉的代码, 这行代码会根据你传入的 name不同而有所变化
String beanName = transformedBeanName(name);
如上图所示, 可以看到, 我们在 实例化非懒加载单例Bean的时候, 有三个地方调用到了 getBean(String name)方法, 且第一次调用在 beanName前加了符号 &
在 getBean(String name)中, 会通过 transformedBeanName
方法去获取 beanName
然后根据获取到的 beanName去单例池中查找 获取单例对象, 如果获取到了单例对象, 那么会根据 bean的单例对象, beanName和传进来的 name去获取 bean实例
如果没有获取到了单例对象, 则会继续进行 bean的生命周期, 最后生成 bean的单例对象, 具体可以看我的下一篇Spring文章
创建 Bean对象
实现SmartInitializingSingleton接口
我们来实现这个接口, 会自动生成一个方法
通过上面的分析, 可以知道, 这个方法的执行时间是: 所有单例Bean都实例化之后
三、合成BeanDefinition-父子BeanDefinition
生成父子 BeanDefinition代码如下
因为child的父BeanDefinition是parent,所以会继承parent上所定义的scope属性。
而在根据child来生成Bean对象之前,需要进行BeanDefinition的合并,得到完整的child的BeanDefinition。
本文内容到此结束了
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位大佬指出。
我是 宁轩 , 我们下次再见
转载自:https://juejin.cn/post/7135955206134562823