Spring是如何解决循环依赖的
1. 解决方案(setter注入)
用setter方法注入(非构造方法注入),且对于单例模式下的Bean
注意:如果是构造器注入引起的循环依赖,直接启动不了,无解
2. 关键词
Bean的生命周期(实例化和属性赋值分开)、ObjectFactory、三级缓存
2. Spring解决循环依赖的过程
1. 现象
ClassA引用ClassB,ClassB中又引用了ClassA。
2. 解决步骤
-
A的实例化,Spring选择合适的构造器实例化A,并把A的ObjectFactory放入到三级缓存中
-
A的属性赋值,赋值的过程中,发现成员变量B为被实例化,接着会去执行B的生命周期
-
B的实例化,Spring选择合适的构造器实例化B,并将B的ObjectFactory放入到三级缓存中
-
B的属性赋值,赋值的过程中,发现成员变量A能够在三级缓存中找到(半成品),B属性赋值成功,删除二级、三级缓存中的Bean,保存到一级缓存中。
-
回到A的属性赋值,A属性赋值成功(完成品),删除二级、三级缓存中的Bean,保存到一级缓存中
1. 问:这里第4步骤,属性B是如何找到A的半成品对象的
-
从三级缓存中取出Bean对应的ObjectFactory实例,调用其getObject方法,来获取早期曝光Bean,最终会调用getEarlyBeanReference方法
-
拿到半成品的bean实例(属性未填充),从三级缓存移除,放到二级缓存earlySingletonObjects中
注意:此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象
1. getEarlyBeanReference的作用
2种情况:
- 如果bean被AOP切面代理则返回的是beanProxy对象
- 如果未被代理则返回的是原bean实例
2. 问:为什么要用到二级缓存?(AOP代理的Bean)
好像只需要用到三级缓存就可以了,为什么要用到二级缓存?
1. bean被AOP进行了切面代理的场景
getEarlyBeanReference方法返回的是代理对象,还是被CGLIB代理的,每次执行都会产生一个新的代理对象
这就会有问题了,因为A是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象(也可以认为是ObjectFactory),执行getObject()方法又会产生新的代理对象,这是不行的,因为A是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了**singleFactory.getObject()**产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象
2. 二级缓存的作用
在Bean被AOP代理的情况下,保证始终只有一个代理对象
3. 总结
如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以需要借助另外一个缓存来保存产生的代理对象
参考一下优秀的文章:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
Spring中Bean的一级缓存、二级缓存、三级缓存
1. 一级缓存(初始化完成的Bean)singletonObjects
存在ConcurrentHashMap中,用来存放已经初始化完成的Bean
2. 二级缓存(有问题的Bean)earlySingletonObjects
存在HashMap中,存放半成品的Bean,一般处于循环引用的Bean才会暂时保存在这个缓存里
3. 三级缓存(存ObjectFactory)singletonFactories
存在HashMap中,存放ObjectFactory实例(用来获取Bean的工厂类)、在IoC容器中,所有刚被创建出来的ObjectFactory,默认都会保存到该缓存中。
1. 作用
spring会提前将已经实例化的bean通过ObjectFactory半成品暴露出去
半成品是指:bean对象已经实例化,但是未进行属性填充,是一个不完整的bean实例对象
4. 三者之间的关系
- 一级二级缓存中的Bean是互斥的,同一个Bean只可能存在其中一个缓存
- 二级缓存中保存的是BeanFactory产生的对象或AOP代理类对象
- 三级缓存只存工厂类,不存Bean实例