在Spring框架中,循环依赖是一个常见的问题,它指的是两个或多个Bean之间互相依赖,形成一个闭环,导致无法准确地完成对象的创建和初始化。为了解决这个问题,Spring引入了三级缓存机制。以下是对Spring中循环依赖和三级缓存的详细解释:
一、循环依赖的定义与场景
循环依赖通常发生在以下场景:
- 直接循环依赖:两个Bean互相依赖,例如Bean A依赖于Bean B,同时Bean B也依赖于Bean A。
- 间接循环依赖:通过多个Bean形成一个依赖链,最终回到起点,形成一个闭环。
二、三级缓存的定义与作用
Spring的三级缓存主要由三个Map数据结构组成,分别用于存储不同状态的Bean:
- 一级缓存(singletonObjects):存储已经完全初始化和实例化的Bean对象。这个缓存的目的是确保Bean只初始化一次(是单例的),避免多次实例化相同的Bean对象,提高性能。
- 二级缓存(earlySingletonObjects):存储尚未完成属性注入和初始化的“半成品”Bean对象。当Spring容器发现两个或多个Bean之间存在循环依赖时,会将这些未完全初始化的对象提前暴露在二级缓存中,以便其他Bean进行引用,确保它们之间的依赖能够被满足。
- 三级缓存(singletonFactories):存储ObjectFactory类型的代理工厂对象。主要用于处理存在AOP(面向切面编程)时的循环依赖问题。每个Bean都对应一个ObjectFactory对象,通过调用该对象的getObject方法,可以获取到早期暴露出去的Bean。
在 DefaultSingletonBeanRegistry中定义的这三个map
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {Assert.notNull(beanName, "Bean name must not be null");Assert.notNull(singletonObject, "Singleton object must not be null");synchronized(this.singletonObjects) {Object oldObject = this.singletonObjects.get(beanName);if (oldObject != null) {throw new IllegalStateException("Could not register object [" + singletonObject + "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");} else {this.addSingleton(beanName, singletonObject);}}}protected void addSingleton(String beanName, Object singletonObject) {synchronized(this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized(this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}}@Nullablepublic Object getSingleton(String beanName) {return this.getSingleton(beanName, true);}@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized(this.singletonObjects) {singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}
三、循环依赖时的处理流程
当Spring发生循环依赖时,会按照以下流程进行处理:
- 遍历待创建的Bean:在容器启动时,Spring会遍历所有需要创建的Bean名称。
- Bean的创建与缓存:当Spring尝试获取一个Bean时,会首先在一级缓存中查找。如果找不到,则检查该Bean是否正在创建中。如果是,则会在二级或三级缓存中查找或创建该Bean的实例。
- 如果Bean是单例且尚未创建完成,会将其BeanFactory存入三级缓存。
- 处理依赖注入:在Bean的创建过程中,Spring会处理其依赖注入。如果依赖的Bean不存在于一级或二级缓存中,则会开始创建该依赖Bean的流程。
- 循环依赖的解决:当依赖的Bean也需要注入当前正在创建的Bean时,会从三级缓存中获取当前Bean的BeanFactory,并通过BeanFactory的getObject方法获取当前Bean的实例(此时该实例被存入二级缓存,并清除三级缓存中的相关记录)。这样,依赖的Bean就能成功注入当前Bean的属性。
- Bean的初始化与缓存更新:当Bean的所有依赖都被注入后,Spring会执行Bean的初始化操作。初始化完成后,该Bean会被存入一级缓存,并清空其在二级和三级缓存中的记录。
四、三级缓存解决循环依赖的示例
假设有两个Bean,A和B,它们之间存在循环依赖:
- Bean A依赖于Bean B的属性。
- Bean B依赖于Bean A的属性。
在没有三级缓存的情况下,Spring容器在初始化A和B时会陷入无限循环。但有了三级缓存后,处理流程如下:
- Spring开始创建Bean A,发现A依赖于B,于是开始创建B。
- 在创建B的过程中,发现B依赖于A。此时,由于A正在创建中,Spring会从三级缓存中获取A的BeanFactory,并通过它创建A的早期暴露实例(存入二级缓存)。
- B成功注入A的属性后,完成初始化并存入一级缓存。
- 回到A的创建流程,A此时可以成功注入B的属性,完成初始化并存入一级缓存。
通过这个过程,Spring成功解决了A和B之间的循环依赖问题。
五、注意事项与限制
- 单例Bean:Spring解决循环依赖的前提是互相依赖的Bean必须是单例的。对于原型范围的Bean(prototype scope),每次请求都会创建一个新的Bean实例,因此无法解决循环依赖问题。
- 依赖注入方式:当使用构造函数注入时,如果两个Bean互相依赖对方的构造函数参数,则会产生死锁情况。因此,循环依赖通常发生在Setter注入或字段注入中。