目录
注:如果文章出现错误,请大佬们指正,共同学习下,谢谢
一、问题
一天同事和我说我们的项目发生了循环依赖,把我给震惊到了,直呼:666
然后默默的翻日志查看是什么的问题
关于循环依赖,相信大家都是非常熟悉的,A依赖B,B又依赖A,它们之间就会形成了循环依赖。或是A依赖B,B依赖C,C又依赖A,它们的依赖关系如同下图:
二、代码查看
通过查看原本项目的代码,发现了这样一段代码
BeanA.java代码
@Component
public class BeanA {
@Autowired
BeanB beanB;
}
BeanB.java
@Component
public class BeanB {
private BeanA beanA;
@Autowired
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
好家伙,构造器注入
解决方案:
@Component
public class BeanB {
@Autowired
private BeanA beanA;
}
如果熟悉IOC的加载流程,相信你已经知道是怎么回事了。
在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。
三、如何解决循环依赖?
3.1 三级缓存
3.2 创建原始Bean对象
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
假如是beanA先创建,创建后的对象是BeanA@12345,上面的代码BeanB的beanA变量指向就是改对象。
3.3暴露早期引用
该方法用于把早期对象包装成一个ObjectFactory暴露到三级缓存中,用于解决循环依赖
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
//如果一级缓存不存在该bean
if (!this.singletonObjects.containsKey(beanName)) {
//加入到三级缓存中,,,,,暴露早期对象用于解决循环依赖
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
3.4解析依赖
//populateBean 用于向 beanA 这个原始对象中填充属性,当它检测到 beanA 依赖于 beanB 时,
//会首先去实例化 beanB。
//beanB 在此方法处也会解析自己的依赖,当它检测到 beanA 这个依赖,于是调用
//BeanFactroy.getBean("beanA") 这个方法,从容器中获取 beanA。
populateBean(beanName, mbd, instanceWrapper);
3.5 获取早期的引用
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
/**
* 第一步:我们尝试去一级缓存(单例缓存池中去获取对象,一般情况从该map中获取的对象是直接可以使用的)
* IOC容器初始化加载单实例bean的时候第一次进来的时候 该map中一般返回空
*/
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 若在第一级缓存中没有获取到对象,并且singletonsCurrentlyInCreation这个list包含该beanName
* IOC容器初始化加载单实例bean的时候第一次进来的时候 该list中一般返回空,但是循环依赖的时候可以满足该条件
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
/**
* 尝试去二级缓存中获取对象(二级缓存中的对象是一个早期对象)
* 何为早期对象:就是bean刚刚调用了构造方法,还来不及给bean的属性进行赋值的对象(纯净态)
* 就是早期对象
*/
singletonObject = this.earlySingletonObjects.get(beanName);
/**
* 二级缓存中也没有获取到对象,allowEarlyReference为true(参数是有上一个方法传递进来的true)
*/
if (singletonObject == null && allowEarlyReference) {
//加锁
synchronized (this.singletonObjects) {
// 重新从一级缓存取
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//重新从二级缓存取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
/**
* 直接从三级缓存中获取 ObjectFactory对象 这个对接就是用来解决循环依赖的关键所在
* 在ioc后期的过程中,当bean调用了构造方法的时候,把早期对象包裹成一个ObjectFactory
* 暴露到三级缓存中
*/
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//从三级缓存中获取到对象不为空
if (singletonFactory != null) {
/**
* 在这里通过暴露的ObjectFactory 包装对象中,通过调用他的getObject()来获取我们的早期对象
* 在这个环节中会调用到 getEarlyBeanReference()来进行后置处理
*/
singletonObject = singletonFactory.getObject();
//把早期对象放置在二级缓存,
this.earlySingletonObjects.put(beanName, singletonObject);
//ObjectFactory 包装对象从三级缓存中删除掉
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
接着上面的步骤讲:
1.populateBean 调用 BeanFactroy.getBean("beanA") 以获取 beanB 的依赖。
2.getBean("beanB") 会先调用 getSingleton("beanA"),尝试从缓存中获取 beanA。此时由于 beanA 还没完全实例化好
3.于是 this.singletonObjects.get("beanA") 返回 null。
4.接着 this.earlySingletonObjects.get("beanA") 也返回空,因为 beanA 早期引用还没放入到这个缓存中。
5.最后调用 singletonFactory.getObject() 返回 singletonObject,此时 singletonObject != null。singletonObject 指向 BeanA@1234,也就是 createBeanInstance 创建的原始对象。此时 beanB 获取到了这个原始对象的引用,beanB 就能顺利完成实例化。beanB 完成实例化后,beanA 就能获取到 beanB 所指向的实例,beanA 随之也完成了实例化工作。由于 beanB.beanA 和 beanA 指向的是同一个对象 BeanA@1234,所以 beanB 中的 beanA 此时也处于可用状态了。
如上面的流程来说:
1.为什么需要二级缓存?
- 一级缓存和二级缓存相比:二级缓存只要是为了分离成熟Bean和纯净Bean(未注入属性)的存放, 防止多线程中在Bean还未创建完成时读取到的Bean时不完整的。所以也是为了保证我们getBean是完整最终的Bean,不会出现不完整的情况。
- 一二三级缓存下二级缓存的意义:二级缓存为了存储 三级缓存的创建出来的早期Bean, 为了避免三级缓存重复执行。