我可以不吃啊 发表于 2025-2-18 13:29:45

Spring 是如何办理循环依赖问题的?

Spring办理循环依赖的核心机制:三级缓存

Spring通过三级缓存(三级Map)提前袒露未完全初始化的Bean,办理循环依赖问题。核心头脑是“提前袒露引用”,允许Bean在构造完成后但未初始化前被其他Bean引用。
三级缓存的作用


[*] singletonObjects(一级缓存)
存放完全初始化好的单例Bean,可以直接利用。
[*] earlySingletonObjects(二级缓存)
存放早期袒露的Bean(已实例化但未初始化),用于办理循环依赖。
[*] singletonFactories(三级缓存)
存放Bean的工厂对象(ObjectFactory),用于生成Bean的早期引用(大概包罗代理对象)。
办理循环依赖的流程(以A→B→A为例)


[*] 创建Bean A

[*] 实例化A(调用构造器),此时A对象未初始化(属性为空)。
[*] 将A的工厂对象放入三级缓存(addSingletonFactory)。
[*] 开始填充A的属性,发现依赖B,触发B的创建。

[*] 创建Bean B

[*] 实例化B,同样将B的工厂对象放入三级缓存。
[*] 填充B的属性时,发现依赖A,触发从缓存获取A的流程:

[*] 从一级缓存查找A → 不存在。
[*] 从二级缓存查找A → 不存在。
[*] 从三级缓存获取A的工厂对象,生成A的早期引用 → 成功获取,将A的引用存入二级缓存,并移除三级缓存中的工厂。

[*] B完成属性填充和初始化,放入一级缓存。

[*] 继续完成A的初始化

[*] B创建完成后,A的属性B被注入。
[*] A完成初始化,从二级缓存移除,并存入一级缓存。

关键源码实现



[*] DefaultSingletonBeanRegistry类:界说了三级缓存的Map结构。
[*] getSingleton()方法:按三级→二级→一级的序次查找Bean。
[*] addSingletonFactory()方法:将Bean工厂参加三级缓存。
[*] getEarlyBeanReference()方法:通过工厂生成早期引用(处置惩罚AOP代理)。
限定条件


[*] 必须是单例(Singleton)
原型(Prototype)作用域的Bean无法办理循环依赖。
[*] 不能是构造器注入
若依赖通过构造器注入,Bean未实例化完成前无法袒露引用,Spring会抛出BeanCurrentlyInCreationException。
[*] 需要启用字段/Setter注入
通过属性注入时,Spring有时机在实例化后干预依赖注入。
为何需要三级缓存(而不是两级)?



[*] 处置惩罚AOP代理:三级缓存中的ObjectFactory可以返回代理对象(如果需要AOP),确保依赖注入的是终极代理对象,而非原始对象。
[*] 性能优化:制止重复创建代理对象,包管单例的唯一性。
面试回复示例

“Spring通过三级缓存办理循环依赖问题。当Bean实例化后、初始化前,Spring会将其工厂对象存入三级缓存。若其他Bean在填充属性时发现循环依赖,会通过这个工厂提前获取Bean的早期引用。需要留意的是,这种机制仅适用于单例且通过Setter或字段注入的Bean,构造器注入的循环依赖无法办理。底层通过DefaultSingletonBeanRegistry类中的三个Map实现,结合Bean的生命周期方法确保依赖的精确注入。”
Spring 中设计了三级缓存来办理
循环依赖问题,当我们去调用 getBean()方法的时候,Spring 会 先从一级缓存中去找到目的 Bean,如果发现一级缓存中没有便会去二级缓存中去找,而如果一、二级 缓存中都没有找到,意味着该目的 Bean 还没有实例化。于是,Spring 容器会实例化目的 Bean(PS: 刚初始化的 Bean 称为早期 Bean) 。然后,将目的 Bean 放入二级缓存中,同时,加上标记是否存在 循环依赖。如果不存在循环依赖便会将目的 Bean 存入到二级缓存,否则,便会标记该 Bean 存在循环 依赖,然后将等待下一次轮询赋值,也就是解析@Autowired 注解。等@Autowired 注解赋值完成后 (PS:完成赋值的 Bean 称为成熟 Bean) ,会将目的 Bean 存入到一级缓存。 这里我可以做个总结,我们来看这张图 Spring 一级缓存中存放所有的成熟 Bean, 二级缓存中存放所有的早期 Bean,先取一级缓存,再去二级缓存。
面试官:那么,前面有提到三级缓存,三级缓存的作用是什么? 三级缓存是用来存储代理 Bean,当调用 getBean()方法时,发现目的 Bean 需要通过代理工厂来创建, 此时会将创建好的实例生存到三级缓存,终极也会将赋值好的 Bean 同步到一级缓存中。 面试官:Spring 中哪些情况下,不能办理循环依赖问题? 1.多例 Bean 通过 setter 注入的情况,不能办理循环依赖问题 2.构造器注入的 Bean 的情况,不能办理循环依赖问题 3.单例的代理 Bean 通过 Setter 注入的情况,不能办理循环依赖问题 4.设置了@DependsOn 的 Bean 的情况,不能办理循环依赖问题




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Spring 是如何办理循环依赖问题的?