
目录
Spring的三级缓存
引入
循环依赖
回顾Bean的生命周期
循环依赖的问题
Spring不支持的循环依赖
Spring解决循环依赖的机制
三级缓存详解
定义
Spring三级缓存源码
工作流程
为什么要使用三级缓存?
Spring的三级缓存
引入
在Spring中,Bean的默认作用域是Singleton,意味着整个应用中只有一个Bean实例。当Spring容器启动时,它会读取配置信息(如XML配置文件或注解),然后根据这些信息来创建Bean实例。在这个过程中,如果两个或多个Bean之间存在循环依赖(例如,Bean A依赖于Bean B,而Bean B又依赖于Bean A),则直接的创建顺序会导致问题。
Spring的三级缓存是为了解决循环依赖的。
循环依赖
简单说,就是A对象依赖B对象,B对象⼜依赖A对象,类似的代码如下:
@Component
public class A{
@Autowired
private B b;
}
@Component
public class B{
@Autowired
private A a;
}

其他还有很多种⽅式,如A依赖B,B依赖C,C依赖A,或是A依赖A⾃⼰,只要产⽣了依赖关系的闭环,即造成了循环依赖。
回顾Bean的生命周期
那么,循环依赖会引发什么问题呢?理解这个问题先得理解Bean的⽣命周期,以下先回顾下Bean的⽣命周期:
1. 启动容器:加载Bean 2. 实例化Bean对象 3. 依赖注⼊:装配Bean的属性 4. 初始化Bean:执⾏aware接⼝⽅法、预初始化⽅法、初始化⽅法、后初始化⽅法 5. 关闭容器:销毁Bean 在以上第四个步骤执⾏完毕,才算⼀个初始化完成的Bean,也即Spring容器中完整的Bean对象。

循环依赖的问题
Spring容器保存Bean的⽅式,是采取缓存的⽅式:使⽤ Map<String, Object> 的结构,key为 Bean的名称,value为Bean对象。需要使⽤时直接从缓存获取。 假如A、B互相依赖(循环依赖):
1. 容器中没有A对象,实例化A对象 2. 装配A中的B对象,发现B在容器中没有,需要先实例化B 3. 实例化B对象 4. 装配B中的A对象,发现A在容器中没有,需要先实例化A 5. 重复第⼀个步骤
Bean会依赖某些注⼊的Bean来完成初始化⼯作 由于Spring⽀持构造⽅法注⼊,属性/Setter注⼊的⽅式,所以不能简单的先把所有对象全部实例 化,放到缓存中,再全部执⾏初始化。原因很简单,此时所有对象的引⽤都可以获取到,但属性都 是null,执⾏初始化甚⾄构造⽅法都可能出现空指针异常。
Spring不支持的循环依赖
Spring框架在处理循环依赖时存在⼀些限制, 它不能解决所有类型的循环依赖问题。
⽐如以下循环依赖的场景, Spring并不⽀持(包含但不限于):
1. 构造器循环依赖:当两个Bean在它们的构造器中相互依赖时,Spring⽆法解决这种循环依赖。 Spring在创建Bean时会检查构造器中的循环依赖,并在发现时抛出 BeanCurrentlyInCreationException 异常。 2. 原型Bean循环依赖:对于原型作⽤域(Prototype)的Bean,Spring不⽀持循环依赖。原型Bean的⽣命周期是短暂的,每次请求都会创建新实例,Spring容器不缓存原型Bean,因此⽆法提前暴露⼀个创建中的Bean来解决循环。 3. 复杂的AOP场景下的循环依赖:在⼀些复杂的AOP(⾯向切⾯编程)场景中,如果Bean之间存在循环依赖,并且这些Bean被⽤于AOP代理,Spring可能⽆法解决这种循环依赖。
Spring解决循环依赖的机制
1.三级缓存机制:
一级缓存(SingletonObjects):存储完全初始化好的Bean实例。 二级缓存(EarlySingletonObjects):存储早期暴露的Bean实例,即已经实例化但尚未初始化的Bean。 三级缓存(SingletonFactories):存储能够生成Bean实例的工厂对象。这些工厂对象在被调用时会返回Bean的早期引用,并允许后续对其进行依赖注入。
当Spring容器在创建Bean时遇到循环依赖,它会首先尝试从一级缓存中获取Bean实例。如果找不到,它会检查二级缓存。如果二级缓存中也没有,它会检查三级缓存中的工厂对象,使用工厂对象生成Bean的早期引用,并将其放入二级缓存中。这样,当其他Bean需要注入这个循环依赖的Bean时,就可以从二级缓存中获取到它的早期引用。随着依赖注入的完成和Bean的完全初始化,最终会将Bean从二级缓存移动到一级缓存中。
2.延迟初始化(@Lazy):
通过在Bean上使用@Lazy注解,可以延迟该Bean的初始化。当Spring容器在创建Bean时遇到@Lazy注解的Bean作为依赖项时,它会返回一个代理对象而不是实际的Bean实例。这个代理对象会在第一次被实际使用时才初始化其代理的Bean。这样,即使存在循环依赖,也可以通过代理对象来避免直接的依赖注入冲突。
三级缓存详解
Spring是使⽤三级缓存的机制来解决循环依赖问题,以下为三级缓存的定义:
定义
一级缓存(SingletonObjects):这是存放完全初始化好的Bean的缓存,即Bean已经完成了从创建到初始化(包括依赖注入)的全过程。当从Spring容器中获取Bean时,首先会尝试从一级缓存中获取。 二级缓存(EarlySingletonObjects):这是存放早期暴露的Bean的缓存,所谓“早期暴露”,指的是Bean已经完成了实例化(通过构造函数),但尚未完成初始化(包括依赖注入)。这个缓存主要是为了解决循环依赖问题,当一个Bean被实例化后,就可以被放入这个缓存中,以便在后续的依赖注入过程中可以被引用。 三级缓存(SingletonFactories):这是存放能够生成Bean实例的工厂的缓存。当一个Bean被实例化后,Spring会创建一个ObjectFactory(或称为工厂),并将其放入三级缓存中。这个ObjectFactory负责生成Bean的早期引用(即未完全初始化的Bean实例),并将其放入二级缓存中。当依赖注入完成后,这个ObjectFactory就会被移除。

Spring三级缓存源码
下面看一下三级缓存的源码:

说明:
1.三级缓存singletonFactories中保存的是ObjectFactory对象(Bean⼯⼚),其中包含了 BeanName,Bean对象,RootBeanDefinition,该⼯⼚可以⽣成Bean对象。 2.由于Bean可能被代理,此时注⼊到其他Bean属性中的也应该是代理Bean。
工作流程
1.实例化Bean:当Spring容器需要创建一个Bean时,首先会通过反射等机制实例化这个Bean。 2.早期暴露:实例化完成后,Spring会创建一个ObjectFactory(或称为早期引用),并将其放入三级缓存中。同时,这个早期引用(通过ObjectFactory生成的)也会被放入二级缓存中,以便解决循环依赖问题。 3.依赖注入:接下来,Spring会尝试填充这个Bean的依赖。如果依赖的Bean已经存在于一级或二级缓存中,则直接注入;如果依赖的Bean尚未创建,则递归地创建并填充依赖。 4.初始化Bean:当Bean的所有依赖都被注入后,Spring会执行Bean的初始化操作(如调用初始化方法)。 5.完成创建:初始化完成后,这个Bean会被从二级缓存移动到一级缓存中,供后续使用。同时,与这个Bean相关的ObjectFactory(如果存在)会从三级缓存中移除。

为什么要使用三级缓存?
依照以上三级缓存的流程,其实使⽤⼆级缓存也能满⾜循环依赖的注⼊: • 普通的IoC容器使⽤⼀级缓存即可,但⽆法解决循环依赖问题。 • 解决循环依赖问题:使⽤⼆级缓存即可。⼀级缓存保存完整Bean,⼆级缓存保存提前曝光的不完整的Bean。 • 需要AOP代理Bean时,有两种实现思路: (1)再加⼀级缓存 (2)只使⽤⼆级缓存,其中⼆级缓存保存Bean的代理对象,代理对象中引⽤不完整的原始对象即可 • Spring使⽤三级缓存保存ObjectFactory即Bean⼯⼚,在代码的层次设计及扩展性上都会更好。三级缓存的引⼊主要是为了解决代理相关的循环依赖问题,尤其是涉及到AOP时,代理对象的创建可能需要提前于Bean的完全初始化。
在Spring 3.6之前,Spring通过使⽤⼀级和⼆级缓存来解决单例Bean的循环依赖问题,⽽从
3.6版本开始,通过引⼊三级缓存,Spring能够更全⾯地处理包括AOP在内的更复杂的循环依
赖情况。




