欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring源码之循环依赖详解

程序员文章站 2022-06-19 16:52:44
通过阅读Spring源码,来看看Spring中的循环依赖是如何产生的,以及又是如何解决的。...


前言

  IOC是Spring的重要特性,但在依赖注入过程中,如果不注意使用的话就会发生循环依赖现象。这篇文章通过跟踪Spring实例化bean的过程,来分析循环依赖产生的原因以及如何解决循环依赖的。


一、单例bean实例化的过程

  1. getSingleton
  2. doCreateBean
    2.1. createBeanInstance
    2.2. applyMergedBeanDefinitionPostProcessors
    2.3. addSingletonFactory
    2.4. populateBean
  3. addSingleton

1. getSingleton

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	//根据beanName从缓存中拿实例
	//先从一级缓存拿
	Object singletonObject = this.singletonObjects.get(beanName);
	//如果bean还正在创建,还没创建完成,其实就是堆内存有了,属性还没有DI依赖注入
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			//从二级缓存中拿
			singletonObject = this.earlySingletonObjects.get(beanName);
			//如果还拿不到,并且允许bean提前暴露
			if (singletonObject == null && allowEarlyReference) {
				//从三级缓存中拿到对象工厂
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					//从工厂中拿到对象
					singletonObject = singletonFactory.getObject();
					//升级到二级缓存
					this.earlySingletonObjects.put(beanName, singletonObject);
					//删除三级缓存
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

Spring提供了三个map用来存放不同状态的bean,也就是我们常说的三级缓存。

  • singletonObjects(单例池,存放实例化完成的bean)
  • earlySingletonObjects(映射bean的早期引用,里面存放实例化不完整的bean)
  • singletonFactories(bean的原始工厂)

2. doCreateBean

2.1. createBeanInstance

//寻找当前正在实例化的bean中有@Autowired注解的构造函数
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
		mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
	//如果ctors不为空,就说明构造函数上有@Autowired注解,或者只有一个有参构造函数
	return autowireConstructor(beanName, mbd, ctors, args);
}
//否则调用bean的无参构造函数进行instantiate
return instantiateBean(beanName, mbd);

2.2. applyMergedBeanDefinitionPostProcessors

//收集bean中的注解,比如@Autowired,@Value,@Resource等
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
	for (BeanPostProcessor bp : getBeanPostProcessors()) {
		if (bp instanceof MergedBeanDefinitionPostProcessor) {
			MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
			bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
		}
	}
}

2.3. addSingletonFactory

//是否允许bean提前暴露(单例、允许循环依赖、正在被创建)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isTraceEnabled()) {
		logger.trace("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	//添加三级缓存
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

2.4. populateBean

for (BeanPostProcessor bp : getBeanPostProcessors()) {
	if (bp instanceof InstantiationAwareBeanPostProcessor) {
		InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
		//依赖注入过程,@Autowired的支持
		PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
		if (pvsToUse == null) {
			if (filteredPds == null) {
				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			}

			//老版本用这个完成依赖注入过程,@Autowired的支持
			pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
			if (pvsToUse == null) {
				return;
			}
		}
		pvs = pvsToUse;
	}
}

二、循环依赖产生的原因

在bean实例化的过程中,也就是在createBeanInstance 方法,Spring会调用bean的构造方法进行instantiated。看过我前面文章的就会清楚,如果调用的是有参函数,参数是一个引用类型,那么Spring会先去实例化参数,如果这里不注意的话就很容易产生循环依赖问题。
举个例子:假如有2个bean,A和B,构造函数如下:public A(B b)public B(A a)
A实例化时需要B实例依赖注入到A中,这样在A还没有实例化完成的时候就会去实例化B,当B实例化时需要A实例依赖注入到B中,这样在B还没有实例化完成的情况下就会去实例化A,从而形成一个闭合的循环,类似于死锁。

如果要实例化的A的构造方法是有参的B,那么会调用autowireConstructor方法,因为构造方法里面有参数,所以先会对参数进行处理,这样就会调用参数B的getBean方法,这样就会触发B的createBeanInstance方法,因为B的构造函数里面需要依赖注入A,因为A还没有实例化完成,就会继续调用A的createBeanInstance方法,进而形成循环,造成内存溢出。
Spring源码之循环依赖详解

三、循环依赖的解决办法

如果bean在进行instantiated的时候,只调用bean的无参构造,就不会产生循环依赖问题。如果我们一定要完成A和B之间的相互注入,可以采用属性注入的方式。

在2.2步和2.4步之间,Spring会将bean放入到三级缓存,也就是bean原始工厂中。这样在实例化A时,属性B进行依赖注入,就会去实例化B,B中的A属性进行依赖注入就会去实例化A,但在实例化A之前会先去缓存中获取,因为之前在实例化A时,已经添加了三级缓存,所以这里可以直接从缓存中拿到A的实例,那么B的实例化完成,这样A就能拿到B的实例完成依赖注入,A的实例化完成。

实例化完成之后加入到一级缓存单例池中,同时删除二级和三级缓存。

从上面的流程中可以看出,三级缓存只是作为一个过渡使用,在bean实例化完成之后就不存在了。

如果是多例的情况,Spring是不支持循环依赖的,其实也好理解,原型模式不存在缓存,每次获取bean都是创建新的,这样bean之间存在相互引用就会OOM。

//如果是原型模式,循环依赖则会报异常
if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
}

思考

我们来看一看三级缓存具体是什么

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

可以看出又是一个BeanPostProcessor的应用,以工厂的方式对exposedObject反复修饰,这里我们可以对其进行扩展,只要实现SmartInstantiationAwareBeanPostProcessor接口,重写getEarlyBeanReference方法。由此可见Spring提供给我们的高扩展性,在我们平时的代码设计中,是不是也应该考虑到高扩展性呢。

本文地址:https://blog.csdn.net/qq_27023083/article/details/112638020