Spring-针对循环引用(基于XML)

341 阅读3分钟

前言

继标题, 循环引用应该算是Spring里一个比较难搞懂的知识点,因为在底层对于这种情况的处理方式十分奇怪,因此各个教程的教师都会结合源码对循环引用进行讲解,但是,源码对于我们这些初学者,哪看得懂啊

所以,这篇博客主要想讲一下循环引用的一些细节,进一步地了解这个复杂的家伙。

正文

1.循环引用产生的原因

循环引用是指两个或多个类相互依赖于对方,形成无限递归调用的过程。在这个概念下,产生的原因主要有以下几种:

  1. 单例Bean之间相互依赖;

  2. 通过构造函数注入依赖时,存在构造函数参数相互依赖的情况;

  3. 对属性进行setter方法注入时,存在相互依赖的情况。

而这几种情况的共同点是在beanA中出现了beanB的配置信息,在beanB中出现了beanA配置信息,

由于对XML中的bean ,当一个bean中引用了另外一个bean时,会先去完成另外一个bean的创建过程,这就会导致循环引用。如以下代码:

<!--    由于原理一致,这里仅示例 setter 注入时发生循环引用的情况-->
    
<!--    VipDao 中有 ref="vipService" 即对VipService的引用-->
    <bean id="VipDao" class="BaseBeans.VipUserDao">
        <property name="vipService" ref="vipService"/>
    </bean>
    
<!--   VipService中有 ref="VipDao" 即对VipUserDao的引用  -->
    <bean id="vipService" class="BaseBeans.VipService">
        <property name="vipUserDao" ref="VipDao"/>
    </bean>

2.循环引用的解决方案

由上所述,我们已经知道循环引用的原因是不同的bean之间互相的引用,那么,解决方案就很明显了,只需要将它们相互之间的引用打断即可,但是由于种种原因,bean之间的引用不能直接中断,所以这里就涉及了一个很重要的知识点: 三级缓存

拓展:在spring中,

singletonObjects (bean单例池)被称为一级缓存,它主要存储单例bean的成品,即实例化与初始化均已完成的bean

earlySingletonObjects(早期bean单例池)被称作二级缓存,缓存半成品对象,这种半成品对象一般都完成了实例化,且已经被其他对象引用。

singletonFactories(单例bean的工厂池) 被称作三级缓存,缓存半成品对象,这种半成品对象一般完成了实例化,但未被其他对象引用,使用时需通过工厂创建bean

循环引用被发现时,Spring会将正在创建的bean注册到三级缓存中。当后续需要创建的bean依赖 已经在三级缓存中的Bean时,Spring会从缓存中获取该bean并继续创建对象,从而可以避免循环依赖带来的问题。

换句话来讲,就是当处于二级缓存的 A对象 获取了存在三级缓存中的B对象时,由于B对象没有对 A对象 的引用(未开始初始化),就不会触发循环引用问题。

当这个 A对象 初始化完成后进入一级缓存,B对象可以直接引用A对象然后完成初始化进入第二缓存,当进入第二缓存成功后,Spring就会删除B对象在三级缓存中的数据以节约内存。

3.循环引用的避免

如前面所分析的,避免循环引用的方法当然就是避免对象互相之间的引用。即便已经知道了方法,在这里我还是想提一提:

  1. 减少单例对象之间的相互依赖;
  2. 避免构造函数参数相互依赖;
  3. 将依赖关系调整为非循环,这是一种最好的做法。

总结

循环引用是一个常见的问题,在使用Spring进行依赖注入时,我们应当注意避免循环引用,并试着去理解它的解决方法。