Spring三级缓存如何解决循环依赖的?

Spring三级缓存解决循环依赖原理

在Spring中,一个对象并不是简单 new出来了,而是会经过一系列的Bean的生命周期。

1、Bean的生命周期

被Spring管理的对象叫做Bean。Bean的生命周期指的就是:在Spring中,Bean是如何生成的?

Bean的生成步骤如下:

  1. Spring扫描class得到BeanDefinition
  2. 根据得到的BeanDefinition去生成bean
  3. 首先根据class推断构造方法
  4. 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
  5. 填充原始对象中的属性(依赖注入)
  6. 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
  7. 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接 从单例池拿即可

2、什么是循环依赖?

很简单,就是A对象依赖了B对象,B对象依赖了A对象
比如:

// A依赖了B 
class A{ 
public B b;
 }
// B依赖了A
 class B{ 
public A a;
 }

那么在创建B类的Bean的过程中,如果B类中存在一个A类的a属性,那么在创建B的Bean的过程中就 需要A类对应的Bean,但是,触发B类Bean的创建的条件是A类Bean在创建过程中的依赖注入,所以这里就出现了循环依赖:

ABean创建–>依赖了B属性–>触发BBean创建—>B依赖了A属性—>需要ABean(但ABean还在 创建过程中)

从而导致ABean创建不出来,BBean也创建不出来

3、Spring通过使用三级缓存机制来解决循环依赖问题

  • 一级缓存(singletonObjects):已完全初始化的单例bean缓存。
  • 二级缓存(earlySingletonObjects):提前暴露的bean缓存,保存早期暴露但未完全初始化的单例bean。
  • 三级缓存(singletonFactories):保存可以创建早期单例bean的工厂。

如果某个bean出现了循环依赖, 就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入 earlySingletonObjects,为了给其他对象使用。

4、Spring处理循环依赖的简要过程

  • 当一个bean被创建时,首先会检查一级缓存(singletonObjects)中是否已有该bean。
  • 如果一级缓存中没有,则会检查二级缓存(earlySingletonObjects)。
  • 如果二级缓存中也没有,则会检查三级缓存(singletonFactories)。
  • 如果三级缓存中存在bean的创建工厂,则通过工厂创建该bean的早期引用,并将其放入二级缓存中。
  • 然后,Spring会继续完成bean的初始化,并在初始化完成后,将其放入一级缓存,并从二级和三级缓存中移除。

通过这种方式,Spring能够在bean尚未完全初始化时提供其早期引用,从而解决循环依赖问题。

5、注意事项

尽管Spring能够通过三级缓存机制解决大多数循环依赖问题,但对于构造器注入的情况,Spring无法解决。这是因为构造器注入要求在bean完全创建之前就要解决依赖,而这时三级缓存机制尚未介入,连普通对象都没办法获取到。因此,避免在构造器注入中形成循环依赖非常重要。

可以通过以下方式避免构造器注入的循环依赖:

  • 使用setter注入或字段注入代替构造器注入。
  • 重新设计bean之间的依赖关系,减少或消除循环依赖。
  • 使用@Lazy注解延迟加载bean。

@Lazy注解会直接生成代理对象,使用时其实还是使用的真正bean对象

Spring容器的启动流程可参看Spring容器的启动流程详解

<think>嗯,用户想了解Spring框架如何通过三级缓存解决Bean之间的循环依赖问题,并且希望有图解说明。我需要先回忆一下Spring三级缓存机制。Spring在创建Bean的时候,如果遇到循环依赖,比如A依赖B,B又依赖A,这时候需要机制来避免无限循环。三级缓存分别是singletonFactories、earlySingletonObjects和singletonObjects。 首先,应该解释三个缓存的作用。singletonFactories存放的是生成早期Bean的工厂,earlySingletonObjects存放的是还未完全初始化的Bean实例,singletonObjects存放的是完全初始化好的Bean。当创建A的时候,A的工厂会被放入singletonFactories,接着填充属性时发现需要B,于是开始创建B。B在填充属性时又需要A,这时候会从singletonFactories中找到A的工厂,生成早期对象放入earlySingletonObjects,并移除对应的工厂。这样B就能完成初始化,放入singletonObjects,然后A也能继续完成初始化。 用户可能对这三个缓存的具体使用流程不太清楚,需要一步步说明。同时,图解的话,可能需要用文字描述流程图,比如步骤1到步骤N,每个步骤涉及哪个缓存的变化。另外,需要提到Spring只能解决单例模式下通过setter注入的循环依赖,构造器注入无法解决,这点很重要,用户可能在实际开发中遇到类似问题需要注意。 还要注意引用部分,用户提到了引用[1]和引用[2],但引用内容似乎和Spring无关,可能是示例中的占位符。不过根据系统指令,可能需要引用相关文献,但用户提供的引用可能与主题无关,需要确认是否有其他来源。不过按照用户给的参考,可能不需要额外引用,或者需要指出Spring官方文档作为参考。 最后,生成相关问题的时候,应该围绕循环依赖的其他解决方式、三级缓存的局限性,以及与构造器注入的关系来提问,这样能帮助用户进一步理解。</think>### Spring框架三级缓存机制解决循环依赖的图解说明 Spring通过三级缓存机制解决单例Bean的循环依赖问题[^1],核心流程分为以下步骤: $$流程图$$ 创建A → 存入三级缓存 → 填充属性B → 创建B → 填充属性A → 从三级缓存获取A的半成品 → 完成B的初始化 → 完成A的初始化 $$ #### 三级缓存结构说明 | 缓存层级 | 数据结构 | 存储内容 | |---------|---------|---------| | 一级缓存 | singletonObjects | 完成初始化的完整Bean | | 二级缓存 | earlySingletonObjects | 已实例化但未初始化的早期引用 | | 三级缓存 | singletonFactories | ObjectFactory工厂对象 | #### 执行流程分解(以A→B→A循环依赖为例): 1. **创建Bean A** - 通过`getBean("A")`触发实例化 - 调用构造器创建A的原始对象(此时对象属性为空) - 将A的ObjectFactory存入三级缓存`singletonFactories` 2. **填充属性阶段** ```java // 伪代码示例 populateBean(){ if (需要注入B) { getBean("B") // 触发B的创建流程 } } ``` 3. **创建Bean B** - 通过`getBean("B")`触发实例化 - 调用构造器创建B的原始对象 - 将B的ObjectFactory存入三级缓存 4. **B填充属性A** - 执行`getBean("A")`时: - 在一级缓存未找到 - 在三级缓存找到A的ObjectFactory - 通过`getObject()`获取A的早期引用 - 将A早期引用移入二级缓存`earlySingletonObjects` - 从三级缓存删除A的工厂对象 5. **完成B的初始化** - B持有A的早期引用 - 将完整B对象存入一级缓存`singletonObjects` - 清除B在二、三级缓存的记录 6. **完成A的初始化** - A获得完整B对象的引用 - 执行初始化后处理器 - 将完整A对象存入一级缓存 #### 关键约束条件 1. 仅支持**setter/field注入**方式的循环依赖 2. 构造器注入无法通过此机制解决 3. 要求Bean必须是**单例作用域** 4. 不支持原型(prototype)作用域的Bean ```plantuml @startuml participant "一级缓存" as L1 participant "二级缓存" as L2 participant "三级缓存" as L3 participant "Bean A" as A participant "Bean B" as B A -> L3: 存入A的ObjectFactory A -> B: getBean("B") B -> L3: 存入B的ObjectFactory B -> L3: 查询A的ObjectFactory L3 --> B: 返回A的早期对象 B -> L2: 存入A的早期引用 B -> L1: 存入完整B对象 A -> L1: 获取完整B对象 A -> L1: 存入完整A对象 @enduml ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只IT攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值