spring循环依赖
-
关于Spring bean的创建,其本质上还是一个对象的创建,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化。
-
在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过一定的方式设置的。
-
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
解决办法 三级缓存
- singletonObjects:一级缓存:用于存放完全初始化好的bean 成品
- earlySingletonObjects:二级缓存:存放原始bean对象,尚未填充属性,用于解决循环依赖
- singletonFactories:三级缓存:用于存放bean工厂对象的,用于解决循环依赖
bean 的获取过程:先从一级获取,失败再从二级、三级里面获取
如果作用域是原型,即scope=“prototype”,也会报错,为什么呢?
-
首先我们要知道循环依赖只发生在作用域是单例的场景里面,
-
因为scope="prototype"时,这时候对应的Bean是在使用的时候才会被创建出来,
-
因为Spring容器不进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean
(singleton一般是用于无状态的bean,prototype一般用于有状态的bean)
具体步骤
检测循环依赖的过程如下:
-
A 创建过程中需要 B,于是 A 将自己放到三级缓里面 ,去实例化 B
-
B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了!
- 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A
- B 顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
-
然后回来接着创建 A,此时 B 已经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将自己放到一级缓存里面
如此一来便解决了循环依赖的问题
不会二级缓存
- 是给用户提供接口扩展的,所以采用了三级缓存。
为什么构造器注入不能解决循环依赖问题
-
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
-
instantiate(实例化)其实就是理解成new一个对象的过程,而new的时候肯定要执行构造方法,所以猜想对于应该是A在instantiate(实例化)时,进行B的初始化。
-
因为A中构造器注入了B,那么A在关键的方法addSingletonFactory()之前就去初始化了B,导致三级缓存中根本没有A,所以会发生死循环,Spring发现之后就抛出异常了。
总结
-
Spring对于构造器注入和使用prototype作用域的setter构成的循环依赖是无法解决的,
-
解决循环依赖前提条件是Bean单例模式下非构造函数的循环依赖,而且要求该对象没有被代理过。
-
Spring通过“提前曝光”机制,配合Java的对象引用原理,解决了某些情况下的循环依赖问题。