Spring如何通过三级缓存解决循环依赖
Posted 诺浅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring如何通过三级缓存解决循环依赖相关的知识,希望对你有一定的参考价值。
什么是循环依赖
什么是循环依赖?比如如下一个例子
public class A
private B b;
public class B
private A a;
这个例子存在的问题:理论上spring创建A的时候依赖了B,然后spring就会去加载B,但是这个时候B又依赖了A,spring又去加载A,就会陷入一个死循环,但我们在实际使用spring的时候并没有出现这样的循环,这是因为spring设计之初就考虑了这个问题,那么spring是如何解决的呢?我们先要明确的是spring对循环依赖的处理有三种情况,分别为
- 构造器的循环依赖:这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常。
- 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
- 非单例循环依赖:无法处理。
Spring如何通过三级缓存解决循环依赖
Spring中有三级缓存,分别如下
- singletonObjects:完成初始化的单例对象的cache(一级缓存)
- earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)
- singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)
Spring获取一个Bean的流程就说从一级到三级依次去寻找这个Bean
protected Object getSingleton(String beanName, boolean allowEarlyReference)
// Quick check for existing instance without full singleton lock
// 从一级缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
// 如果没有,且依赖的A正在初始化,那我们去尝试看看earlySingletonObjects(二级缓存)是否有对象
// earlySingletonObjects(二级缓存)存放的是还未实例化好对象
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
// 从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果获取不到 但是支持获取EarlyReference
if (singletonObject == null && allowEarlyReference)
// 这里应该是Spring 5.x的优化,只有一二级缓存中都获取不到的时候才进行加锁,相对于5.x以前的一开始就加锁后移了,提高了性能
synchronized (this.singletonObjects)
// Consistent creation of early reference within full singleton lock
// 这里与5.x以前的一致
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null)
// 从三级缓存中获取,需要注意的是这里获取的不是singletonObject了,而是一个ObjectFactory
// 这个ObjectFactory就是 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 中的() -> getEarlyBeanReference(beanName, mbd, bean)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null)
// 通过从三级缓存中拿到的是一个工厂对象,这个工厂对象可以用于生产singletonObject并放入二级缓存中
singletonObject = singletonFactory.getObject();
// 把工厂生产出来的singletonObject放入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
// 也就是说,这里的逻辑是 从三级缓存中拿到的是一个工厂对象,这个工厂对象可以用于生产singletonObject并放入二级缓存中
// 为什么不把原对象直接放入二级缓存中呢?因为可能有AOP,所以需要生成代理对象
// 那为什么不直接把生成好的代理对象放入二级缓存中呢?因为为了性能考虑,如果没有循环引用,是不需要生成对象的。如果我们早早的就在doCreateBean方法中生成好了对象并放入二级缓存中,那是不是影响性能呢?
return singletonObject;
如果一二三级均没有这个Bean,就会走新建方法,Spring创建一个Bean的时候也会有3个步骤,如下
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
- initializeBean:调用spring xml中的init 方法。
我们再来说上面的循环依赖的例子Spring的解决方案:
- A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行第二步填充属性
- 发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A)
- 尝试从一级缓存singletonObjects中获取(肯定没有,因为A还没初始化完全)
- 尝试从二级缓存earlySingletonObjects中获取(也没有)
- 尝试三级缓存,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过从三级缓存中拿到A的工厂对象,通过这个工厂对象构建出了A的Bean对象,B拿到A对象后顺利完成了初始化阶段1、2、3。B对象完全初始化之后将自己放入到一级缓存singletonObjects中
- 此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。
为了更好的理解,我们来看看往三级缓存中放入A工厂对象的关键代码
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure)
if (logger.isTraceEnabled())
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
// 重点:Spring解决循环依赖的关键
// 把当前bean对象的工厂对象加入到三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
() -> getEarlyBeanReference(beanName, mbd, bean)
即为构建了一个ObjectFactory
对象,后续可以通过这个工厂对象构建出Bean对象
为什么构造器循环依赖和多例循环依赖Spring无法解决
构造器循环依赖
循环依赖的解决是实例化后通过三级缓存来解决的,但构造器是在实例化时调用的,此时bean还没有实例化完成。如果此时出现了循环依赖,一二三级缓存并没有Bean实例的任何相关信息,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。总结:spring的三级缓存解决的是实例化之后属性赋值的循环依赖,构造器被调用是在实例化之前,所以无法解决构造器的循环依赖!
多例循环依赖
对于prototype
作用域bean, Spring 容器无法完成依赖注入,因为Spring 容器不进行缓存prototype
作用域的bean ,因此无法提前暴露一个创建中的bean。
为什么不能只用一二级缓存来解决循环依赖?
这里面涉及到的问题很复杂,可以看这篇文章。
总结起来就是几点:一级缓存是放完全初始化好的对象的,如果只需要IOC功能其实一级缓存就能解决。但涉及到循环依赖,我们就需要暴露出一个没有初始化好的对象,所以这个时候就需要引入二级缓存,把初始化好的放到一级缓存里面去,没初始化完全的放到二级缓存里面去。看起来可以完美解决问题了,但如果有代理对象的话,实际流程就会变成下面这样。我们假设上诉例子中A为代理对象
- A初始化的时候把能够生成A代理对象的一个A工厂对象放到三级缓存中
- A发现自己依赖B对象就去生成B对象,B对象发现自己需要A对象,就会去三级缓存中把这个A工厂对象取出来并且生成了A对象的代理对象,然后把代理对象放入到二级缓存,同时赋值B对象中的A,到此B对象构建完毕并放入一级缓存中。
- A对象使用构建好的B对象构建自己,A对象构建完毕。
以上是关于Spring如何通过三级缓存解决循环依赖的主要内容,如果未能解决你的问题,请参考以下文章