Spring循环依赖

前言

最近发现循环依赖的源码又忘的干干净净。赶紧去撸一撸源码,记录一下。这篇文章简单说一说spring是怎么处理循环依赖的,如果你想看spring解决循环依赖又无从下手时,希望你带着这篇文章去看源码能对你有一丢小小的帮助。重点:源码一定要自己去撸,哪怕带着别人的理解,或者已经知道了怎么回事,也要亲自去撸一下源码。

注入循环依赖。

场景:beanA使用@Autowired注解注入beanB,beanB使用@Autowired注解注入beanA,当然,也可以是个大循环,只要是个闭环就行。

流程:

  1. beanA进入doGetBean()方法
  2. 尝试从缓存里拿beanA,没拿到
  3. 多例集合检查,因为是默认是单例,检查通过
  4. 创建beanA,创建前检查,检查把beanA放到正在创建的单例set集合里,添加成功
  5. 调用beanA构造方法实例化
  6. 收集beanA的属性(这里收集到了beanB)
  7. 添加beanA到三级缓存
  8. 注入收集的属性,注入beanB,触发beanB的创建
  9. beanB进入doGetBean()方法
  10. 尝试从缓存里拿beanB,没拿到
  11. 多例集合检查,因为是默认是单例,检查通过
  12. 创建beanB,创建前检查,检查把beanB放到正在创建的单例set集合里,添加成功,此时set集合里有beanA和beanB
  13. 调用beanB构造方法实例化
  14. 收集beanB的属性(这里收集到了beanA)
  15. 添加beanB到三级缓存
  16. 注入收集的属性,注入beanA,触发beanA的创建
  17. beanA进入doGetBean()方法
  18. 尝试从缓存里拿beanA,一级缓存没找到,找二级缓存,二级缓存没找到,找三级缓存,找到了。
  19. 通过三级缓存实例化beanA,删除三级缓存,放到二级缓存里去。
  20. beanB的属性注入完成
  21. beanB创建完成
  22. 返回到beanA的创建栈帧,beanA的属性注入完成
  23. beanA和beanB创建完成

构造方法循环依赖

场景:beanA在构造方法里传入beanB参数,beanB在构造方法里传入beanA参数,当然,也可以是个大循环,只要是个闭环就行。

流程:

  1. beanA进入doGetBean()方法
  2. 尝试从缓存里拿beanA,没拿到
  3. 多例集合检查,因为是默认是单例,检查通过
  4. 创建beanA,创建前检查,检查把beanA放到正在创建的set集合里,添加成功
  5. 调用beanA构造方法实例化
  6. 实例化之前spring会准备构造方法的参数,去容器里找beanB
  7. 没找到beanB,触发beanB的创建
  8. beanB进入doGetBean()方法
  9. 尝试从缓存里拿beanB,没拿到
  10. 多例集合检查,因为是默认是单例,检查通过
  11. 创建beanB,创建前检查,检查把beanB放到正在创建的单例set集合里,添加成功,此时set集合里有beanA和beanB
  12. 调用beanB构造方法实例化
  13. 实例化之前spring会准备构造方法的参数,去容器里找beanA
  14. 没找到beanA,触发beanA的创建
  15. beanA进入doGetBean()方法
  16. 尝试从缓存里拿beanA,没找到
  17. 创建beanA,创建前检查,检查把beanA放到正在创建的set集合里,添加失败
  18. 添加失败就抛异常,流程结束

多例循环依赖

场景:beanA在构造方法里传入beanB参数,beanB在构造方法里传入beanA参数,当然,也可以是个大循环,只要是个闭环就行。但是bean的scope属性都是prototype

流程:

  1. beanA进入doGetBean()方法
  2. 尝试从缓存里拿beanA,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
  3. 多例集合检查,这时候啥也没有,检查通过
  4. 创建beanA,创建前检查,检查把beanA放到正在创建的多例set集合里,添加成功
  5. 调用beanA构造方法实例化
  6. 收集beanA的属性(这里收集到了beanB)
  7. 多例,不添加beanA到三级缓存
  8. 注入收集的属性,注入beanB,触发beanB的创建
  9. beanB进入doGetBean()方法
  10. 尝试从缓存里拿beanB,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
  11. 多例集合检查,这时候里面只有beanA,检查通过
  12. 创建beanB,创建前检查,检查把beanB放到正在创建的多例set集合里,添加成功,此时set集合里有beanA和beanB
  13. 调用beanB构造方法实例化
  14. 收集beanB的属性(这里收集到了beanA)
  15. 多例,不添加beanB到三级缓存
  16. 注入收集的属性,注入beanA,触发beanA的创建
  17. beanA进入doGetBean()方法
  18. 尝试从缓存里拿beanA,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
  19. 多例集合检查,检查失败,发现已经存在beanA,抛异常,流程结束。

问题

这里有个问题,为何要在三级缓存里把beanA删除,然后添加到二级缓存里

以前我一直觉得二级缓存是多余的,但是这一次我仔细看过之后发现不是这样。

通过3级缓存创建对象的时候,会调用bean的BeanPostProcessor链表里类型是SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法。

如果没有二级缓存,beanA注入了beanB,beanB注入了beanA,假设beanB又注入了beanC,beanC又注入了beanA。那么创建beanA的时候,会有两次从缓存中拿beanA的操作,如果没有二级缓存,第一次从三级缓存拿到,走一遍上面的BeanPostProcessor调用链,第二次又会走一次,导致BeanPostProcessor链重复执行。而有了二级缓存,第一次从三级缓存里拿到,走一遍BeanPostProcessor调用链,删除三级缓存,放到二级缓存,第二次直接从二级缓存里取。

为啥不放到一级缓存,一级缓存里都是已经实例化好的bean,而我们这整个过程,beanA其实还没有创建完毕。

总结

spring目前只允许单例注入的循环依赖存在,建议自己去撸一撸源码。我只提到了bean创建时关于循环依赖的细节,跟循环依赖无关的我没有提到,这不代表bean的整个创建流程。
后面有空我会把代码都贴一下,最近有点忙。
附:

  1. doGetBean()在AbstractBeanFactory类里。
  2. 去缓存里拿是类DefaultSingletonBeanRegistry的getSingleton()方法

你可能感兴趣的:(Spring循环依赖)