关于Spring如何解决循环依赖这个问题,以前写过一篇文章Spring源码分析3-对Bean循环依赖的处理。但是如今回头看,写得还是略粗糙,而且细节也忘得差不多了。所以决定再写一写,重新理一遍思路。
Bean循环依赖
spring框架的bean可以互相依赖,因此,可能存在循环依赖的情况,例如:
<!--构造器循环依赖(单例)-->
<bean id = "testA" class="org.demo.circle.TestA">
<constructor-arg index = "0" ref="testB"/>
</bean>
<bean id = "testB" class="org.demo.circle.TestB">
<constructor-arg index = "0" ref="testC"/>
</bean>
<bean id = "testC" class="org.demo.circle.TestC">
<constructor-arg index = "0" ref="testA"/>
</bean>
<!--构造器循环依赖(prototype)-->
<bean id = "testA0" class="org.demo.circle.TestA" scope="prototype">
<constructor-arg index = "0" ref="testB0"/>
</bean>
<bean id = "testB0" class="org.demo.circle.TestB" scope="prototype">
<constructor-arg index = "0" ref="testC0"/>
</bean>
<bean id = "testC0" class="org.demo.circle.TestC" scope="prototype">
<constructor-arg index = "0" ref="testA0"/>
</bean>
<!--属性注入循环依赖(单例)-->
<bean id = "testA1" class="org.demo.circle.TestA">
<property name="testB" ref = "testB1"></property>
</bean>
<bean id = "testB1" class="org.demo.circle.TestB">
<property name="testC" ref = "testC1"></property>
</bean>
<bean id = "testC1" class="org.demo.circle.TestC">
<property name="testA" ref = "testA1"></property>
</bean>
<!--属性注入循环依赖(prototype)-->
<bean id = "testA2" class="org.demo.circle.TestA" scope="prototype">
<property name="testB" ref = "testB2"></property>
</bean>
<bean id = "testB2" class="org.demo.circle.TestB" scope="prototype">
<property name="testC" ref = "testC2"></property>
</bean>
<bean id = "testC2" class="org.demo.circle.TestC" scope="prototype">
<property name="testA" ref = "testA2"></property>
</bean>
如上配置文件,包含常见的几种循环依赖,可以归类为以下两大类:
通过构造器注入形成循环依赖:
- testA –> testB –> testC –> testA –> …
- testA0 –> testB0 –> testC0 –> testA0 –> …
通过属性注入形成循环依赖:
- testA1 –> testB1 –> testC1 –> testA1 –> …
- testA2 –> testB2 –> testC2 –> testA2 –> …
按照bean的类型又可以细分为单例和prototype两种(其他scope类型不常用暂不讨论)
本文主要探讨spring对这几种循环依赖的处理策略和原理。
Spring处理策略
先来说结论,通过测试用例我们可以简单验证以上几种循环依赖的处理策略:
/**
* 测试构造器注入循环依赖(单例)
*/
@Test(expected = BeanCreationException.class)
public void testCircleRefByConstructor() {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
TestA testA = (TestA) beanFactory.getBean("testA");
}
/**
* 测试构造器注入循环依赖(prototype类型)
*/
@Test(expected = BeanCreationException.class)
public void testCircleRefByConstructorForPrototype() {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
TestA testA = (TestA) beanFactory.getBean("testA0");
}
/**
* 测试属性注入循环依赖(单例)
*/
@Test
public void testCircleRefBySetter() {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
TestA testA = (TestA) beanFactory.getBean("testA1");
}
/**
* 测试的属性注入循环依赖(prototype类型)
*/
@Test(expected = BeanCreationException.class)
public void testPrototypeRefBySetter() {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
TestA testA = (TestA) beanFactory.getBean("testA2");
}
本文的测试用例代码可以移步spring-learn仓库
测试结果表明:只有单例模式下通过属性注入才能够实现循环依赖,其他情况下都将抛出以下异常BeanCreationException。
源码实现
调用时序图
我们从上边的测试用例出发分析源码实现,主要涉及的类包括XmlBeanFactory及其父类AbstractBeanFactory,和DefaultSingletonBeanRegistry以及它的父类AbstractAutowireCapableBeanFactory。核心的方法如下时序图:
读者可以沿着这个时序图的思路去阅读下bean初始化构建的源码。
检查缓存的单例
我们直接从AbstractBeanFactory#doGetBean开始深入。这个方法一开始先调用getSingleton(beanName)检查缓存里是否有名为name的单例Bean缓存。
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
// 获取已经注册的单例bean实例
Object sharedInstance = getSingleton(beanName);
// ...
}
注意getSingleton(beanName)默认传入的allowEarlyReference参数为true。EarlyReference(早期引用)其实就是spring将还没有初始化完毕的单例bean放进缓存的bean引用,当发生循环依赖的时候,可以直接引用这个早期引用,而不会发生死循环不断地进行bean初始化。
@Override
@Nullable
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从singletonObjects获取已经初始化完毕的单例对象
Object singletonObject = this.singletonObjects.get(beanName);
// 判断该bean是不是正在初始化的单例bean
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从earlySingletonObjects中获取单例bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从singletonFactories缓存里获取单例工厂对象
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过单例工厂对象中获取早期单例bean对象,并缓存到earlySingletonObjects中
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
总结下,getSingleton的流程就是:
- 如果该单例已经初始化完毕,直接返回;
- 如果是在建的单例,则尝试返回单例bean早期引用(也就是尚未构建完的半成品bean引用);
- 如果有单例工厂对象,则使用该对象获取单例bean早期引用,加进缓存并返回;
这里先记住这个几个缓存很重要,尤其关注singletonFactories,下文会用到它。
检查prototype循环依赖
上述检查单例如果返回null,则会进入Prototype的循环依赖检测:
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// ...
} else {
// prototype循环依赖检测
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// ...
}
这里检查到该bean是prototype,而且处于构建中状态,则意味着形成了循环依赖,直接抛异常终止初始化过程。
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
检查方法很简单,通过名字相同来判断是否为同一个bean。注意因为是prototype,所以缓存字段prototypesCurrentlyInCreation可能为Set,包含多个bean实例。
因此我们知道,prototype的bean无论是构造器或者属性注入,都不允许实现循环依赖。
构建不同类型的实例
根据bean类型,分别有各自的构建类型,分为单例、prototype和其他类型三种情况:
//单例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// prototype类型
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// 其他类型
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
值得注意的是构建实例前,spring都会在缓存里标记该bean正常构建中。
比如单例类型在构建前会调用beforeSingletonCreation(beanName),该方法会把该beanName加入缓存中。在前一个章节 《检查缓存的单例》 中会使用这里的缓存来判断这个bean是否处于单例构建状态。注意这个方法,如果缓存里边已存在该bean,会抛出BeanCurrentlyInCreationException异常。这个异常正是在构造器循环依赖(单例)的时候抛出。
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
而prototype以及其他类型调用的是beforePrototypeCreation(beanName),同样也是将beanName加入缓存:
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
这里用到的缓存prototypesCurrentlyInCreation,也正好呼应了是前一章 《检查prototype循环依赖》。
其他类型的bean和prototype类型相同。
因此,spring在构建bean之前都会在缓存里标记该bean处于构建中,所以当发生循环依赖的时候可以快速感知到。
单例对象的早期引用
接上文,如果是单例对象,会调用匿名函数新建createBean进行单例bean构建:
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
}
createBean只是做一些构造前的准备,实际的构建过程在doCreateBean中。这很符合spring一贯的命名风格:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
/// ...
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
/// ...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
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));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
/// ...
}
doCreateBean比较长,上边只保留核心代码。可以看到主要包含三个步骤:
- 1.调用createBeanInstance初始化一个半成品的bean,该方法中会寻找满足条件的构造函数进行bean的初始化;
- 2.如果是构建中的单例bean,并且允许循环依赖(默认允许),那么就会尝试调用addSingletonFactory方法提前曝光一个早期引用(也就是一个半成品bean)到缓存中;
- 3.调用populateBean、initializeBean,将半成品的bean进一步初始化,主要是处理属性注入等等;
扼要地讲,步骤一处理了构造器注入;步骤二进行了半成品bean的早期引用曝光;步骤三进行了属性注入!
我们来看看早期引用曝光的逻辑:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
我们看到singletonFactories中会缓存一个工厂对象ObjectFactory,而这个ObjectFactory的getObject方法正是可以返回一个处于构建中半成品bean对象引用。
我们在前边已经讲到,单例对象构建中如果发现出现循环依赖,会尝试从单例的工厂对象中获得一个早期bean对象,并返回,从而避免循环依赖出现死循环。我们再来回忆下:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从singletonObjects获取已经初始化完毕的单例对象
Object singletonObject = this.singletonObjects.get(beanName);
// 判断该bean是不是正在初始化的单例bean
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从earlySingletonObjects中获取单例bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从singletonFactories缓存里获取单例工厂对象
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过单例工厂对象中获取早期单例bean对象,并缓存到earlySingletonObjects中
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
因此看到这里你就应该明白,单例对象的构造器注入没有进行早期引用曝光,所以是不能实现循环依赖的!但是单例对象的属性注入是先进行了早期引用曝光,在进行属性注入,所以是支持循环依赖的!
总结
本文主要是探讨spring对循环依赖的处理策略和实现原理。我们认识到spring只支持单例对象的属性注入循环依赖,而其他类型的循环依赖都不支持。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 duval1024@gmail.com