spring中是如何解决循环依赖的问题
Spring 通过三级缓存机制有效解决了循环依赖问题,尤其是在单例作用域(@Scope("singleton"))下。以下是 Spring 解决循环依赖的详细过程,包括各个步骤的深入解析。
1. 什么是循环依赖
循环依赖(Circular Dependency)指的是多个对象之间的相互依赖,例如:
- Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A,这样形成了一个循环。
在构造器注入的情况下,Spring 无法解决这个问题,因为所有依赖必须在构造器执行之前就已经准备好。然而,在属性注入(setter注入)和字段注入(field注入)的场景中,Spring 可以通过三级缓存机制打破循环依赖。
2. Spring 解决循环依赖的关键机制:三级缓存
Spring 采用了三级缓存(3-level cache)来解决循环依赖问题。这三级缓存分别为:
-
一级缓存(
singletonObjects):- 存储的是完全初始化好的单例对象。这些对象已经过了所有的生命周期(实例化、依赖注入、初始化)并且是最终可以使用的对象。
-
二级缓存(
earlySingletonObjects):- 存储的是早期暴露的单例对象,这些对象已经实例化并且完成了部分注入,但尚未执行初始化方法。二级缓存是为了避免重复暴露早期对象(比如在循环依赖中,多个 bean 同时依赖)。
-
三级缓存(
singletonFactories):- 存储的是对象工厂(
ObjectFactory),用于延迟创建早期单例对象。只有在二级缓存没有合适的对象时,Spring 才会通过这个工厂生成早期引用。三级缓存的设计尤其重要,它允许代理对象的处理(比如 AOP 代理)并解决特定场景下的复杂依赖问题。
- 存储的是对象工厂(
3. 解决循环依赖的具体流程
3.1 正常的单例 Bean 创建过程
Spring 中创建单例 bean 的过程可以分为以下几个步骤:
-
检查一级缓存:Spring 首先检查一级缓存(
singletonObjects)中是否有已经创建好的 bean。如果有,直接使用。如果没有,进入下一步。 -
提前暴露对象工厂到三级缓存:在创建 bean 之前,Spring 会将一个对象工厂(
ObjectFactory)放入三级缓存(singletonFactories)中。这个工厂的作用是允许在 bean 尚未完全初始化时,将其引用暴露出来,以备后续使用。 -
实例化 bean:Spring 创建 bean 的原始实例(没有进行依赖注入或初始化)。
-
依赖注入:Spring 尝试为这个 bean 注入所需的依赖。
- 如果依赖的 bean 已经存在于一级缓存,直接使用。
- 如果依赖的 bean 不存在于一级缓存,而依赖的 bean 正在创建中(涉及到循环依赖),Spring 会从三级缓存中获取该 bean 的早期引用,避免陷入死循环。
-
初始化 bean:注入依赖后,Spring 会执行初始化步骤,包括调用
@PostConstruct或InitializingBean中的afterPropertiesSet()方法。 -
将完全初始化的 bean 放入一级缓存:初始化完成后,bean 被放入一级缓存,并从三级和二级缓存中删除。
3.2 处理循环依赖的过程
在处理循环依赖时,三级缓存起到了关键作用。以两个互相依赖的 bean A 和 B 为例,假设 A 依赖 B,B 也依赖 A:
-
创建 A:
- Spring 开始创建 A,首先将 A 的对象工厂放入三级缓存。
- 然后实例化 A,接着发现 A 依赖于 B。
-
创建 B:
- 为了完成 A 的依赖注入,Spring 开始创建 B。同样,B 的对象工厂也会放入三级缓存。
- 然后实例化 B,接着发现 B 依赖于 A。
-
解决 B 中对 A 的依赖:
- 此时 A 尚未完全初始化,但 Spring 可以从三级缓存中找到 A 的对象工厂,并通过工厂生成 A 的早期引用(可能是一个未完全初始化的 A 或者是 A 的代理对象)。
- 这个早期引用会被注入到 B 中,完成 B 对 A 的依赖。
-
完成 B 的初始化:
- 依赖注入完成后,Spring 会继续初始化 B,完成其所有生命周期方法。
-
完成 A 的依赖注入和初始化:
- B 完成初始化后,Spring 回到 A 的创建流程,注入 B 的实例。
- 最后,完成 A 的初始化,A 和 B 都成功创建,循环依赖得以解决。
4. 为什么需要三级缓存
三级缓存的设计可以解决以下问题:
-
避免重复创建对象:在二级缓存中,Spring 存储的是早期的 bean 对象,避免了在循环依赖情况下多次创建同一个对象。
-
处理代理对象:某些 bean 在创建过程中可能会被代理(如 AOP 场景)。通过三级缓存存储对象工厂,Spring 可以根据需要提前创建代理对象,确保 bean 的依赖注入可以顺利完成。
5. 不能解决的场景
虽然三级缓存能够解决大多数情况下的循环依赖问题,但有以下两种情况是无法解决的:
-
构造器注入的循环依赖:
- 如果 A 和 B 是通过构造器注入互相依赖的,Spring 无法通过三级缓存解决,因为在构造器执行之前依赖必须已经准备好。这种情况会导致
BeanCurrentlyInCreationException异常。
- 如果 A 和 B 是通过构造器注入互相依赖的,Spring 无法通过三级缓存解决,因为在构造器执行之前依赖必须已经准备好。这种情况会导致
-
@Scope("prototype")的循环依赖:- 原型作用域的 bean 不会缓存,因此 Spring 无法处理它们的循环依赖。每次请求原型 bean 时,都会创建一个新的实例,并且没有提前暴露的对象可以用于解决依赖。
6. 总结
Spring 使用三级缓存机制(singletonObjects、earlySingletonObjects、singletonFactories)来解决循环依赖问题。在 bean 创建的早期阶段,Spring 会将尚未完全初始化的对象暴露出来,以便其他依赖于它的对象能够使用。这一机制特别适用于属性注入和字段注入场景,但对构造器注入和原型作用域的 bean 则无能为力。
通过这一机制,Spring 能够打破循环依赖,确保应用程序在复杂依赖关系下正常运行。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)