目录
1、构造器循环依赖(无法解决)
在调用构造函数的时候就进行成员变量的初始化。
Spring有一个“当前正在创建Bean池”,每一个正在创建的Bean标识符都会放到该池中。当创建bean的过程中发现自己已经存在“当前正在创建Bean池”中时,就会抛出异常,表示循环依赖。创建完毕后的bean,标识符会从Bean池中移除。
示例:
通过构造器初始化对象时:
- 创建A,A依赖于B,所以将A放入“正在创建Bean池中”,先去创建B;
- 创建B,B依赖于C,所以将B放入“正在创建Bean池中”,先去创建C;
- 创建C,C依赖于A,所以将C放入“正在创建Bean池中”,先去创建A;
- 创建A,发现A已经在“正在创建Bean池”中,抛出异常。
2、Setter循环依赖
在调用构造函数的时候不进行成员变量的初始化,而是先将Bean对象实例化(无参构造函数),然后再设置对象属性。
3、解决方案
3.1、三级缓存
Spring通过将实例化后的对象(未设置属性值)提前暴露给Spring容器中的singletonFactories,解决了循环依赖的问题。
三级缓存只能解决单例Bean的循环依赖问题,因为"prototype"作用域中,Spring的对象实例是在被调用的时候才会进行,因此无法提前暴露一个创建中的bean。
通过三级缓存来解决bean的循环依赖问题:
singletonObjects(一级)
单例对象缓存(单例池),存放可用的成品Bean。
earlySingletonObjects(二级)
存放半成品的Bean,已实例化的对象,但尚未进行属性赋值和初始化操作。
singletonFactories(三级):
存放Bean工厂对象,用来生成半成品的Bean并放入到二级缓存中。
3.2、单例模式下通过setter获取Bean的流程
- 从单例对象缓存singletonObjects中获取;
- 若获取对象为null,且对象正在创建中,则从earlySingletonObjects中获取;
- 若获取对象仍为null,且允许从单例工厂缓存中获取,则从singletonFactory中获取;获取成功后,则将该对象放入到earlySingletonObjects缓存中,暴露出来。
4、三级缓存原理
假设A、B对象存在循环依赖:
- 实例化A:并把获取半成品A对象的BeanFactory,放入Map<String, ObjectFactory<?>> singletonFactories缓存(三级)中(这里并不是直接将Bean放入缓存,而是包装成ObjectFactory对象再放入);
- A属性赋值(阻塞):发现A依赖B,去一二三级缓存中找B,找不到,则去创建对象B;
- 实例化B:同A,把获取半成品B对象的BeanFactory,放入singletonFactories缓存(三级)中;
- B属性赋值:发现B依赖A,从三级缓存中通过singletonFactories得到A,然后把对象A放入earlySingletonObjects缓存(二级)中,并删除三级缓存中的对象A;
- B属性赋值成功:对象B创建完成,把B放入一级缓存,删除三级缓存中的对象B;
- A属性赋值成功:从一级缓存中得到B,属性赋值成功,删除二级缓存中的A,并将A放入一级缓存。
5、为什么使用三级缓存而不是二级缓存
创建普通Bean(非代理,无AOP)对象时,二级缓存和三级缓存都可以解决循环依赖。
Bean的创建流程:实例化->暴露Bean->填充属性->初始化->生成(代理)对象。
假设A、B对象存在循环依赖,且A对象关联AOP:
如果按以上Bean的创建流程,对象A暴露的是普通对象而非代理对象,所以对象B的属性A注入的也是普通对象而非代理对象。这种情况下,需要在属性填充前,就需要提前暴露A的代理对象。
三级缓存的bean工厂getObject方式,实际执行的是getEarlyBeanReference,如果对象需要被代理(存在beanPostProcessors -> SmartInstantiationAwareBeanPostProcessor),则提前生成代理对象。
所以三级缓存的不是对象而是对象工厂,如果不存在AOP,则返回普通实例化对象;如果存在AOP,则提前生成对象的AOP代理对象,并返回。
出现循环依赖时,才会执行工厂的getObject生成(获取)早期依赖。
6、补充
以上内容为个人学习汇总,仅供学习参考,如有问题,欢迎在评论区指出,谢谢!