Spring三级缓存解析:循环依赖解决机制
时间:2025-09-06 17:53:36 141浏览 收藏
Spring框架通过精妙的三级缓存机制,巧妙且高效地解决了单例Bean之间的循环依赖问题,这对于理解Spring底层机制至关重要。本文深入解析Spring的三级缓存:一级缓存(singletonObjects)存放完全初始化的Bean,二级缓存(earlySingletonObjects)存放早期引用的Bean,三级缓存(singletonFactories)存放生成早期引用的ObjectFactory。当Bean A依赖Bean B,而Bean B又依赖Bean A时,Spring会将A的ObjectFactory放入三级缓存,B通过该工厂获取A的早期引用,并放入二级缓存。三级缓存的关键在于它能按需生成代理后的早期引用,确保AOP功能正确执行。通过singletonsCurrentlyInCreation标识,Spring能精准判断并触发三级缓存机制,从而打破循环依赖,避免创建死锁。
Spring通过三级缓存机制解决单例Bean的循环依赖问题,其中一级缓存(singletonObjects)存放完全初始化的Bean实例,二级缓存(earlySingletonObjects)存放早期引用的Bean实例,三级缓存(singletonFactories)存放用于生成早期引用的ObjectFactory;当Bean A创建过程中依赖Bean B,而Bean B又依赖Bean A时,Spring会将A的ObjectFactory放入三级缓存,B在需要A时通过该工厂获取A的早期引用(可能是代理对象),并将其放入二级缓存供B使用,待B初始化完成后,A继续初始化并最终将完整实例放入一级缓存,从而打破循环依赖;之所以需要三级缓存而非二级,是因为在涉及AOP代理时,若直接将原始对象放入二级缓存会导致B持有的是未代理的实例,而三级缓存的ObjectFactory能按需生成代理后的早期引用,确保AOP功能正确执行;Spring通过singletonsCurrentlyInCreation标识当前正在创建的Bean,当发现某Bean正在创建中又被请求时,即触发从三级缓存获取ObjectFactory并生成早期引用的机制,从而精准判断并解决循环依赖。
Spring框架通过其精妙的三级缓存机制,巧妙且高效地解决了单例Bean之间的循环依赖问题。它并非简单地依靠一次性创建,而是在Bean的生命周期不同阶段,通过暴露不同状态的Bean实例,最终确保所有相互依赖的Bean都能被正确初始化并注入,避免了传统方式下因依赖无法满足而导致的创建死锁。这种设计是Spring底层机制中一个非常值得深入理解的亮点。
解决方案
Spring解决循环依赖的核心在于其三级缓存的设计与协同工作。这个过程可以概括为:当一个Bean A开始创建,并被实例化后(但尚未完成属性填充和初始化方法调用),它会先将自己包装成一个ObjectFactory
放入第三级缓存。如果此时Bean A在后续的属性填充过程中需要依赖Bean B,而Bean B又反过来依赖Bean A,那么当Spring尝试创建Bean B并发现它需要Bean A时,它会首先从缓存中查找Bean A。由于Bean A的ObjectFactory
已存在于第三级缓存,Spring就能通过这个工厂获取到Bean A的“早期引用”(一个尚未完全初始化的实例)。这个早期引用随后会被放入第二级缓存,供Bean B使用。这样,Bean B就能顺利完成初始化。当Bean B初始化完成后,Bean A可以继续它的初始化过程,填充属性(包括已经完整初始化好的Bean B),并最终完成自身初始化。一旦Bean A完全初始化,它的完整实例会替换掉第二级缓存中的早期引用,并最终被放入第一级缓存。
具体来说,这三级缓存分别是:
- 一级缓存(
singletonObjects
): 存放已经完全初始化并可供使用的单例Bean。这是最“成熟”的Bean。 - 二级缓存(
earlySingletonObjects
): 存放早期暴露的Bean实例。这些Bean可能已经实例化但尚未完成属性填充或初始化方法调用,也可能已经经过了AOP代理。 - 三级缓存(
singletonFactories
): 存放的是ObjectFactory
,一个能够生产早期Bean实例的工厂。这个工厂在需要时才会被调用,用于获取Bean的早期引用,特别是在处理AOP代理时显得尤为重要。
Spring三级缓存具体指哪些,它们各自承担了什么职责?
在Spring的单例Bean生命周期管理中,这三层缓存各司其职,共同构筑了一个健壮的循环依赖解决方案。
首先是singletonObjects
,这是最核心的一级缓存,它直接映射了Bean名称到其完全初始化、可供使用的单例实例。可以把它想象成一个“成品仓库”,所有经过完整生命周期(实例化、属性填充、初始化方法执行)的Bean最终都会安家于此。当其他Bean需要依赖时,Spring会优先从这里查找。
接着是earlySingletonObjects
,这是二级缓存,它扮演了一个“半成品仓库”的角色。当一个Bean被实例化后,但其属性尚未完全填充,或者初始化方法尚未执行时,它的早期引用(可能是原始对象,也可能是经过AOP处理的代理对象)就会被放置在这里。这个缓存的存在,是为了在循环依赖发生时,能够提供一个“临时”的、可用的Bean引用,让依赖它的Bean能够继续初始化。一旦这个Bean完成了所有初始化步骤,它就会从二级缓存中移除,并晋升到一级缓存。
最后,也是最关键的,是singletonFactories
,这是三级缓存,它存储的不是Bean实例本身,而是一个ObjectFactory
(对象工厂)。这个工厂的职责是“按需生产”Bean的早期引用。为什么需要一个工厂而不是直接存放早期实例呢?这主要是为了解决AOP代理的复杂性。如果一个Bean需要被AOP代理,那么它的早期引用应该是代理对象,而不是原始对象。这个ObjectFactory
就封装了生成代理对象的逻辑。只有当其他Bean真正需要这个循环依赖的Bean时,Spring才会调用这个ObjectFactory
来获取早期引用。这样,就避免了不必要的AOP代理创建,也确保了获取到的早期引用是正确的(即可能是代理后的)。
这三者协同工作,构成了一个巧妙的“生产线”。当A需要B,B需要A时,A实例化后,其ObjectFactory
先入三级缓存。B需要A时,通过三级缓存的ObjectFactory
拿到A的早期引用,放入二级缓存供B使用。B初始化完毕,A继续初始化,最终A的完整实例入一级缓存。这种机制确保了在Bean还未完全“成熟”时,也能提供一个可用的引用,从而打破循环依赖的僵局。
为什么需要三级缓存,二级缓存不能解决循环依赖吗?
这是一个非常经典的问题,也是理解Spring循环依赖解决机制深度的关键点。答案是:如果仅仅是纯粹的循环依赖(即没有AOP代理),二级缓存确实可以解决问题。但一旦涉及到AOP(面向切面编程),二级缓存就显得力不从心了,这就是三级缓存存在的根本原因。
想象一下这个场景:Bean A依赖Bean B,Bean B依赖Bean A,并且Bean A还需要被AOP代理。
如果只有一级和二级缓存:
- Spring开始创建Bean A,实例化Bean A的原始对象。
- 将Bean A的原始对象(早期引用)放入二级缓存
earlySingletonObjects
。 - Bean A在填充属性时需要Bean B,Spring开始创建Bean B。
- Bean B在填充属性时需要Bean A,从二级缓存
earlySingletonObjects
中获取到Bean A的原始对象。 - Bean B完成初始化。
- Bean A继续初始化,此时Spring会执行AOP代理逻辑,生成Bean A的代理对象。
- 问题来了:Bean B持有的是Bean A的原始对象引用,而不是AOP代理后的引用。这导致Bean B调用的A的方法不会经过AOP切面,这显然是不正确的行为。
而三级缓存的引入,正是为了解决这个“AOP代理时机”的问题。三级缓存singletonFactories
中存储的是一个ObjectFactory
,这个工厂能够按需生成Bean的早期引用。这个“按需”和“生成”是关键。
当Bean A实例化后,Spring会将其原始对象包装成一个ObjectFactory
放入三级缓存。这个ObjectFactory
里面封装了生成AOP代理的逻辑(如果需要代理的话)。
当Bean B需要Bean A时,它会从三级缓存中获取到这个ObjectFactory
,然后调用getObject()
方法。此时,getObject()
方法会判断Bean A是否需要AOP代理。如果需要,它就会立即生成Bean A的代理对象,并将这个代理对象作为早期引用返回给Bean B。这个代理对象随后会被放入二级缓存,同时从三级缓存中移除。
这样一来,Bean B获取到的就是Bean A的“正确”引用——一个可能已经被AOP代理过的对象。当Bean A最终完成所有初始化步骤时,它的一级缓存中的最终实例(也可能是代理对象)会替换掉二级缓存中的早期引用。整个过程中,所有Bean都持有了彼此的正确引用,包括AOP代理后的实例。
所以,三级缓存的真正价值在于它提供了一个“延迟生成AOP代理”的机制,确保了在循环依赖场景下,注入的早期引用是经过AOP处理的(如果需要),从而保证了AOP功能的正确性。没有它,AOP和循环依赖的结合就会出现问题。
Spring是如何判断并触发三级缓存机制来解决循环依赖的?
Spring在Bean的创建过程中,通过一套严谨的内部状态管理和查找逻辑来判断并触发三级缓存机制,从而解决循环依赖。这个过程主要发生在DefaultSingletonBeanRegistry
类中的getSingleton()
方法内部,这是Spring获取单例Bean的核心入口。
当Spring尝试获取一个单例Bean时(例如,通过getBean()
方法),它会按照以下步骤进行:
- 尝试从一级缓存(
singletonObjects
)获取: 这是最直接的查找,如果Bean已经完全初始化并存在于此,直接返回。 - 检查Bean是否正在创建中: 如果一级缓存中没有,Spring会检查
singletonsCurrentlyInCreation
这个Set。这个Set记录了当前正在创建过程中的Bean名称。如果发现当前请求的Bean正在创建中,这就意味着可能存在循环依赖。 - 允许早期引用(
allowEarlyReference
): 对于单例Bean,Spring默认是允许早期引用的。如果Bean正在创建中且允许早期引用,Spring会进一步尝试从二级或三级缓存获取。 - 尝试从二级缓存(
earlySingletonObjects
)获取: 如果Bean正在创建中,并且之前已经有其他Bean请求过它的早期引用,那么这个早期引用可能已经被放入了二级缓存。如果能找到,直接返回这个早期引用。 - 尝试从三级缓存(
singletonFactories
)获取: 如果二级缓存中也没有,但Bean确实正在创建中,Spring会检查三级缓存。如果三级缓存中存在这个Bean对应的ObjectFactory
,Spring就会调用这个ObjectFactory
的getObject()
方法。getObject()
方法会负责生成Bean的早期引用。这个早期引用可能是原始的Bean实例,也可能是在AOP增强逻辑作用下生成的代理实例。- 一旦通过
ObjectFactory
获取到早期引用,Spring会立即将这个早期引用放入二级缓存earlySingletonObjects
,并同时从三级缓存singletonFactories
中移除对应的ObjectFactory
。这是为了避免重复生成早期引用,也确保了后续对该Bean早期引用的请求直接从二级缓存获取,效率更高。
- 注入并完成初始化: 获取到早期引用后,依赖它的Bean就可以继续完成属性填充和初始化。当这个早期引用所对应的Bean最终完成所有初始化步骤后,它的完整实例会晋升到一级缓存,替换掉之前在二级缓存中的早期引用。
这个机制的关键在于singletonsCurrentlyInCreation
这个Set,它就像一个“正在施工”的标志牌。当Spring发现一个Bean正在施工中又被请求时,它就知道可能遇到了循环依赖,于是会启动从二级和三级缓存获取早期引用的逻辑。这种设计确保了只有在真正需要时才触发三级缓存的ObjectFactory
,既解决了循环依赖,又兼顾了性能和AOP的正确性。
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于文章的相关知识,也可关注golang学习网公众号。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
392 收藏
-
333 收藏
-
205 收藏
-
137 收藏
-
471 收藏
-
281 收藏
-
468 收藏
-
148 收藏
-
412 收藏
-
217 收藏
-
495 收藏
-
387 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 514次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 499次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习