0 看前必读
有不懂的或者不同意见的, 欢迎留言讨论, 留言必回!
先看下 00 spring 源码剖析系列说明
1 什么是循环依赖
循环依赖在 spring 框架中有一个专有名词叫 Circular dependencies, 其具体是指受 spring 管理的两个 bean 对象 Bean1 和 Bean2,Bean1 中有成员变量 Bean2;Bean2 中有成员变量 Bean1. 具体代码 case 如下:
代码结构如图:
代码结构图
前前后后一共使用了四个类, 其中两个 Bean 类如下:
- @Component
- public class Chicken {
- @Autowired
- Egg egg;
- }
- @Component
- public class Egg {
- @Autowired
- Chicken chicken;
- }
一个配置类:
- @ComponentScan("spring.post1.beans")
- public class Config {
- }
一个简单的 main 方法启动类:
- public class DemoSpringCircularDependencies {
- public static void main(String[] args) {
- AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
- Chicken chicken = ac.getBean("chicken", Chicken.class);
- System.out.println(chicken);
- }
- }
通过代码可以看出, 本章主要讨论下 spring 怎么解决基于 @AutoWired 注解的 Bean 的循环依赖问题. 而两个循环依赖的 Bean 就是 Chiken(里面需要属性 Egg)和 Egg(里面需要属性 Chicken).
2 前置知识
学习本文前需要对 spring 的基于注解的 bean 管理配置方式有基本的了解, 不然看不懂上述 4 个类的作用, 那么就无从谈及学习 spring 源码了, 本系列的文章也不是基本的 spring 配置学习文章, 这部分知识自行 google.
需要对 jdk8 的 lambda 有基础的了解.
3 源码分析
3.1 源码栈帧
首先我们先看下需要分析的源码的主要栈帧:
源码栈帧图
先对上图做简单的说明, 上图中的每蓝色小块代表一个方法, 里面的数字部分表示方法的执行先后顺序(数字小的先执行). 两个相邻的方法之间大数字方法是程序在执行小数字方法的过程中要调用的方法(和 debug 时的的栈信息类似). 我们对源码的分析也将按照 "创建所有单例 Bean","创建 Chicken 对象","填充 Chicken 对象属性","创建 Egg 对象","填充 Egg 对象属性","获取 Chicken 对象" 等顺序进行.
3.2 创建所有单例 Bean
方法 1. 是 AnnotationConfigApplicationContext 类的构造方法, 构造方法引出对 Bean 的初始化创建操作. 其中可以留意下方法 2. 中要执行的 finishBeanFactoryInitialization 方法也就是源码栈帧图中的 3. 方法. 在方法 3. 上面有一句英文注释: "// Instantiate all remaining (non-lazy-init) singletons.", 清晰的表明方法 3. 的主要目的就是要创建剩下没被创建的非懒加载的单例对象. 那么我们定义的两个 Bean 对象 Chiken 和 Egg 显然是在这个方法里面创建的, 至于为什么是 "剩下的" 而不是所有的, 其它的非懒加载的单例对象是在哪里创建的, 不是本文要描述的问题.
3.3 创建 Chicken 对象
spring 创建在创建 Bean 对象前会给每个 Bean 对象创建一个 BeanDefinition 对象, BeanDefinition 对象会搜集用户定义的关于 Bean 的各种配置信息, 如这个 Bean 对象的类型, 这个 Bean 对象的 id 和 name, 是否为单例对象等等, 这些配置信息可以是 xml 形式的配置文件, 也可以是基于注解的配置信息.
以 BeanDefinition 的形式搜集了这些信息后, spring 就开始初始化非懒加载的单例对象了(这里我们只分析我们自己定义的和循环依赖相关的两个 Bean 对象 Chicken 和 Egg 的加载过程). 也就是执行 5. getBean 方法. 方法 5. 是个空壳方法其内部调用的是方法 6. doGetBean 方法. doGetBean 方法执行过程中会执行一个名为 getSingleton(String beanName, boolean allowEarlyReference) 的方法. 此方法定义如下:
- protected Object getSingleton(String beanName, boolean allowEarlyReference) {
- Object singletonObject = this.singletonObjects.get(beanName);
- if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
- synchronized (this.singletonObjects) {
- singletonObject = this.earlySingletonObjects.get(beanName);
- if (singletonObject == null && allowEarlyReference) {
- ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
- if (singletonFactory != null) {
- singletonObject = singletonFactory.getObject();
- this.earlySingletonObjects.put(beanName, singletonObject);
- this.singletonFactories.remove(beanName);
- }
- }
- }
- }
- return singletonObject;
- }
其主要流程就是通过 beanName 参数查看先查看 map 对象 singletonObjects 中是否有对应名称的 Bean 对象, 有返回此 Bean 对象; 没有查看 map 对象 earlySingletonObjects 中是否有对应名称的 Bean 对象, 有返回此 Bean 对象; 没有查看 map 对象 singletonFactories 是否有对应名称的 ObjectFactory 对象, 有通过 ObjectFactory 对象的 getObject 方法获取到对应的 Bean 对象, 然后清除 singletonFactories 对应的 beanName 的映射, 同时将得到的 Bean 对象放到 earlySingletonObjects 中. 这其中还有一个方法 isSingletonCurrentlyInCreation(String beanName) 其内部是通过查看一个名为 singletonsCurrentlyInCreation 的 Set 对象是否包含指定的 beanName, 来判断这个单例 bean 是否正在创建 bean 对象.
这三个 Map 对象和一个 Set 对象就是 Spring 中解决循环依赖非常重要的缓存, 一下我们简称 "三 Map 一 Set", 三个 map 对象因其在执行获取 beanName 对应的 Bean 对象的过程中的先后执行顺序, 分别简称为 一级缓存, 二级缓存, 三级缓存.
singletonObjects: 一级缓存. 此缓存中的 Bean 对象是经历 Spring 完整生命周期的 Bean 对象,
earlySingletonObjects: 二级缓存. 此缓存中的 Bean 对象是已经通过创建出来的但没有经历 spring 完整的生命周期的 Bean 对象.
singletonFactories: 三级缓存. 此缓存存在的是 beanName 和能获取 Bean 对象的一个工厂类 ObjectFactory 对象.
方法 6. doGetBean 第一次调用 getSingleton(String beanName) 方法时从三个缓存中都没能获得参数 chicken 对应的 Bean 对象, 程序继续执行到方法 7. getSingleton(String beanName, ObjectFactory<?> singletonFactory) ,7. 方法中会执行一个 名为 beforeSingletonCreation(String beanName) 的方法, 这个方法会在我们上文提到的中的三 Map 一 Set 中的 Set 添加对应的 beanName(chicken)表示此 chicken 对应的单例 Bean 处在正在创建过程中, 程序继续执行执行到 8. getObject() 方法, 方法 8. 是一个 lambada 对象对应的方法, 其调用的是方法 9. createBean 方法进入 Bean 的创建过程. 方法 9. 中我们重点关注其执行的方法 10. doCreateBean , 此方法是真正执行 bean 对象的创建的方法. 在方法 10. 中我们注意到其会执行一个名为 addSingletonFactory 方法, 此方法会在我们提到的 三 Map 一 Set 中的 三级缓存 singletonFactories 添加一个 beanName(chicken)对应的 ObjectFactory 对象, 而后执行方法 10. 中的方法 11. populateBean, 此时程序传给方法 11. 的三个参数分别为 beanName: 值为 chicken,mbd:ChickenBean 的 BeanDefiniton 对象, instanceWrapper: 通过构造方法创建的一个 Chicken 对象, 即 Spring 注解中经常提到的 raw bean 对象. 由方法 11. 的名称可知, 此类的主要目的是填充 Chicken 对象中的属性(Egg 对象), 循环依赖正是在此方法中解决的.
3.4 填充 Chicken 对象属性
紧接上面小节, 方法 11. 中 spring 通过在 Chicken 类中的 @Autowired 注解来发现其需要的属性: Egg 对象并填充其值, 这个过程在方法 12. postProcessProperties 方法中执行, 顺便提一句 @Autowired 注解依赖的属性由 AutowiredAnnotationBeanPostProcessor 类处理,@Resource 注解依赖的属性由 CommonAnnotationBeanPostProcessor 类处理.
而方法 13. 到方法 17. 主要作用就是找到合适的 beanName 以便用来通过此 beanName 找到对应的 Bean 来填充 Chicken 中的 Egg 对象, 此部分代码和本文主旨无关以后的文章会分析, 感兴趣的童鞋可以自行 debug 看下代码.
3.5 创建 Egg 对象
紧接上面小节, spring 通过方法 17. resolveCandidate 将找到的合适 beanName(egg)传递下来, 通过方法 18. getBean 来执行对 Egg Bean 对象的获取操作. 此小节调用的方法栈和 "3.3 创建 chicken 对象" 小节的方法栈是一样的, 唯一的区别是 3.3 小节传递的 beanName 参数值为 chicken, 而本小节传递的 beanName 参数为 egg.
3.6 填充 Egg 对象属性
本小节对标的是 "3.4 填充 Chicken 对象属性" 小节, 两个小节调用的方法栈是一样的, 区别也是参数的不同而已. Spring 发现 Egg 对象需要注入一个 Chicken 对象.
3.7 获取 Chicken 对象
这里我们分析的方法 31. getBean 和方法 18. getBean 都是因为我们自己定义的 Bean 对象中有需要的注入的 Bean 对象. 但是方法 31. 传递的参数是 chicken, 而 Chicken 对象在 3.3 小节中分析得知, 其在三 Map 一 Set 中的第三级缓存 singletonFactories 存放了一个对应的 ObjectFactory 对象. spring 通过这个 ObjectFactory 对象获取到了对应的 Chicken 对象, 而避免了循环依赖.
3.8 缓存创建完的 Egg 和缓存创建完的 Chicken
通过 3.6 小节我们获取到了 Egg 对象需要的成员变量 Chicken 对象. 随着方法栈帧的层层返回, 我们将焦点聚焦在由方法 21. 返回后的方法 20. 中, 在程序执行完方法 21. getObject 并获取到经历完 Bean 生命周期的 Egg Bean 后, 其在方法 20. 中还要执行两个比较重要的方法 afterSingletonCreation 和 addSingleton, 其中前者会把三 Map 一 Set 中的 Set 对象 singletonsCurrentlyInCreation 中的 egg 移除, 表示此 Bean 对象不是正在创建的 Bean 对象, Bean 创建已经完成; 后者会把 Egg Bean 存放在一级缓存中, 同时清空二级缓存和三级缓存中 egg 对应的映射, 至此 Egg Bean 的 spring 生命周期已经大体完成. Chicken 对象也会执行 afterSingletonCreation 和 addSingleton 两个方法来完成 Chicken Bean 的 spring 生命周期.
3.9 源码分析小结
创建 chicken 对象, 创建 Egg 对象: 步骤主要解决一个 Bean 的 raw bean 对象的创建和的前期准备工作, 和本文循环依赖相关的主要是对三 Map 一 Set 的对象的保存的内容的修改.
填充 Chicken 对象属性, 填充 Chicken 对象属性: 本文中主要通过 AutowiredAnnotationBeanPostProcessor 类完成依赖对象的搜集和适合依赖对象的 beanName 的筛选.
获取 Chicken 对象: 主要是通过第三级缓存来获取, 避免了 Chicken 对象的重复创建而进入一个死循环.
缓存创建完的 Egg 和缓存创建完的 Chicken: 完成善后工作, 将走完 spring 生命周期的 Egg Bean 和 Chicken Bean 放到一级缓存中, 供客户端程序从 spring 中获取使用.
4 缓存数据变化
在 "3 源码分析" 章节中, 随着程序运行过程中除了有由方法调用和方法返回而产生的线程方法栈图中方法的压栈和出栈外. 在这进进出出的背后发生改变的是我们的三 Map 一 Set 中的数据.
在源码分析的开头小节 "3.3 创建 Chicken 对象" 和结尾小节 "3.8 缓存创建完的 Egg 和缓存创建完的 Chicken" 我们有对三 Map 一 Set 的分析, 但着并不是说只有这两个小节的部分有数据变更, 而是其缓存变化的原理和这连个小节一直, 唯一的区别是方法调用的参数不同. 整个数据变化图如下:
数据流通图
图中每个状态图都有一个 "[a,b)" 形式的步骤指示器, 其中 a,b 分别表示 "3.1 小节" 中源码栈帧图中一个方法数字, 而括号用的是高等数学中常见的方式左闭右开方式, 表示在程序在执行方法 a 到方法 b(包含 a 不包含 b)过程中缓存数据的状态和其下面的表格一致.
通过对八个表格数据的观察我们可以发现, 对于同一个 beanName 所映射的对象, 基本上经历从第三级缓存, 第二级缓存, 第一级缓存, 的一个升级过程. 而对网上经常困惑的第三级缓存的作用(认为第三级缓存没有必要存在), 博主认为存在第三级缓存是基于以下两个事实的:
某些 Bean 对象 (并不是所有的 bean 对象) 在创建过程中且尚未创建完时就会被其它 Bean 对象所引用的问题(就是循环依赖, 貌似是一句废话_).
Bean 的生命周期过程是一个成本较高的过程.
本文中只有 Chicken 对象在创建过程中有被其它对象引用而 Egg 对象没有. 因为第三级缓存存储的是一个 raw bean 后续创建的方法, 那么对于在创建时被其它对象引用的 Chicken 对象来说, 可以执行完第三级缓存中存储的 bean 对象后续的处理方法 (AOP 的功能就是在此实现的) 后将 Chicken bean 返回, 对于没有在创建过程中被引用的 Egg 对象来说, 其只是浪费第三级缓存中的一点点内存, 而避免重复执行 spring 对 Egg Bean 的某些生命周期逻辑的重复执行, 这些重复的逻辑很可能是很高成本的过程, 如 AOP 的实现.
5 总结
编程界有个很著名的说法:"算法加数据结构等于程序", 本文的 "3 源码分析" 和 "4 缓存数据变化" 分别充当了 spring 解决基于 @AutoWired 注解的 Bean 的循环依赖程序中的算法和数据结构. 和理解其关键是对 "三 Map 一 Set" 数据变化的深入理解.
参考资料
1,Spring Circular Dependencies
来源: http://www.jianshu.com/p/6fd58401fc74