SpringBean的循环依赖

什么是循环依赖

  循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A。产生循环依赖的问题,主要是:A创建时–>需要B----s去创建—>需要A,从而产生了循环。
SpringBean的循环依赖_第1张图片
注意,这里不是函数的循环调用,是对象的相互依赖关系。
Spring中循环依赖场景有:

  • 构造器的循环依赖。构造器循环依赖是编码不当造成,将抛出异常
  • field属性的循环依赖 ⭐️

解决循环依赖的方法

  Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或者属性是可以延后设置的(但是构造器必须是在获取引用之前)。Spring Bean的声明周期大体上分为四步:实例化,属性赋值,初始化,销毁。

  从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。

  那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存

  • singletonFactories : 单例对象工厂的cache (一级缓存)
  • earlySingletonObjects :提前暴光的单例对象的Cache(二级缓存)
  • singletonObjects:单例对象的cache(三级缓存)

为什么要设立三级缓存呢?

其实当不需要实现AOP的时候,解决循环依赖不用三级缓存机制,也不用单例工厂,二级缓存就足以实现。

  • 第一级:singletonObjects
  • 第二级:earlySingletonObjects

不需要实现AOP时Spring解决循环依赖基本流程:
假设单例一与单例二相互依赖对方并且此时都没有加入到单例池

  1. 创建单例一
  2. 发现循环依赖单例二,则将单例一加入earlySingletonObjects缓存提前曝光
  3. 自动装配单例二
  4. 判断单例二在earlySingletonObjects缓存是否存在
  5. 不存在则创建单例二
  6. 发现单例二循环依赖单例一,且在二级缓存中找到,则注入
  7. 随后完成单例二的创建,加入singletonObjects一级缓存
  8. 此时回到创建单例一的流程,B此时已经创建完毕
  9. 接着创建单例一,单例一也创建完毕,加入singletonObjects一级缓存

那为什么要设立三级缓存呢?

  这是因为当我们需要使用AOP时,将会对原始对象进行代理,因此最后的对象将是代理对象而不是原始对象!

所以按照二级缓存的步骤进行创建的话将会造成一个问题:

  第2步加入到二级缓存中的对象是原始对象,导致第7步自动装配到到单例2中的单例1对象是原始对象,这个对象还没有完成AOP的处理。如果此时完成单例2的创建,之后在完成进行单例1的创建时,单例1对原始对象进行AOP处理,将导致最终的单例1对象不是之前自动装配到单例2中的单例1对象。

  Spring选择使用三级缓存来解决这个问题:singletonFactories缓存中的对象是一个单例工厂,该工厂可以将原始对象进行AOP处理。

自己理解:
  Spring将实例化、注入、初始化一套流程完毕的Bean存入一级缓存,将仅仅完成实例化的半成品放到二级缓存中,如果用到了AOP,则将半成品的代理对象放到三级缓存中。
  比如A、B循环依赖,先实例化A,发现依赖B,如果A没有用到AOP,不是被代理对象,则将半成品A存到二级缓存,如果用到了AOP,则是被代理对象,将代理类存入三级缓存。然后因为依赖B,然后初始化B,发现依赖A,就去二三级缓存中找,然后注入属性,然后将初始化完毕的B加入到一级缓存使用,并在二三级缓存中删除。最后A依赖的B也在一级缓存中拿到了,A也完成实例化、注入、初始化一套流程,加入到一级缓存中。两个Bean都成功创建。
SpringBean的循环依赖_第2张图片
参考博文:

  • https://blog.csdn.net/u010853261/article/details/77940767
  • https://blog.csdn.net/weixin_45702700/article/details/114190782

你可能感兴趣的:(Spring,java,spring,aop,bean)