Spring 循环依赖分析

By youfang

循环依赖分析

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。

Spring中循环依赖场景有:
  1. 构造器的循环依赖
  2. field属性的循环依赖。

对于构造器的循环依赖,Spring 是无法解决的。

Spring 对于scope 为 prototype 的 bean 也无法解决。

流程

解决循环依赖
  1. 在 doGetBean() 中,首先会根据 beanName 从单例 bean 缓存中获取,如果不为空则直接返回。
  2. 这个方法主要是从三个缓存中获取,分别是:singletonObjects、earlySingletonObjects、singletonFactories。
    1. singletonFactories : 单例对象工厂的cache
    2. earlySingletonObjects :提前暴光的单例对象的Cache
    3. singletonObjects:单例对象的cache
  • isSingletonCurrentlyInCreation():判断当前 singleton bean 是否处于创建中
  • allowEarlyReference:允许从 singletonFactories 缓存中通过 getObject() 拿到对象

getSingleton() 整个过程如下:
首先从一级缓存 singletonObjects 获取,如果没有且当前指定的 beanName 正在创建,
就再从二级缓存中 earlySingletonObjects 获取,如果还是没有获取到且运行 singletonFactories 通过 getObject() 获取,
则从三级缓存 singletonFactories 获取,如果获取到则,通过其 getObject() 获取对象,
并将其加入到二级缓存 earlySingletonObjects 中 从三级缓存 singletonFactories 删除

操作过程

首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来,然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来,这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories ),通过 ObjectFactory 提前曝光,所以可以通过 ObjectFactory.getObject() 拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中,回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。

总结

关键点:提前暴露三级缓存

  1. 创建bean实例
  2. 单例、正在创建、允许循环依赖,调用addSingletonFactory将bean实例放入缓存中。
  3. 调用populateBean方法进行依赖注入
  4. 调用initializeBean方法完成对象初始化和AOP增强
spring为什么要用三级缓存,而不是二级缓存?

但是假如有这种情况:a实例同时依赖于b实例和c实例,b实例又依赖于a实例,c实例也依赖于a实例。

a实例化时,先提前暴露objectFactorya到三级缓存,调用getBean(b)依赖注入b实例。b实例化之后,提前暴露objectFactoryb到三级缓存,调用getBean(a)依赖注入a实例,由于提前暴露了objectFactorya,此时可以从三级缓存中获取到a实例, b实例完成了依赖注入,升级为一级缓存。a实例化再getBean(c)依赖注入c实例,c实例化之后,提前暴露objectFactoryc到三级缓存,调用getBean(a)依赖注入a实例,由于提前暴露了objectFactorya,此时可以从三级缓存中获取到a实例。注意这里又要从三级缓存中获取a实例,我们知道三级缓存中的实例是通过调用singletonFactory.getObject()方法获取的,返回结果每次都可能不一样。如果不用二级缓存,这里会有问题,两次获取的a实例不一样。