源码级详解Spring的三级缓存: 循环依赖的处理流程

Spring源码解析简图:

图片

Spring 如何解决循环依赖,⽹上的资料很多,但是感觉写得好的极少,特别是源码解读⽅⾯,我就⾃⼰单独出⼀ 篇,这篇⽂章绝对肝!

文章目录:

图片

一. 基础知识

1.1 什么是循环依赖 ?

⼀个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成⼀个环形调⽤,有下⾯ 3 种⽅式。

图片

我们看⼀个简单的 Demo,对标“情况 2”。

@ServicepublicclassModel1{@Autowiredprivate Model2 model2;publicvoidtest1(){    }}@ServicepublicclassModel2{@Autowiredprivate Model1 model1;publicvoidtest2(){    }}

这是⼀个经典的循环依赖,它能正常运⾏,后⾯我们会通过源码的⻆度,解读整体的执⾏流程。

1.2 三级缓存

解读源码流程之前,spring 内部的三级缓存逻辑必须了解,要不然后⾯看代码会蒙圈。

图片

第⼀级缓存:singletonObjects,⽤于保存实例化、注⼊、初始化完成的 bean 实例;

第⼆级缓存:earlySingletonObjects,⽤于保存实例化完成的 bean 实例;

第三级缓存:singletonFactories,⽤于保存 bean 创建⼯⼚,以便后⾯有机会创建代理对象。

图片

这是最核⼼,我们直接上源码:

图片

执⾏逻辑:

图片

先从“第⼀级缓存”找对象,有就返回,没有就找“⼆级缓存”;

找“⼆级缓存”,有就返回,没有就找“三级缓存”;

找“三级缓存”,找到了,就获取对象,放到“⼆级缓存”,从“三级缓存”移除。

图片

1.3 原理执⾏流程

我把“情况 2”执⾏的流程分解为下⾯ 3 步,是不是和“套娃”很像 ?

图片

整个执⾏逻辑如下:

引用字数:386/300(单次引用不得超过300字,避免不合理引用)

图片

在第⼀层中,先去获取 A 的 Bean,发现没有就准备去创建⼀个,然后将 A 的代理⼯⼚放⼊“三级缓存”(这个 A 其实是⼀个半成品,还没有对⾥⾯的属性进⾏注⼊),但是 A 依赖 B 的创建,就必须先去创建 B;

在第⼆层中,准备创建 B,发现 B ⼜依赖 A,需要先去创建 A;

在第三层中,去创建 A,因为第⼀层已经创建了 A 的代理⼯⼚,直接从“三级缓存”中拿到 A 的代理⼯⼚,获 取 A 的代理对象,放⼊“⼆级缓存”,并清除“三级缓存”;

回到第⼆层,现在有了 A 的代理对象,对 A 的依赖完美解决(这⾥的 A 仍然是个半成品),B 初始化成功;

回到第⼀层,现在 B 初始化成功,完成 A 对象的属性注⼊,然后再填充 A 的其它属性,以及 A 的其它步骤 (包括 AOP),完成对 A 完整的初始化功能(这⾥的 A 才是完整的 Bean)。

将 A 放⼊“⼀级缓存”。

图片

为什么要⽤ 3 级缓存 ?我们先看源码执⾏流程,后⾯我会给出答案。

二. 源码解读

图片

注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不⼀样!!!

图片

上⾯的知识,⽹上其实都有,下⾯才是我们的重头戏,让我们一起⾛⼀遍代码流程。

2.1 代码⼊⼝

图片

图片

这⾥需要多跑⼏次,把前⾯的 beanName 跳过去,只看 Model1。

图片

图片

2.2 第⼀层

图片

进⼊ doGetBean(),从 getSingleton() 没有找到对象,进⼊创建 Bean 的逻辑。

图片

图片

进⼊ doCreateBean() 后,调⽤ addSingletonFactory()。

图片

往三级缓存 singletonFactories 塞⼊ model1 的⼯⼚对象。

图片

图片

图片

进⼊到 populateBean(),执⾏ postProcessProperties(),这⾥是⼀个策略模式,找到下图的策略对象。

图片

正式进⼊该策略对应的⽅法。

图片

下⾯都是为了获取 model1 的成员对象,然后进⾏注⼊。

图片

图片

图片

图片

进⼊ doResolveDependency(),找到 model1 依赖的对象名 model2

图片

需要获取 model2 的 bean,是 AbstractBeanFactory 的⽅法。

图片

正式获取 model2 的 bean。

图片

到这⾥,第⼀层套娃基本结束,因为 model1 依赖 model2,下⾯我们进⼊第⼆层套娃。

2.3 第⼆层

图片

获取 louzai2 的 bean,从 doGetBean(),到 doResolveDependency(),和第⼀层的逻辑完全⼀样,找到 model2 依赖的对象名 model1。

前⾯的流程全部省略,直接到 doResolveDependency()。

图片

正式获取 model1 的 bean。

图片

到这⾥,第⼆层套娃结束,因为 model2 依赖 model1,所以我们进⼊第三层套娃。

2.4 第三层

图片

获取 model1 的 bean,在第⼀层和第⼆层中,我们每次都会从 getSingleton() 获取对象,但是由于之前没有初始 化 model1 和 model2 的三级缓存,所以获取对象为空。

图片

敲重点!敲重点!!敲重点!!!

到了第三层,由于第三级缓存有 model1 数据,这⾥使⽤三级缓存中的⼯⼚,为 model2 创建⼀个代理对象,塞⼊ ⼆级缓存。

图片

这⾥就拿到了 model1 的代理对象,解决了 model2 的依赖关系,返回到第⼆层。

2.5 返回第⼆层

返回第⼆层后,model2 初始化结束,这⾥就结束了么?⼆级缓存的数据,啥时候会给到⼀级呢?

甭着急,看这⾥,还记得在 doGetBean() 中,我们会通过 createBean() 创建⼀个 model2 的 bean,当 model2 的 bean 创建成功后,我们会执⾏ getSingleton(),它会对 model2 的结果进⾏处理。

图片

我们进⼊ getSingleton(),会看到下⾯这个⽅法。

图片

这⾥就是处理 model2 的 ⼀、⼆级缓存的逻辑,将⼆级缓存清除,放⼊⼀级缓存。

图片

2.6 返回第⼀层

同 2.5,model1 初始化完毕后,会把 model1 的⼆级缓存清除,将对象放⼊⼀级缓存。

图片

到这⾥,所有的流程结束,我们返回 model1 对象。

三. 原理深度解读

3.1 什么要有 3 级缓存 ?

这是⼀道⾮常经典的⾯试题,前⾯已经告诉⼤家详细的执⾏流程,包括源码解读,但是没有告诉⼤家为什么要⽤ 3 级缓存?

这⾥是重点!敲⿊板!!!

我们先说“⼀级缓存”的作⽤,变量命名为 singletonObjects,结构是 Map,它就是⼀个单例池, 将初始化好的对象放到⾥⾯,给其它线程使⽤,如果没有第⼀级缓存,程序不能保证 Spring 的单例属性

“⼆级缓存”先放放,我们直接看“三级缓存”的作⽤,变量命名为 singletonFactories,结构是 Map>,Map 的 Value 是⼀个对象的代理⼯⼚,所以“三级缓存”的作⽤,其实就是⽤来存放对象的代 理⼯⼚

那这个对象的代理⼯⼚有什么作⽤呢,我先给出答案,它的主要作⽤是存放半成品的单例 Bean,⽬的是为了“打破 循环”,可能⼤家还是不太懂,这⾥我再稍微解释⼀下。

我们回到⽂章开头的例⼦,创建 A 对象时,会把实例化的 A 对象存⼊“三级缓存”,这个 A 其实是个半成品,因为没 有完成依赖属性 B 的注⼊,所以后⾯当初始化 B 时,B ⼜要去找 A,这时就需要从“三级缓存”中拿到这个半成品的 A(这⾥描述,其实也不完全准确,因为不是直接拿,为了让⼤家好理解,我就先这样描述),打破循环

那我再问⼀个问题,为什么“三级缓存”不直接存半成品的 A,⽽是要存⼀个代理⼯⼚呢 ?答案是因为 AOP。

在解释这个问题前,我们看⼀下这个代理⼯⼚的源码,让⼤家有⼀个更清晰的认识。

直接找到创建 A 对象时,把实例化的 A 对象存⼊“三级缓存”的代码,直接⽤前⾯的两幅截图。

图片

图片

下⾯我们主要看这个对象⼯⼚是如何得到的,进⼊ getEarlyBeanReference() ⽅法。

图片

图片

最后⼀幅图太重要了,我们知道这个对象⼯⼚的作⽤:

图片

如果 A 有 AOP,就创建⼀个代理对象;

如果 A 没有 AOP,就返回原对象。

图片

图片

那“⼆级缓存”的作⽤就清楚了,就是⽤来存放对象⼯⼚⽣成的对象,这个对象可能是原对象,也可能是个代理对 象。

图片

我再问⼀个问题,为什么要这样设计呢?把⼆级缓存⼲掉不⾏么 ?我们继续往下看。

3.2 能⼲掉第 2 级缓存么 ?

@ServicepublicclassA{@Autowiredprivate B b;@Autowiredprivate C c;publicvoidtest1(){    }}
@ServicepublicclassB{@Autowiredprivate A a;publicvoidtest2(){    }}
@ServicepublicclassC{@Autowiredprivate A a;publicvoidtest3(){    }}

根据上⾯的套娃逻辑,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A。

假如 A 需要进⾏ AOP,因为代理对象每次都是⽣成不同的对象,如果⼲掉第⼆级缓存,只有第⼀、三级缓存:

图片

B 找到 A 时,直接通过三级缓存的⼯⼚的代理对象,⽣成对象 A1。

C 找到 A 时,直接通过三级缓存的⼯⼚的代理对象,⽣成对象 A2。

图片

看到问题没?你通过 A 的⼯⼚的代理对象,⽣成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个⼆级缓存,把 A1 存下来,下次再获取时,直接从⼆级缓存获取,⽆需再⽣成新的代理对象。

所以“⼆级缓存”的⽬的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。

如果没有 AOP 的话,我们其实只要 1、3 级缓存,就可以满⾜要求。

4. 总结

我们再回顾⼀下 3 级缓存的作⽤:

图片

⼀级缓存:为“Spring 的单例属性”⽽⽣,就是个单例池,⽤来存放已经初始化完成的单例 Bean;

⼆级缓存:为“解决 AOP”⽽⽣,存放的是半成品的 AOP 的单例 Bean;

三级缓存:为“打破循环”⽽⽣,存放的是⽣成半成品单例 Bean 的⼯⼚⽅法。

图片

如果你能理解上⾯我说的三条,恭喜你,你对 Spring 的循环依赖理解得⾮常透彻!

关于循环依赖的知识,其实还有,因为篇幅原因,就不再写了,这篇⽂章的重点,⼀⽅⾯是告诉⼤家循环依赖的 核⼼原理,另⼀⽅⾯是让⼤家⾃⼰去 debug 代码,跑跑流程,挺有意思的。

这⾥说⼀下我看源码的⼼得:

图片

需要掌握基本的设计模式;

看源码前,最好能找⼀些理论知识先看看;

英语能力,学会读英⽂注释,不会的话就百度翻译;

debug 时,要克制⾃⼰,不要陷⼊⽆⽤的细节,这个最重要。

图片

其中最难的是第 4 步,因为很多同学看 Spring 源码,每看⼀个⽅法,就想多研究研究,这样很容易被绕进去了, 这个要学会克制,有全剧意识,并能分辨哪⾥是核⼼逻辑,⾄于如何分辨,可以在⽹上先找些资料,如果没有的话, 就只能多看代码了。

<think>首先,用户的问题是关于Spring的第三级缓存的详细信息。他们提供了几个引用,我需要参考这些引用来构建回答。关键点是理解三级缓存的作用、如何工作以及为什么重要。 回顾引用: - 引用[1]:解释了三级缓存是什么,涉及Spring Bean的生命周期和创建过程。它提到三级缓存是为了解决循环依赖和支持AOP。 - 引用[2]:深度解析三级缓存的实现原理,包括创建Bean的流程图和一些关键方法如getBean(), doGetBean(), createBean(), doCreateBean(), getSingleton()等。 - 引用[3]:讨论了三级缓存的作用,包括解决循环依赖、提高性能和支持AOP代理。给出了一个示例代码。 - 引用[4]:提到当有超过两个对象相互依赖时,会用到二级缓存,以避免每次从三级缓存创建的实例不同。 - 引用[5]:解释了三级缓存的必要性,特别是在AOP场景下。它强调一级缓存存储完全创建好的Bean,二级缓存存储代理对象,三级缓存存储ObjectFactory(用于创建对象的工厂)。 用户的问题是:“我想了解Spring的第三级缓存的详细信息 请问Spring三级缓存详细解析”。所以,我需要专注于第三级缓存。 从引用中提取关键点: - 第三级缓存(通常称为三级缓存)在Spring中是用于存储ObjectFactory的。ObjectFactory是一个函数或接口,用于创建Bean的实例。 - 其主要目的是解决循环依赖问题。例如,当Bean A依赖Bean B,而Bean B又依赖Bean A时,三级缓存确保在Bean创建过程中不会无限递归。 - 三级缓存支持AOP代理:在AOP场景下,代理对象需要在适当的时候创建,三级缓存帮助处理这个。 - 实现细节:三级缓存在getSingleton()方法中处理,涉及检查缓存层级。 - 相比于其他缓存:一级缓存(singletonObjects)存储完全初始化的Bean;二级缓存(earlySingletonObjects)存储早期Bean实例(已实例化但未完全初始化);三级缓存(singletonFactories)存储ObjectFactory。 结构清晰地回答: 1. **介绍Spring三级缓存**:解释什么是三级缓存及其在Spring框架中的角色。 2. **为什么需要第三级缓存**:详细说明解决的问题,尤其是循环依赖和AOP。 3. **第三级缓存的工作机制**:描述缓存如何被访问和使用,基于引用的实现细节。 4. **示例**:提供一个简单的代码示例来说明循环依赖如何处理。 5. **注意事项**:讨论潜在问题和优化点。 使用中文回答,如系统指令要求。 生成相关问题:在回答末尾添加相关问题,基于主题添加。 引用标识:在引用段落末尾自然地添加引用标识,例如[^1]。因此,在回答中,当我引用特定部分时,添加引用标识。 回答结构: - 开头:总结三级缓存。 - 主体:分点详细解析。 - 结尾:总结并添加相关问题。 确保LaTeX语法正确:如果有数学表达式,使用$...$或$$...$$,但在这个主题中可能没有数学表达式,所以可能不需要。如果有变量的定义,使用$...$。 现在,构建回答大纲: 1. **引言**:简述Spring三级缓存概念。 2. **三级缓存的定义和作用**:基于引用[1]和[3],解释三级缓存解决了循环依赖问题和支持AOP。 3. **工作机制**:描述具体如何工作,参考引用[2]的实现流程。 - 例如:在创建Bean时,getSingleton()方法检查缓存。 - 三级缓存存储ObjectFactory,当需要Bean时,调用它来获取早期引用。 4. **与AOP的集成**:解释三级缓存如何支持AOP代理,引用[3]和[5]。 5. **示例场景**:使用引用[3]的ServiceA和ServiceB示例。 6. **重要性**:为什么不能只用二级缓存,引用[4]和[5]。 7. **相关问题**:生成几个相关问题。 最后,添加相关问题: - 例如: - Spring如何防止循环依赖导致的内存泄漏? - 如果三级缓存被移除,Spring会有什么问题? - 如何自定义Spring缓存机制? </think>### Spring三级缓存详细解析 Spring框架中的三级缓存是其Bean创建机制的核心部分,主要用于解决循环依赖问题和支持AOP代理。三级缓存(Singleton Factories Cache)存储的是`ObjectFactory`对象,这些对象用于生成Bean的早期引用(尚未完全初始化的实例)。下面我将基于站内引用资料,逐步解析第三级缓存的详细信息。 #### 1. **三级缓存的定义与角色** 在Spring的容器管理中,三级缓存是三个缓存层级的最后一级,位于`DefaultSingletonBeanRegistry`类中: - **第一级缓存(singletonObjects)**:存储完全初始化好的单例Bean,可直接使用。 - **第二级缓存(earlySingletonObjects)**:存储已实例化但未完成属性注入和初始化的Bean(早期引用)。 - **第三级缓存(singletonFactories)**:存储`ObjectFactory`对象,这些工厂对象能创建Bean的早期引用。当Spring检测到循环依赖时,会从这里获取一个代理或原始实例,以避免无限递归[^1][^3]。 第三级缓存的键是Bean名称(如`serviceA`),值是一个`ObjectFactory`函数。例如: ```java // 伪代码表示三级缓存的操作 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean)); ``` 当其他Bean依赖该Bean时,Spring会调用这个工厂对象获取一个部分初始化的引用,从而打破循环依赖链[^2][^5]。 #### 2. **为什么需要第三级缓存?** 第三级缓存主要解决两个核心问题: - **循环依赖问题**:当Bean A依赖Bean B,而Bean B又依赖Bean A时,Spring通过三级缓存提供早期引用。例如,在Bean A实例化后,Spring将其`ObjectFactory`存入三级缓存;当Bean B创建时,通过工厂获取A的早期引用注入B,完成后再回填A的属性。这样避免了死锁或栈溢出[^1][^3]。 - **支持AOP代理**:Spring的AOP代理通常在Bean初始化后生成(通过`postProcessAfterInitialization`)。但如果Bean在循环依赖中需要被代理,三级缓存的`ObjectFactory`可以提前生成代理对象。直接从三级缓存获取代理实例,确保注入的是代理而非原始对象,保持AOP功能一致[^3][^5]。 如果没有三级缓存: - 只用二级缓存会导致AOP代理无法正确创建,因为代理应在初始化后生成,但循环依赖强制提前实例化。 - 三级缓存确保代理对象只在必要时创建,符合Spring的生命周期设计原则[^5]。 #### 3. **三级缓存的工作机制** 三级缓存的运作紧密集成在Bean创建流程中,基于`getSingleton()`方法实现。以下是简化步骤(参考Spring源码流程): 1. **检查缓存**:当调用`getBean("beanA")`时,Spring先检查一级缓存(完全初始化Bean)。若不存在,检查二级缓存(早期引用)。若仍不存在,则检查三级缓存。 2. **创建ObjectFactory**:如果三级缓存中存在`ObjectFactory`,调用其`getObject()`方法获取Bean早期引用(可能是原始对象或代理)。例如: ```java ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { earlySingletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, earlySingletonObject); // 移入二级缓存 this.singletonFactories.remove(beanName); // 清除三级缓存条目 } ``` 3. **处理循环依赖**:在`doCreateBean()`方法中,Bean实例化后(通过反射创建空实例)立即将`ObjectFactory`存入三级缓存。后续依赖注入时,可直接从此获取引用。 4. **完成初始化**:一旦依赖解决,Bean进行属性注入和初始化,最终移入一级缓存[^2][^4]。 例如,在循环依赖场景: - **ServiceA依赖ServiceB**:当`ServiceA`实例化后,其`ObjectFactory`存入三级缓存。创建`ServiceB`时,从三级缓存获取A的早期引用注入B。 - **ServiceB依赖ServiceA**:B完成后,回填A的属性,A完成初始化[^3]。 #### 4. **三级缓存的优势与限制** - **优势**: - **解决复杂依赖**:支持多个对象的循环依赖(如A→B→C→A),通过`ObjectFactory`动态生成引用,避免重复创建[^4]。 - **性能优化**:减少不必要的Bean实例化,尤其在高并发场景下提升效率[^3]。 - **AOP兼容性**:确保代理对象在循环依赖中正确注入,避免AOP失效[^5]。 - **限制**: - **仅适用于单例Bean**:原型作用域(Prototype)Bean不支持三级缓存,无法解决循环依赖。 - **潜在问题**:如果`ObjectFactory`生成的对象不一致(如涉及动态代理),可能引发错误。解决方法是确保工厂逻辑幂等或使用二级缓存辅助[^4]。 #### 5. **示例代码说明** 以下是一个简单的循环依赖示例,展示三级缓存如何工作: ```java @Service public class ServiceA { @Autowired private ServiceB serviceB; // 依赖ServiceB } @Service public class ServiceB { @Autowired private ServiceA serviceA; // 依赖ServiceA } ``` - **步骤**: - Spring实例化`ServiceA`,将`ObjectFactory`存入三级缓存。 - 当注入`ServiceB`时,Spring三级缓存获取A的早期引用。 - 完成`ServiceB`创建后,回填`ServiceA`的属性,最终A和B均初始化完成[^3]。 #### 总结 Spring的第三级缓存是一个关键机制,通过存储`ObjectFactory`对象,高效解决循环依赖和AOP代理问题。它确保Bean在部分状态下可被引用,同时维护生命周期一致性。理解三级缓存有助于优化Spring应用设计和调试依赖问题[^1][^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值