Spring 产生循环依赖问题的前提条件
- Spring管理的Bean默认都是单例的,这些对象在Spring容器里面只有唯一一份,所以Spring创建bean的时候就必须要考虑到不能重复创建对象,否则也就违背了单例的原则,所以这个时候就需要考虑到循环依赖的情况
Spring解决循环依赖的思路
- Spring 是通过三级缓存Map,和一个registeredSingletons(标记缓存: 保存着正在创建中的对象的名称)来解决循环依赖的
Spring创建对象的两个阶段
- 对象的实例化
- 对象属性的注入(注入阶段会引起循环依赖)
Spring的三级缓存
- 一级缓存:保存所有已经创建完成的bean
- private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256)
- 二级缓存:保存正常创建中的bean(完成了实例化,但是还未完成属性注入)
- private final Map<String, Object> earlySingletonObjects = new HashMap<>(16)
- 三级缓存:保存的是ObjectFactory类型的对象的工厂,通过工厂的方法可以获取到目标对象
- private final Map<String, ObjectFactory<?>> singletonFactoies = new HashMap<>(16)
- 标记缓存:保存着正在创建中的对象名称
为什么需要三个缓存而不是两个缓存
- 可以通过第三级Map的value属性作为参考,存放的是ObjectFactory对象
- ObjectFactory对象在调用getObject的时候,会(需要)处理代理类的包装和替换
- 即第三级缓存中还存在不确定性,所以需要三级
Spring无法解决循环依赖的场景
- 因为二级缓存中存放的是完成了实例化的对象,完成了实例化的对象放入二级缓存是解决循环依赖不可缺少的一环,但是对象的实例化是通过调用构造函数实例化的(即构造和注入放到一起的时候),所以如果是通过构造函数进行DI且有循环依赖的情况的话,Spring是无法解决的
- 类中有@Async注释的方法的话,Spring也无法解决循环依赖问题,原因是对于定义了Async方法的场景,Spring会新增一个后置处理器AsyncAnnotationBeanPostProcessor,其生成的代理对象和前面的对象不同,并且该后置处理器的顺序偏后
- @Lazy注解可以解决上述的两种循环依赖问题,其原理是Lazy修饰的属性,Spring会先为其创建一个代理对象而不是一个原始对象
- https://blog.youkuaiyun.com/WX10301075WX/article/details/123904543
- https://blog.youkuaiyun.com/yxh13521338301/article/details/117450763
无法解决循环问题场景的总结
- 无法进行实例化(如构造方法DI场景)
- 由于代理类与加载滞后的问题所引发的,拿不到同一个对象而照成的情况
最终总结
从SpringBoot 2.6.0 之后Spring官方已经不建议循环依赖了,从业务或者说包、Service类划分上去解决循环依赖才是一个相对正规的路径