Spring的循环依赖问题
文章目录
- 一、什么是循环依赖?
- 二、Spring 是如何解决循环依赖的?
- 1.三级缓存
- 2.解决循环依赖的流程
- 三、三级缓存机制可以解决所有的循环依赖问题吗?
- 1. 为什么三级缓存在这里无效?
- 2. 如何解决构造器循环依赖?
- 四、循环依赖总结
一、什么是循环依赖?
是指两个或多个Bean互相依赖,导致Spring在实例化它们时形成死循环。
@Component
public class A {@Autowiredprivate B b;
}@Component
public class B {@Autowiredprivate A a;
}
当Spring容器尝试创建A的实例时,发现需要B,则要去创建B;而创建B又需要A,最终就会导致依赖注入死循环。
二、Spring 是如何解决循环依赖的?
1.三级缓存
Spring 容器采用了 三级缓存 来解决单例 Bean 的循环依赖问题。
缓存级别 | 作用 |
---|---|
singletonObjects | 一级缓存,存放完全初始化完成的单例 Bean |
earlySingletonObjects | 二级缓存,存放实例化但未初始化完成的早期 Bean |
singletonFactories | 三级缓存,存放可以创建早期 Bean 的 ObjectFactory |
2.解决循环依赖的流程
以BeanA中依赖BeanB,BeanB中又依赖BeanA为例。
1. 创建BeanA
Spirng通过构造方法实例化BeanA得到一个原始对象,将BeanA的ObjectFactory放入三级缓存中,其中ObjectFactory封装了BeanA的早期引用,包括AOP代理。
2. 尝试注入BeanB
Spring为BeanA填充属性,发现需要BeanB,Spring会去检查缓存:
① 如果一级缓存中已经有初始化好的BeanB,就直接返回,反之进入下一步。
② 如果二级缓存中已经有BeanB的早期引用,则把它注入给BeanA,反之进入下一步。
③如果三级缓存中也没有BeanB的ObjectFactory,说明BeanB尚未创建,进入实例化流程。
3. 创建BeanB
当发现缓存中不存在BeanB,则会去实例化BeanB,将其ObjectFactory放入三级缓存。
在创建BeanB的过程中,Spring发现依赖BeanA,那么又会重复第二步去缓存里面寻找BeanA。
因为之前三级缓存中已经存放了BeanA的ObjectFactory,那么可以调用ObjectFactory.getObject()方法生成BeanA的早期引用(也可以是代理对象),将其放入二级缓存并删除三级缓存中BeanA的ObjectFactory。
4. 继续BeanB的创建
使用二级缓存中BeanA的早期引用完成BeanB的属性填充和初始化,将BeanB放入一级缓存。
5. 回到BeanA
从一级缓存中获取BeanB注入到BeanA中完成BeanA的初始化,将BeanA放入一级缓存并删除二级缓存中BeanA的早期引用。
三、三级缓存机制可以解决所有的循环依赖问题吗?
答案:不能
虽然三级缓存机制可以解决大多数字段注入或setter注入场景下的循环依赖,但对于构造器注入导致的循环依赖,Spring 是无能为力的。
构造器循环依赖示例:
@Component
public class A {private final B b;public A(B b) { this.b = b; }
}@Component
public class B {private final A a;public B(A a) { this.a = a; }
}
由于 A 和 B 都必须在构造时就传入彼此,Spring 根本无法先创建一个“半成品”放到缓存中 —— 因为构造阶段前就要依赖对方,因此会抛出异常。
1. 为什么三级缓存在这里无效?
构造器注入发生在Bean实例化的最开始阶段,此时还没有机会将ObjectFactory放入三级缓存,也就无法提前暴露 “半成品 Bean” 来打破循环。换句话说,Spring甚至连放进三级缓存的机会都没有,自然就无法利用缓存解决依赖问题。
2. 如何解决构造器循环依赖?
由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用@Lazy懒加载注解,延迟bean的创建直到实际需要时。
@Component
public class A {private final B b;public A(@Lazy B b) { this.b = b; }
}
这样Spring会为B创建一个代理对象,在真正调用到它时才进行实例化,从而规避构造器阶段的死循环问题。
四、循环依赖总结
- Spring 中的循环依赖问题是 Bean 生命周期管理中一个经典而复杂的问题。对于 字段注入或 setter 注入 的循环依赖,Spring 通过 三级缓存机制(singletonObjects、earlySingletonObjects、singletonFactories)巧妙地解决了大多数场景。
- 但对于构造器注入造成的循环依赖,由于在实例化阶段就必须提供依赖对象,Spring 根本无法提前将“半成品”放入缓存,因此无法使用三级缓存解决,最终会导致报错。
- 为了解决构造器循环依赖,可以使用 @Lazy 注解延迟依赖的加载。Spring 会注入一个代理对象,在真正使用时再初始化该依赖,从而绕开构造阶段的死循环问题。