引言
当两个或多个 Bean 相互交织引用,形成一个无法打破的闭环时,不仅会在创建 Bean 的过程中引发无休止的循环,还可能导致内存资源的大量消耗,最终引发内存溢出等灾难性后果。然而,Spring 框架凭借其卓越的设计智慧,成功地化解了这一棘手难题。在接下来的篇章中,我们将深入剖析 Spring 是如何巧妙地解决 Bean 的循环依赖问题,并对相关的源码进行逐行解读,揭开这一神秘面纱背后的技术奥秘。
循环依赖的基本概念
在我们正式踏入 Spring 解决循环依赖的奇妙世界之前,有必要先牢固掌握循环依赖的核心概念。循环依赖,这一复杂而又微妙的现象,可以在任意数量的 Bean 之间悄然浮现。它不仅仅局限于简单的两两相互引用,甚至可能延伸至 Bean 的自我引用,从而构建出一个错综复杂的依赖网络。
在未借助 Spring 框架的原生环境中,如果在构造函数之间不幸出现了循环依赖的情况,那么程序在运行时将会陷入一个永无止境的循环调用深渊。随着时间的推移,调用栈不断堆积,最终必然导致 StackOverflowError
的爆发,使整个应用陷入崩溃的边缘。
Spring 中的循环依赖场景
在 Spring 的广袤世界里,循环依赖主要分化为三种引人瞩目的场景,每一种都具有其独特的特性和挑战。
构造器注入循环依赖:这无疑是循环依赖中最为棘手的难题。因为构造器肩负着实例化对象的重任,一旦循环依赖在构造器中生根发芽,整个实例化过程就会如同陷入泥沼,无法顺利完成。想象一下,多个对象在构造器中相互依赖,彼此等待对方先完成初始化,结果只能是无尽的等待和程序的停滞。
Field 属性注入(setter 方法注入)循环依赖:这是我们在日常开发中最为常见的循环依赖场景。然而,幸运的是,Spring 凭借其独特的机制,成功地在这片荆棘中开辟出了一条通路,有效地解决了这一问题。
prototype 作用域下的 Field 属性注入循环依赖:prototype 作用域的 Bean 宛如一颗闪烁着独特光芒的星星。在每次请求时,它都会崭新地创建实例。正因如此,循环依赖问题在这种情况下表现得相对隐晦,但这并不意味着我们可以对其视而不见,仍然需要我们精心处理,以确保系统的稳定与可靠。
Spring 解决循环依赖的原理
Spring 之所以能够在循环依赖的重重迷雾中找到出路,关键在于其巧妙运用了 “三级缓存” 和 “引用传递” 的创新理念。接下来,让我们一同深入探究这三级缓存的神奇作用以及其背后精妙的实现原理。
三级缓存机制
在 Spring 容器的内部,存在着一套精心设计的三级缓存体系,如同三把利剑,专门用来斩断单例 Bean 循环依赖的乱麻。
一级缓存(singletonObjects):这是 Spring 容器中的核心缓存区域,存放着那些已经完全初始化好的 Bean 实例。从这里获取的 Bean 如同已经磨砺完备的宝剑,可以直接投入到战斗中,无需任何额外的加工和处理。
二级缓存(earlySingletonObjects):这里存放的是原始的 Bean 实例,但它们的属性尚未被完整填充。这些未成熟的 Bean 实例在解决循环依赖的过程中扮演着关键的角色,如同临时的桥梁,帮助跨越依赖的鸿沟。
三级缓存(singletonFactories):这一级缓存存放的是 Bean 工厂对象。它们就像是神奇的魔法工坊,不仅能够生成 Bean 实例,还在解决循环依赖的战役中发挥着不可或缺的作用。
引用传递
在 Java 的世界中,实际上只有值传递这一基本原则。然而,Spring 的循环依赖解决方案却在概念上巧妙地借鉴了引用传递的思想。在 Spring 的框架下,当我们获取对象的引用时,对象的属性可以被智慧地延后设置。这一独特的策略为解决循环依赖问题打开了一扇全新的大门,提供了无限的可能。
Spring 创建 Bean 的流程
要想真正洞悉 Spring 解决循环依赖的奥秘,深入了解 Spring 创建 Bean 的流程是至关重要的一步。这个流程就像是一部精心编排的交响乐,每个音符都恰到好处,共同奏响了成功的乐章。
createBeanInstance:这是整个流程的起始乐章,通过调用对象的构造方法,为 Bean 的诞生奠定基础,实例化出对象的雏形。
populateBean:紧接着,进入到填充属性的阶段,就如同为刚刚诞生的对象穿上华丽的外衣,对 Bean 的依赖属性进行注入,比如通过
@Autowired
注解实现自动装配。initializeBean:最后,执行初始化回调方法,如
initMethod
、InitializingBean
等,为 Bean 注入生命的活力,使其完全准备好投入到应用的运行中。需要特别注意的是,循环依赖问题往往在
populateBean
阶段悄然浮现,这也是我们需要重点关注和解决的关键环节。
源码解读
接下来,让我们勇敢地深入 Spring 的源码世界,用放大镜仔细观察每一行代码,剖析它是如何巧妙地解决循环依赖问题的。
getSingleton 方法
getSingleton
方法无疑是获取单例 Bean 的关键钥匙,它巧妙地涉及到了三级缓存的精细运用。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// 省略其他代码...
public Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory!= null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
// 省略其他代码...
}
在这段代码中,首先在一级缓存 singletonObjects
中查找指定的 Bean 实例。如果未找到,并且当前 Bean 正在创建中,就会在二级缓存 earlySingletonObjects
中继续查找。如果还是未找到且允许提前引用,就会从三级缓存 singletonFactories
中获取 ObjectFactory
,通过它来获取 Bean 实例,并将其放入二级缓存,同时从三级缓存中移除。
doCreateBean 方法
doCreateBean
方法则是创建 Bean 的核心引擎,它涵盖了 Bean 从实例化、属性填充到初始化的全过程。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
// 省略其他代码...
protected Object doCreateBean(String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
// 省略其他代码...
// 创建Bean对象,并且将对象包裹在BeanWrapper中
instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = instanceWrapper.getWrappedInstance();
// 省略其他代码...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 填充属性,解决@Autowired依赖
populateBean(beanName, mbd, instanceWrapper);
// 执行初始化回调方法们
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 循环依赖校验
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference!= null && exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
return exposedObject;
}
// 省略其他代码...
}
在这个方法中,首先创建 Bean 对象并将其包裹在 BeanWrapper
中。然后,判断是否支持循环依赖,如果支持,就将一个 ObjectFactory
添加到三级缓存中。接下来进行属性填充和初始化操作。最后,进行循环依赖的校验和处理。
循环依赖的处理
在 doCreateBean
方法中,当 Bean 支持循环依赖(即 earlySingletonExposure
为 true
)时,一个精妙的操作就会发生。此时,会将一个 ObjectFactory
添加到三级缓存中。这个 ObjectFactory
就像是一个待命的奇兵,当需要解决循环依赖时,它能够提前获取 Bean 的引用,即使该 Bean 还没有完全初始化。
AOP 代理对象的创建
Spring AOP 如同一位神奇的魔法师,通过代理对象来实现令人惊叹的面向切面编程。在处理循环依赖这一复杂场景时,Spring 展现出了其卓越的智慧和严谨的设计。它确保即使在循环依赖的复杂情况下,放入容器的始终是代理对象,而不是原始对象。这一精妙的实现是通过 AbstractAutoProxyCreator
类来完成的。在 getEarlyBeanReference
方法中,提前创建代理对象,为后续的处理奠定基础。而在 postProcessAfterInitialization
方法中,则进一步处理代理对象的创建,确保整个过程的无缝衔接和稳定运行。
结论
Spring 凭借其独特的三级缓存和引用传递机制,巧妙地化解了循环依赖这一难题。这一创新的解决方案不仅在普通的 Bean 中发挥着神奇的作用,即使面对需要 AOP 代理的复杂 Bean ,也能游刃有余,确保应用在复杂的依赖关系中依然能够稳定前行。同时,Spring 以其简洁易用的 API ,对使用者屏蔽了内部复杂的实现细节,让开发者能够专注于业务逻辑的实现,而无需在底层技术的泥潭中苦苦挣扎。
总结
本文深入探讨了 Spring 如何解决 Bean 的循环依赖问题,通过对源码的抽丝剥茧般的解读,我们揭开了 Spring IOC 容器背后的神秘面纱,领略了其工作原理的精妙之处。深入理解这些原理不仅对于我们熟练掌握 Spring 框架具有重要意义,更是在面对复杂的系统问题时进行高效排查和解决的关键所在。希望这篇文章能够成为广大开发者在探索 Spring 框架道路上的一盏明灯,帮助大家更好地理解和运用这一强大的框架,从而在软件开发的征程中创造出更加卓越和稳定的应用。
我叫马丁,是一名专业的 Java 程序员,经常在 CSDN 平台撰写技术博客。如果您觉得这篇文章对您有所帮助,欢迎点赞、收藏、评论,您的支持是我持续创作的动力。同时,也期待您的关注,让我们一起在技术的海洋中不断探索前行!