spring源码解析之原型模式下的循环依赖

众所周知,Spring托管了我们对象的创建,销毁,管理着整个bean的生命周期。但是在对象的创建过程中,有一种特殊情况,存在可能两个bean之间互相引用,例如下面的TestA中引用了TestB,TestA中引用了TestA,即你中有我,我中有你。

public class TestA {
    //引用了TestB
    private TestB testB;

    //省略get,set方法
}

================================
public class TestB {
    //引用了TestA
    private TestA testA;
    
    //省略get,set方法
}

那像这种情况,为什么会产生循环依赖呢,Spring又是如何巧妙的解决了这么一个问题呢?我们带着问题继续看下去。

多例模式下的循环依赖

在Spring中,所有的bean默认是单例的,即singleton。而多例模式下的循环依赖,Spring是无法解决的。首先看下源码,这里有一个prototypesCurrentlyInCreation变量,很是重要,Spring也给出了说明,这个变量用来记录当前正在创建的beanName。最后再着重介绍下到底为什么要设这么一个标志。

/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");

在刚开始调用doGetBean(beanName)来创建bean的时候,会调用isPrototypeCurrentlyInCreation方法进行判断,判断当前的beanName是否在当前线程变量中,如果在则直接抛出BeanCurrentlyInCreationException异常。

/*在刚进来创建时,会判断当前bean是否正在创建*/
if (isPrototypeCurrentlyInCreation(beanName)) {
    //如果正在创建,就会抛出异常
	throw new BeanCurrentlyInCreationException(beanName);
}

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
	Object curVal = this.prototypesCurrentlyInCreation.get();
	return (curVal != null && (curVal.equals(beanName) || 
(curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

第一次进来创建时,变量中肯定没有存储,返回false。接着往下走,调用beforePrototypeCreation方法,将当前的beanName加入当前线程的变量中保存起来,打上一个标记,当前的bean已经开始创建了,为以后的循环依赖做准备。

//IOC容器创建原型模式Bean实例对象
else if (mbd.isPrototype()) {
	// It's a prototype -> create a new instance.
	//原型模式(Prototype)是每次都会创建一个新的对象
	Object prototypeInstance = null;
	try {
		//重点一:回调beforePrototypeCreation方法,默认的功能是注册当前创建的原型对象
		beforePrototypeCreation(beanName);
		//重点二:创建指定Bean对象实例
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		//回调afterPrototypeCreation方法,默认的功能告诉IOC容器指定Bean的原型对象不再创建
		afterPrototypeCreation(beanName);
	}
	//获取给定Bean的实例对象
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

protected void beforePrototypeCreation(String beanName) {
	Object curVal = this.prototypesCurrentlyInCreation.get();
	if (curVal == null) {
		this.prototypesCurrentlyInCreation.set(beanName);
	}
	//...省略无关代码
}

第二步会调用createBean方法,而crateBean的真正是实现是doCreateBean,这里会完成bean的创建,属性依赖注入,初始化等一系列操作。

//创建Bean实例对象
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {

	//...省略无关代码
	try {
		//真正的核心:创建Bean的入口
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		return beanInstance;
	}
	catch (BeanCreationException ex) {
		// A previously detected exception with proper bean creation context already...
		throw ex;
	}
	//...省略无关代码
}

//真正创建Bean的方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
		throws BeanCreationException {
	//...省略无关代码	
	
	//调用默认构造函数进行反射生成实例
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	//Bean对象的初始化,依赖注入在此触发
	populateBean(beanName, mbd, instanceWrapper);
	//初始化Bean对象
	exposedObject = initializeBean(beanName, exposedObject, mbd);
	
	//...省略无关代码
	return exposedObject;
}

在调用populateBean方法时,会去为每个属性赋值,以上面的TestA,TestB为例,当testA发现依赖testB,接着就会去getBean(testB),testB的创建流程跟testA一样。首先会先去set集合中获取当前beanName是否正在创建当中,如果没有,会进行set操作,保存到当前线程变量中。然后继续进行bean的创建。当调用到populateBean方法时,testB会发现依赖testA,那么转过身又会回去创建testA,当调用到isPrototypeCurrentlyInCreation方法时,此时的testA由于还在进行属性赋值,并没有完全的创建成功,那么理所当然他会在set集合当中,此时程序会直接抛出异常,告知当前的bean正在创建当中。

那么问题来了,如果不抛异常,会怎么样呢?没有这么一个set集合又会怎么样呢?大家可以试想一下。如果没有这么一个标记,testA在进行属性赋值时,发现依赖testB,马上又去创建testB,到testB进行属性赋值时,又发现依赖了testA,转过头又去创建testA,而testA依赖了testB,则又会去创建testB。循环往复,形成了一个死循环,永远都出不来了。所以Spring问了避免这种现象,索性直接抛出异常,因为在Spring看来,它也不知道如何来解决这种情况。

下一篇文章,我会着重的介绍单例模式下的循环依赖,包括单例下的构造器的循环依赖,set注入的循环依赖!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值