spring的依赖循环--记录一次工作时的偶遇

目录

一、问题

二、代码查看

三、如何解决循环依赖?


注:如果文章出现错误,请大佬们指正,共同学习下,谢谢

一、问题

一天同事和我说我们的项目发生了循环依赖,把我给震惊到了,直呼:666

然后默默的翻日志查看是什么的问题

spring的依赖循环--记录一次工作时的偶遇

 spring的依赖循环--记录一次工作时的偶遇

关于循环依赖,相信大家都是非常熟悉的,A依赖B,B又依赖A,它们之间就会形成了循环依赖。或是A依赖B,B依赖C,C又依赖A,它们的依赖关系如同下图:

spring的依赖循环--记录一次工作时的偶遇 

 

二、代码查看

通过查看原本项目的代码,发现了这样一段代码

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的加载流程,相信你已经知道是怎么回事了。

spring的依赖循环--记录一次工作时的偶遇

 在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。

三、如何解决循环依赖?

3.1 三级缓存

spring的依赖循环--记录一次工作时的偶遇

 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, 为了避免三级缓存重复执行。

上一篇:10个月,15亿,阿里云如何赋能企业打造交付和创新竞争力


下一篇:Spring处理循环依赖只使用二级缓存可以吗?