目录
一、问题
二、代码查看
三、如何解决循环依赖?
一天同事和我说我们的项目发生了循环依赖,把我给震惊到了,直呼: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.为什么需要二级缓存?