1.什么是循环依赖
通俗来讲,循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
2.Spring如何解决循环依赖
首先,先介绍Spring是如何创建Bean的。
(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
(3)initializeBean:初始化,调用spring xml中的init方法。
循环依赖主要发生在第二步填充属性:在创建对象A时需要填充成员变量B,可是在这时候对象B还没有进行创建。
三级缓存
Spring为了解决单例的循环依赖问题,使用了三级缓存,也就是下文中的三个Map对象。一级缓存用于存放初始化完毕的Bean对象,二级缓存用于存放不完整(仅进行实例化,还没有填充属性)Bean对象,三级缓存用于存放对象工厂(用于创建AOP实例)。
/** Cache of singleton objects: bean name --> bean instance */
//单例对象的cache
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
/** Cache of early singleton objects: bean name --> bean instance */
//提前暴光的单例对象的Cache
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */
//单例对象工厂的cache
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
步骤:
1.Spring首先从一级缓存singletonObjects中获取。
2.如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。
3.如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取.
4.如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
Spring解决循环依赖的步骤如下图所示,以创建A、B对象为例。
- 实例化A对象,将未初始化的A对象实例加入三级缓存,构建A对象工厂。
- 进行A对象的属性填充,发现A对象引用了未创建的B对象,进行B对象的创建。
- 实例化B对象,B对象属性填充时,发现B对象引用了A对象,按照三级缓存查询的步骤查到了A对象在第三级缓存中有对象工厂,于是将A对象创建出来并将A对象的三级缓存移除,加入到第二级缓存。这时的A对象没有填值,仅包含一个引用。
- 完成B对象的属性填充、初始化过程,B对象加入到一级缓存中。
- 返回到A对象的属性填充过程,填充B对象到A中,再接着初始化、将A对象加入一级缓存中,最终完成A、B对象的创建。
AOP的实现就是在三级缓存的对象工厂上返回代理对象。
3.Spring无法解决哪些情况下的循环依赖?
- 使用构造方法注入,因为加入singletonFactories三级缓存的前提是执行了构造器来创建半成品的对象,所以构造器的循环依赖没法解决。因此,Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!
- Bean的作用于为prototype(原型),因为对于原型bean,spring容器只有在需要时才会实例化,初始化它。spring容器不缓存prototype类型的bean,使得无法提前暴露出一个创建中的bean。
4.面试问题
1.第三级缓存是没有实际作用的,为什么不使用一级缓存和二级缓存能解决循环依赖?
答:如果只用一级缓存来解决循环依赖,那么一级缓存中会在某个时间段存在不完整的bean,这是不安全的。
使用二级缓存确实可以解决循环依赖,但是这要求每个原始对象创建出来后就立即生成动态代理对象(如果有的AOP代理增强话),然后将这个动态代理对象放入二级缓存,这就打破了Spring对AOP的设计原则,即:在对象初始化完毕后,再去创建代理对象。所以引入三级缓存,并且在三级缓存中存放一个对象的ObjectFactory,目的就是:延迟代理对象的创建,这里延迟到啥时候创建呢,有两种情况:第一种就是确实存在循环依赖,那么没办法,只能在需要的时候就创建出来代理对象然后放到二级缓存中,第二种就是不存在循环依赖,那就应该正常地在初始化的后置处理器中创建。
因此不直接使用一级缓存和二级缓存来解决循环依赖的原因就是:希望在不存在循环依赖的情况下不破坏Spring对AOP的设计原则。
所以总结来说,如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。
2.Spring是如何解决的循环依赖?
答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么通过这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
简单点说,Spring解决循环依赖的思路就是:当A的bean需要B的bean的时候,提前将A的bean放在缓存中(实际是将A的ObjectFactory放到三级缓存),然后再去创建B的bean,但是B的bean也需要A的bean,那么这个时候就去缓存中拿A的bean,B的bean创建完毕后,再回来继续创建A的bean,最终完成循环依赖的解决。Spring 利用 三级缓存 巧妙地将出现 循环依赖 时的 AOP 操作 提前到了 属性注入 之前(通过第三级缓存实现的),解决了循环依赖问题。
引用:https://blog.csdn.net/cy973071263/article/details/132676795