type
status
date
slug
summary
tags
category
icon
password
在Spring框架中,循环依赖(或称循环注入)是指两个或多个Bean之间存在相互依赖的情况。具体而言,Bean A依赖Bean B,而Bean B又依赖Bean A。这种情况如果不处理得当,会导致应用程序在启动时出现问题,通常表现为
BeanCurrentlyInCreationException
。Spring有一套机制来处理这种循环依赖,尤其是在单例Bean的情况下。1. 循环依赖的类型
在探讨Spring如何处理循环依赖之前,首先需要了解循环依赖的不同型。主要可以分为以下几类:
1.1 构造器注入中的循环依赖
构造器注入中的循环依赖是指两个或多个Bean通过构造器彼此依赖。这种情况下,Spring是无法处理循环依赖的,因为在创建第一个Bean时,需要其依赖的Bean已经完全初始化,但由于构造器注入的性质,无法在Bean创建前将其注入。
例如:
在上述代码中,A依赖于B,而B也依赖于A,这种情况会导致构造器循环依赖,Spring会抛出异常。
1.2 Setter注入中的循环依赖
Setter注入中的循环依赖是指通过Setter方法或字段注入的方式存在的循环依赖。这种类型的循环依赖,Spring通过特定的机制是可以处理的。
例如:
在这种情况下,Spring可以成功处理循环依赖。
2. Spring 如何处理循环依赖
为了避免循环依赖导致的问题,Spring框架在管理Bean的生命周期时,设计了一套复杂的机制来处理Setter注入中的循环依赖。Spring主要通过以下几个步骤来实现这一功能:
2.1 单例模式下的三级缓存
Spring的单例Bean管理采用三级缓存机制,以解决循环依赖问题。具体而言,这三个缓存分别是:
- 一级缓存(Singleton Cache):用于存放完全初始化好的单例Bean。这个缓存是一个
ConcurrentHashMap
,存储的是已经完全初始化并且可以使用的Bean。
- 二级缓存(Early Singleton Cache):用于存放部分初始化的单例Bean。这个缓存存储的Bean已经被实例化,但还没有被完全初始化(即未完成依赖注入等后处理操作)。
- 三级缓存(Singleton Factory Cache):用于存放Bean工厂对象(
ObjectFactory
),其通过回调机制来创建Bean实例。这是一个Map
,存储的是Bean工厂,用于在循环依赖情况下提前暴露Bean引用。
2.2 Bean的创建流程
当Spring在创建一个Bean时,通常会按照以下流程处理:
- 实例化Bean:首先,Spring通过构造器或工厂方法创建一个Bean的实例。这一步并不会处理Bean的依赖注入。
- 添加到三级缓存:在实例化Bean后,Spring会将这个实例的工厂(
ObjectFactory
)添加到三级缓存中。这意味着Bean虽然还未完全初始化,但已经有了一个创建工厂,可以提供其引用。
- 依赖注入:Spring会尝试为Bean注入其依赖的其他Bean。如果依赖的Bean尚未完全创建,Spring会在此时检查二级缓存和三级缓存,以找到提前暴露的Bean引用。
- 从三级缓存提升至二级缓存:如果某个Bean的引用被另一个Bean需要,Spring会将其从三级缓存提升到二级缓存,并通过
ObjectFactory
创建出Bean实例,供依赖注入使用。
- 完成初始化:当所有依赖注入完成后,Bean将从二级缓存移至一级缓存,表示该Bean已经完全初始化,可以正常使用。
通过这种三级缓存机制,Spring可以在不完全初始化一个Bean的情况下,将其引用提前暴露,从而解决Setter注入中的循环依赖问题。
2.3 一个具体的循环依赖示例
假设我们有两个Bean:
A
和 B
,它们通过Setter方法相互依赖:当Spring容器初始化时,它会按照以下顺序处理:
- Spring尝试创建Bean
A
。此时,A
被实例化,并且它的工厂被放入三级缓存。
- 在为
A
注入依赖时,Spring发现A
依赖于B
,但B
还没有被创建。因此,Spring开始创建B
。
B
被实例化,并且它的工厂也被放入三级缓存。
- 在为
B
注入依赖时,Spring发现B
依赖于A
。此时,A
已经在三级缓存中,因此Spring会通过A
的工厂获取A
的引用,并将这个引用注入到B
中。
- 之后,
B
完成初始化,并从三级缓存提升至二级缓存,最后进入一级缓存。
- 接着,Spring返回去完成
A
的依赖注入,现在B
已经完全初始化,可以安全地注入到A
中。
- 最终,
A
也完成初始化,进入一级缓存。
3. 构造器注入中的循环依赖问题
与Setter注入不同,构造器注入中的循环依赖不能通过三级缓存机制来解决。因为在构造器注入的情况下,Bean必须在构造器中被完全创建,而此时无法提前暴露Bean的引用。对于构造器注入的循环依赖,Spring会抛出
BeanCurrentlyInCreationException
,开发者需要手动解决这种依赖问题。解决构造器注入循环依赖的一些常见方法包括:
- 重构代码:尝试消除循环依赖,通过调整Bean的职责或依赖关系来避免循环依赖。
- 使用Setter注入:将构造器注入转换为Setter注入,使Spring可以利用其三级缓存机制来处理循环依赖。
- 使用
@Lazy
注解:通过懒加载(Lazy Initialization)来推迟Bean的创建,从而打破循环依赖。
4. 总结
循环依赖在复杂的Spring应用程序中是常见的问题,尤其是在依赖关系错综复杂的情况下。Spring通过单例Bean的三级缓存机制解决了Setter注入中的循环依赖问题,使得开发者可以在不必显式管理Bean的生命周期的情况下,处理大多数的循环依赖问题。然而,对于构造器注入中的循环依赖,开发者仍需要特别注意并进行适当的代码重构或使用其他技术手段来避免问题的发生。
理解Spring循环依赖的原理,不仅有助于编写更加健壮的代码,还能帮助开发者在面对复杂的依赖关系时,做出合理的设计决策,确保应用程序的稳定性和可维护性。
- 作者:奥利弗
- 链接:https://www.aolifu.org/article/spring_circular_dependency
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章