Spring循环依赖与三级缓存详解

Spring 循环依赖与三级缓存详解

目标:把 “Spring 为啥能解决一部分循环依赖” 讲清楚;把 三级缓存(singletonObjects / earlySingletonObjects / singletonFactories) 的作用讲透;以及 什么时候一定解决不了


1. 什么是循环依赖

循环依赖(Circular Dependency)本质是:Bean A 的创建过程依赖 Bean B,而 Bean B 的创建过程又依赖 Bean A(直接或间接)

常见形式:

  • 直接循环:A → B → A
  • 间接循环:A → B → C → A

依赖发生的位置也很关键:

  • 构造器注入(constructor injection)
  • Setter/字段注入(property/field injection)

Spring 能解决的,主要是 单例 + Setter/字段注入 这类。


2. Spring 解决循环依赖的核心思路:先“露个面”,后“补齐”

Spring 创建 Bean 的过程可以粗略拆成两段:

  1. 实例化(Instantiation)new 出对象(此时属性还没注入)
  2. 属性填充(Populate Properties):注入依赖(A 需要 B)
  3. 初始化(Initialize):执行 Aware、BPP、init-method、@PostConstruct 等
  4. 注册成单例(Add to Singleton Cache)

循环依赖卡住的点一般在第 2 步:
A 还在创建中,A 注入 B → 创建 B → B 又要注入 A(但 A 还没创建完)。

解决办法
当 A “刚实例化完但还没初始化完” 时,Spring 先把 A 的一个“早期引用”暴露出去,让别人(比如 B)先拿到 A 的引用把依赖链打通,后面再把 A 的初始化补齐。

这就是三级缓存要做的事。


3. 三级缓存分别是什么(DefaultSingletonBeanRegistry)

Spring 单例相关的缓存(简化理解):

3.1 一级缓存:singletonObjects(成品池)

  • Map<String, Object> singletonObjects
  • 存的是 完整创建完成 的单例 bean(成品)。
  • 你平时从容器 getBean 拿到的,大多来自这里。

3.2 二级缓存:earlySingletonObjects(半成品引用池)

  • Map<String, Object> earlySingletonObjects
  • 存的是 早期暴露的 bean 引用(半成品引用)。
  • 只有在发生循环依赖时,才会被用到。

3.3 三级缓存:singletonFactories(工厂池)

  • Map<String, ObjectFactory<?>> singletonFactories
  • 存的是一个 ObjectFactory,可以在“确实需要”时,创建 bean 的 早期引用
  • 关键点:它不是直接存 bean,而是存 “如何生成早期引用 的方法”。

直觉类比:

  • 一级:成品仓库
  • 二级:临时借出去的半成品
  • 三级:半成品的“出库申请单/生成器”——只有别人真来借,才生成并借出

4. 为什么一定要三级?二级不够吗?

很多人卡在这里:如果只要“早期引用”,二级缓存不就够了?

三级缓存的存在,核心是为了支持 AOP 代理等“需要在早期就决定返回什么对象” 的场景:

  • 早期引用有可能不是原始对象,而是 代理对象
  • 是否需要代理,要看 BeanPostProcessor(例如 AOP 的 AutoProxyCreator)逻辑
  • Spring 不希望“所有 bean 都提前生成早期引用”,只有在循环依赖真正发生时才生成

因此使用三级缓存:

  1. A 刚实例化时,先往三级缓存塞一个 factory
  2. 只有当 B 真的来要 A 的引用(且 A 正在创建中)时,才调用 factory
  3. factory 内部会走 getEarlyBeanReference,给你一个“正确的早期引用”(可能是代理)

这能避免:

  • 过早创建代理导致不必要的开销
  • 代理创建时机不对,出现 双重代理 / 代理丢失 / 代理对象不一致 等坑

5. 经典流程拆解:A → B → A(Setter 注入,单例)

假设:

@Component
class A { @Autowired B b; }

@Component
class B { @Autowired A a; }

5.1 关键流程(简化版)

  1. 创建 A
    • 实例化 A:new A()
    • 把 A 的 ObjectFactory 放入三级缓存(此时 A 还没注入 B)
  2. A 需要注入 B → 创建 B
    • 实例化 B:new B()
    • 把 B 的 ObjectFactory 放入三级缓存
  3. B 需要注入 A → 再次获取 A
    • 发现 A “正在创建中”
    • 一级缓存没有 A(还没创建完)
    • 二级缓存也没有(还没暴露)
    • 去三级缓存拿到 A 的 factory,生成 A 的早期引用
    • 把这个早期引用放入二级缓存,并从三级缓存移除
    • 返回 A 的早期引用给 B 注入
  4. B 完成属性填充与初始化 → 放入一级缓存
  5. 回到 A,拿到 B,完成属性填充与初始化 → 放入一级缓存

5.2 三张缓存的状态变化(直观)

  • A 刚实例化:A 在 三级(factory)
  • B 请求 A:A 从 三级(factory) → 生成早期引用 → 放到 二级
  • A 初始化完成:A 从 二级(早期) → 升级到 一级(成品)

6. 关键源码位置(看懂这几个就够了)

不同版本方法细节可能有变化,但核心结构基本稳定。

6.1 提供早期引用的入口:getSingleton(beanName, allowEarlyReference)

典型逻辑(伪代码):

Object singleton = singletonObjects.get(name);
if (singleton == null && isCurrentlyInCreation(name)) {
    singleton = earlySingletonObjects.get(name);
    if (singleton == null && allowEarlyReference) {
        ObjectFactory<?> factory = singletonFactories.get(name);
        if (factory != null) {
            singleton = factory.getObject();          // 生成早期引用(可能是代理)
            earlySingletonObjects.put(name, singleton);
            singletonFactories.remove(name);
        }
    }
}
return singleton;

6.2 何时放入三级缓存:addSingletonFactory

在 bean 实例化之后、属性填充之前:

  • 实例化后:已经有了 raw bean
  • 属性注入前:正是循环依赖会发生的位置

6.3 早期引用如何可能变成代理:getEarlyBeanReference

AOP 场景下通常由 SmartInstantiationAwareBeanPostProcessor 参与,例如:

  • AbstractAutoProxyCreator#getEarlyBeanReference

它会判断该 bean 是否需要代理,如果需要就提前返回代理对象。


7. Spring 能解决哪些循环依赖?不能解决哪些?

7.1 ✅ 能解决(典型)

  • 单例(singleton)
  • Setter/字段注入
  • 依赖发生在“属性填充阶段”
  • AOP 也可以(靠 getEarlyBeanReference)

7.2 ❌ 不能解决(高频面试点)

  1. 构造器注入循环依赖
  • A 的构造器需要 B,B 的构造器需要 A
  • 还没实例化就需要对方,根本没有“先露个面”的机会
  • 三级缓存也救不了
  1. 原型(prototype)Bean 的循环依赖
  • prototype 不进单例缓存体系(每次都是 new)
  • 没有统一的缓存“早期暴露”机制
  1. 过早暴露导致的“代理/最终对象不一致”问题(复杂 AOP 链)
  • 虽然 Spring 做了很多兼容,但某些极端组合仍可能翻车
  • 常见表现:拿到的是 raw bean 而不是代理,导致事务/切面失效

8. Spring Boot 的默认行为(非常实用)

从 Spring Boot 2.6 开始,默认禁止 Bean 循环依赖(更鼓励你改代码结构),需要时可以手动打开。

application.yml:

spring:
  main:
    allow-circular-references: true

也可以通过 SpringApplication#setAllowCircularReferences(true) 设置。

现实建议:能不靠循环依赖就别靠。允许只是 “救急开关”,不是“架构特性”。


9. 如何“从根上”消除循环依赖(更像高级工程师的答案)

  1. 引入中间层/抽象
  • A 依赖接口 X,B 实现 X 或由第三方协调者实现
  1. 事件驱动 / 发布订阅
  • A 发布事件,B 监听事件,避免直接互相持有引用
  1. @Lazy 延迟注入
  • 注入一个延迟代理,打断初始化期的强依赖
    @Autowired @Lazy
    private A a;
    
  1. 把“互相调用”改成“单向调用 + 回调/策略”
  • 循环依赖本质经常是职责混乱,拆职责通常就顺便拆掉依赖环

10. 面试速记版

  • Spring 解决的是 单例 + setter/字段注入 的循环依赖
  • 核心是 提前暴露早期引用 来打断依赖环
  • 三级缓存
    • 一级:成品单例
    • 二级:早期引用(半成品)
    • 三级:ObjectFactory(按需生成早期引用,支持 AOP 提前代理)
  • 构造器循环依赖、prototype 循环依赖基本无解
  • Spring Boot 2.6+ 默认禁止循环依赖,可通过 spring.main.allow-circular-references=true 临时开启

<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]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值