目录
一、问题
二、代码查看
三、如何解决循环依赖?
注:如果文章出现错误,请大佬们指正,共同学习下,谢谢 一、问题
一天同事和我说我们的项目发生了循环依赖,把我给震惊到了,直呼: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 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && 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 = this.singletonFactories.get(beanName); //从三级缓存中获取到对象不为空 if (singletonFactory != null) { 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, 为了避免三级缓存重复执行。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)