什么是循环依赖?
Spring 循环依赖的场景有两种:
- 构造器的循环依赖。
- field 属性的循环依赖。
先上结果:Spring只解决单例下的循环依赖,且只解决field 属性的循环依赖!
构造器的循环依赖无解,只能抛出 BeanCurrentlyInCreationException
异常。(为啥构造器循环依赖无解?后面思考有)
如何解决?
答案就是Spring使用了三级缓存,其中第三级缓存缓存了只实例化但还没有实例化/属性填充的Bean对象!!
首先,要明确spring单例对象的初始化大概分三步:
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
- initializeBean:调用spring xml中的init方法。
我们从加载Bean最初的位置,AbstractBeanFactory.doGetBean()
方法开始:
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 尝试通过bean名称从三级缓存中获取目标bean对象,比如这里的A对象
Object sharedInstance = getSingleton(beanName);
// 我们这里的目标对象都是单例的
if (mbd.isSingleton()) {
// 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
// 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
// 目标对象
sharedInstance = getSingleton(beanName, () -> {
try {
// 尝试创建目标对象
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
throw ex;
}
});
}
return (T) bean;
}
- 第一个步骤的
getSingleton()
方法的作用是尝试从三级缓存中获取目标对象,如果没有获取到,则尝试获取半成品的目标对象(仅实例化);如果第一个步骤没有获取到目标对象的实例,那么就进入第二个步骤。 - 第二个步骤的
getSingleton()
方法的作用是尝试创建目标对象,并且为该对象注入其所依赖的属性。
所以其实获取Bean对象也是先分两步走:一、查缓存 二、再走创建
我们先看一下第一个getSingleton
:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 尝试从缓存中获取成品的目标对象,如果存在,则直接返回
Object singletonObject = this.singletonObjects.get(beanName);
// 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象
// 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的
// 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 获取目标对象的实例
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这里就体现了Spring为解决循环依赖问题所设立的三级缓存:
// DefaultSingletonBeanRegistry.java
/**
* Cache of singleton objects: bean name to bean instance.
*
* 存放的是单例 bean 的映射。
*
* 对应关系为 bean name --> bean instance
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* Cache of singleton factories: bean name to ObjectFactory.
*
* 存放的是【早期】的单例 bean 的映射。
*
* 对应关系也是 bean name --> bean instance。
*
* 它与 {@link #singletonObjects} 的区别区别在,于 earlySingletonObjects 中存放的 bean 不一定是完整的。
*
* 从 {@link #getSingleton(String)} 方法中,中我们可以了解,bean 在创建过程中就已经加入到 earlySingletonObjects 中了,
* 所以当在 bean 的创建过程中就可以通过 getBean() 方法获取。
* 这个 Map 也是解决【循环依赖】的关键所在。
**/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* Cache of early singleton objects: bean name to bean instance.
*
* 存放的是 ObjectFactory 的映射,可以理解为创建单例 bean 的 factory 。
*
* 对应关系是 bean name --> ObjectFactory
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
在加载Bean时,会依次从上面三个缓存中查找并加载,三个缓存分别存有以下三种类型的Bean对象:
singletonObjects
:完整的,属性装配完毕,完成初始化的Bean (一级缓存)earlySingletonObjects
:完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)singletonFactories
:进入实例化阶段的单例对象工厂的cache (三级缓存)
那么问题来了,是在哪儿将这三种类型的Bean存入缓存的呢?
首先可以知道的是二级缓存earlySingletonObjects
,当在三级缓存中查找到数据时,它会被自动存入二级缓存,然后将其从三级缓存中删除。
好,关于三级缓存的存入,我们先看一下上面提到的第二个getSingleton
操作,在查找缓存失败后,他首先会通过createBean
方法尝试创建目标对象,该方法的最终调用是委托给了另一个doCreateBean()
方法进行的:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories
// 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是
// 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是
// 目标bean直接返回
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
try {
// 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,
// 就会递归的调用getBean()方法尝试获取目标bean
populateBean(beanName, mbd, instanceWrapper);
} catch (Throwable ex) {
// 省略...
}
return exposedObject;
}
这里的addSingletonFactory()
方法,会将已实例化但未属性装配、未初始化的Bean对象存入三级缓存singletonFactories
中:
// DefaultSingletonBeanRegistry.java
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);
}
}
}
然后会执行populateBean
以及initializeBean
操作,其中populateBean
会递归调用doGetBean
方法,来装配其依赖的其他Bean!
那么一级缓存是在哪个地方存入的呢?
回到上面的org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
方法:
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 尝试通过bean名称从三级缓存中获取目标bean对象,比如这里的A对象
Object sharedInstance = getSingleton(beanName);
// 我们这里的目标对象都是单例的
if (mbd.isSingleton()) {
// 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
// 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
// 目标对象
sharedInstance = getSingleton(beanName, () -> {
try {
// 尝试创建目标对象
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
throw ex;
}
});
}
return (T) bean;
}
其中的第二个getSingleton
:
// AbstractBeanFactory.java
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//....
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
//.....
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
因为此时Bean以及初始化完毕了,所以他现在是一个完整的Bean,通过addSingleton
将其加入一级缓存即可。
// 可以看到,加入1级缓存后,把二三级都删了
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
总结
用一张图来形象描述下这个Bean初始化中处理循环依赖的过程:
总结一下:
- Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到
singletonFactories
缓存中)。 - 这样,一旦下一个 bean 创建的时候需要依赖 bean ,而这个bean又没有初始化完成,则直接使用 ObjectFactory 的
#getObject()
方法来获取。
问题1: 为什么构造器的循坏依赖无法解决?
因为Spring存入三级缓存的未完全Bean对象必须是已经实例化的,而通过构造器装配在实例化这关就挂了,自然无法存入三级缓存。
问题2:为什么要采用三级缓存?
既然有了三级缓存了,为什么还要设计二级缓存呢?可能很多人觉得二级缓存是个鸡肋,可有可无,其实这是Spring大量使用缓存提高性能的一点体现。每次都通过工厂去拿,需要遍历所有的后置处理器、判断是否创建代理对象,而判断是否创建代理对象本身也是一个复杂耗时的过程。设计二级缓存避免再次调用调用getEarlyBeanReference方法,提高bean加载流程。只能说,Spring是个海洋。
问题3:缓存的生命周期?
- 三级缓存
当earlySingletonExposure属性为true时,将beanFactory加入缓存;当通过getSingleton从三级缓存中取出实例化的原始bean时或者完成初始化后,并清除singletonFactories中bean的缓存。
- 二级缓存
当earlySingletonExposure属性为true时,将beanFactory加入缓存,当通过getSingleton从三级缓存中取出实例化的原始bean时,此时,将获取的bean加入二级缓存。当完成bean初始化,将bean加入一级缓存后,清除二级缓存;
- 一级缓存
当完成bean初始化,通过addSingleton将bean加入一级缓存singletonObjects中,并且这个缓存是常驻内存中的。
从上述分析可知,三级缓存和二级缓存是不共存的,且其在Spring完成初始化,都会被清除掉。
发表评论