Spring之循环依赖

所谓循环依赖,顾名思义,就是类 A 依赖类 B,类B同时也依赖类A。Spring解决了基于setter注入基于field注入单例bean的循环依赖问题,没有解决基于构造器的注入以及其他作用域的bean的循环依赖问题。

一、三级缓存

三级缓存对循环依赖的解决至关重要,Spring中的三级缓存在DefaultSingletonBeanRegistry中定义:

/** Cache of singleton objects: bean name to bean instance.
 * 一级缓存: 存放完整的bean对象,完成了属性的填充
 * */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of early singleton objects: bean name to bean instance.
 * 二级缓存: 存放尚未完成属性填充的bean对象,用于将bean对象提前“暴露”出去
 * */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Cache of singleton factories: bean name to ObjectFactory.
 * 三级缓存: 存放ObjectFactory, 顾名思义, ObjectFactory就是一个对象工厂,可以通过它的
 * getObject方法获取一个对象。根据bean name, 我们可以索引到singletonFactories中对应该bean
 * name的ObjectFactory,然后通过ObjectFactory获取bean对象的早期引用(未完成属性填充的对象)。
 * */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

额外提一点,从下面的源码分析可以看到,三级缓存中始终只有其中的某一级缓存保存着bean对象或bean对象的ObjectFactory。

二、bean对象的创建和属性填充过程

解释下面用到的几个术语:

  • 创建:调用对象的构造器创建对象
  • 属性填充:对于被@Autowired注解标注的setter方法或字段,将对应的值设置给它们
  • 初始化:执行bean对象的初始化回调方法
  • 完备的对象:完成了创建和属性填充的对象
  • 不完备的对象/对象的早期引用/暴露出来的对象:完成了创建,但是还没完成属性填充的对象

有了三级缓存的概念后,下面我们还需要看一下bean对象是如何创建的,下面的讨论中,我们只关注单例对象。

在Spring容器的初始化过程中,会调用getBean()方法创建并属性填充所有的单例bean对象,而getBean()又调用了AbstractBeanFactory#doGetBean()方法,doGetBean()包含创建并初始化对象的主要逻辑:

代码1:doGetBean()

doGetBean()的大致逻辑如下:

  • 尝试从三级缓存获取指定bean
  • 获取失败则调用getSingleton(beanName, singletonFactory)创建、填充、初始化单例对象
  • 如果需要的话对得到的完备的bean对象进行类型转换
  • 返回完备的bean对象
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
		@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

	final String beanName = transformedBeanName(name);
	Object bean;

	// <1> 尝试从单例缓存(三级缓存)中获取bean实例
	Object sharedInstance = getSingleton(beanName);
	//  单例缓存中存在bean对象
	if (sharedInstance != null && args == null) {
		// ...
		// 获取到之后不能直接返回, 因为sharedInstance也可能是一个FactoryBean, 该情形下
		// 可能需要返回FactoryBean创建的对象, getObjectForBeanInstance就是完成这个逻辑
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
	// 单例缓存中不存在bean对象
	else {
		// ...
		try {
			final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			// ...
			
			// Create bean instance.
			// 下面开始创建bean实例

			// 要创建的bean是单例的
			if (mbd.isSingleton()) {
				sharedInstance = getSingleton(beanName, () -> { // <2>
					try {
						// ★ 创建bean实例的真正逻辑
						return createBean(beanName, mbd, args); // <3>
					}
					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);
			}
			// 要创建的bean是原型的
			else if (mbd.isPrototype()) {
				// ...
			} else {
				// ...
			}
		}
		catch (BeansException ex) {
			cleanupAfterBeanCreationFailure(beanName);
			throw ex;
		}
	}

	// 如果传入了requiredType参数,需要对上面得到的bean进行类型转换
	if (requiredType != null && !requiredType.isInstance(bean)) {
		// ...
	}
	return (T) bean;
}

下面来看看 代码1 <1>处的逻辑,即代码2。

代码2:getSingleton(beanName, allowEarlyReference)

getSingleton(beanName, allowEarlyReference)的主要逻辑:

  • 尝试从一级缓存获取bean对象
  • 获取到则返回;获取不到下一步
  • 判断当前单例对象是不是正在创建中,如果是的话,则说明存在循环依赖,尝试从二/三级缓存获取对象以解决循环依赖
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 1. 尝试从一级缓存获取【完备的】单例bean对象, 即属性填充完全(或者说完成了初始化)的bean对象
	Object singletonObject = this.singletonObjects.get(beanName);
	// 2. 不存在完备的bean对象,尝试从二级/三级缓存获取不完备的bean对象
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // <1>
		synchronized (this.singletonObjects) {
			// 3. 从二级缓存获取不完备的bean对象
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 4. 没有从二级缓存获取到
			if (singletonObject == null && allowEarlyReference) {
				// 5. 尝试从三级缓存singletonFactories中获取这个单例bean对应的
				//    ObjectFactory对象
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					// 6. 三级缓存非空,使用三级缓存获取不完备的bean对象,放入二级缓存,
					// 并情况二级缓存,确保三级缓存中只有一级持有bean对象
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

额外注意上述代码<1>处的isSingletonCurrentlyInCreation(beanName)判断,它和下面代码3的<1>处相呼应,在代码3的<1>处:创建一个单例对象之前,我们会将该对象标记为正在创建中(singletonCurrentlyInCreation)。

因此,如果上面代码中isSingletonCurrentlyInCreation(beanName)条件为true的话,就说明beanName对应的bean对象已经在创建中了,此时发生了循环依赖

下面再看看上述代码1 <2>getSingleton()的逻辑:

代码3:getSingleton(beanName, singletonFactory)

getSingleton(beanName, singletonFactory)的主要逻辑:

  • 将当前bean标记为正在创建(this.singletonsCurrentlyInCreation.add(beanName)
  • 调用singletonFactory.getObject()创建、属性填充、初始化bean对象
  • 将完备的bean对象放入一级缓存,同时从二/三级缓存移除
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(beanName, "Bean name must not be null");
	synchronized (this.singletonObjects) {
		// 1. 先看看此时一级缓存中是不是有beanName对应的单例对象了
		Object singletonObject = this.singletonObjects.get(beanName);
		// 2. 一级缓存中不存在, 那么就【创建】,对应singletonObject = singletonFactory.getObject();
		if (singletonObject == null) {
			// ... 
			
			// ★ 前置处理: 添加当前beanName到singletonsCurrentlyInCreation, 
			// 即this.singletonsCurrentlyInCreation.add(beanName), 
			// 表示当前bean正在创建当中, 这句代码和【循环依赖】有关,和代码2的<1>处相呼应!
			beforeSingletonCreation(beanName); // <1>
			
			boolean newSingleton = false;
			// ...
			try {
				// <1> 创建单例bean对象
				singletonObject = singletonFactory.getObject();
				newSingleton = true;
			}
			catch (Exception ex) {
				// ...
			}
			if (newSingleton) {
				// 到这里我们得到了一个【完备的】bean对象了,下面的代码将这个bean
				// 对象添加到【一级缓存】,同时从二/三级缓存移除
				addSingleton(beanName, singletonObject);
			}
		}
		return singletonObject;
	}
}

创建单例对象并填充属性的核心逻辑是上述代码(代码3)的<1>处,它实际上调用了代码1<3>处的createBean方法。下面来看看createBean的逻辑:

代码4:createBean()

createBean()的主要逻辑:

  • 委托 doCreateBean()创建、属性填充、初始化bean对象
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	// ...
	RootBeanDefinition mbdToUse = mbd;
	// ...
	try {
		// 大多数情况下,是走下面的逻辑创建bean对象
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		// ...
		return beanInstance;
	} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
		// ...
	}
}

下面来看看doCreateBean的逻辑:

代码5:doCreateBean

doCreateBean的主要逻辑:

  • 委托 createBeanInstance() 创建bean对象(是创建,不包括属性填充和初始化,因此得到的是不完备的bean对象
  • 将不完备的bean对象暴露到三级缓存中,允许提前引用,解决循环依赖
  • 对不完备的bean对象进行属性填充,得到完备的bean对象
  • 执行bean对象的初始化回调方法,例如init-method
  • 注册bean对象的销毁回调方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
	throws BeanCreationException {

	// --------------------1. 获取bean实例------------------------

	// BeanWrapper只不过是对【bean实例对象】的一个包装
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		// note: 只有在getSingletonFactoryBeanForTypeCheck方法中才会往factoryBeanInstanceCache放东西
		// 注意factoryBeanInstanceCache中存放的是FactoryBean对象
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}

	// factoryBeanInstanceCache中获取不到, 两种可能:
	//   - mbd不是单例的
	//   - mbd是单例的, 且
	//   	- beanName压根就不是FactoryBean类型
	//   	- beanName是FactoryBean类型, 但是factoryBeanInstanceCache缓存中没有
	if (instanceWrapper == null) {
		// ★★★ <1> 创建bean实例
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	final Object bean = instanceWrapper.getWrappedInstance();

	Class<?> beanType = instanceWrapper.getWrappedClass();
	if (beanType != NullBean.class) {
		mbd.resolvedTargetType = beanType;
	}

	// --------------------2. 执行所有的MergedBeanDefinitionPostProcessors,
	//                        对BeanDefinition(mbd)进行处理------------------------

	// Allow post-processors to modify the merged bean definition.
	synchronized (mbd.postProcessingLock) {
		if (!mbd.postProcessed) {
			try {
				applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
			} catch (Throwable ex) {
				throw new BeanCreationException(mbd.getResourceDescription(), beanName,
						"Post-processing of merged bean definition failed", ex);
			}
			mbd.postProcessed = true;
		}
	}

	// --------------------3. 处理循环依赖相关(将对象提前暴露到三级缓存)------------------------

	// Eagerly cache singletons to be able to resolve circular references
	// even when triggered by lifecycle interfaces like BeanFactoryAware.

	// ★★★ 此时bean对象刚创建完了还没填充属性,还是【不完备】的,
	// 但是为了避免循环依赖导致栈溢出,不得不把这个不完备的对象【提前暴露】
	// 注: allowCircularReferences恒为true(除非用户显示自定义了)
	// 所以: earlySingletonExposure恒为true(除非用户显示自定义了)
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		// ...
		// ★★★ 这是解决循环依赖的关键点, 把ObjectFactory添加到【三级缓存】,从ObjectFactory
		// 可以【获取不完备的bean对象】
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// --------------------4. 对bean对象进行初始化(not 实例化)------------------------

	// Initialize the bean instance.
	// Spring的命名很讲究: 很显然第3步中将bean对象暴露出去了,所以叫exposedObject
	Object exposedObject = bean;
	try {
		// <4-1> 填充属性
		populateBean(beanName, mbd, instanceWrapper);
		// <4-2> 初始化, 执行初始化回调方法
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	} catch (Throwable ex) {
		// ...
	}

	// ...
	
	// -------------------- 5. bean的销毁回调方法相关的逻辑 ------------------------
	try {
		registerDisposableBeanIfNecessary(beanName, bean, mbd);
	} catch (BeanDefinitionValidationException ex) {
		// ...
	}

	return exposedObject;
}

下面来看一下createBeanInstance()是怎样创建bean对象的,即代码6

代码6:createBeanInstance()

createBeanInstance()的主要逻辑:

  • 如果当前bean存在工厂方法,则调用工厂方法创建bean对象并返回;否则,下一步
  • 尝试从缓存获取 (第二次创建),获取不到下一步
  • 说明是第一次创建,调用determineConstructorsFromBeanPostProcessors()获取候选的构造器
  • 调用autowireConstructor(),该方法利用Spring的构造器匹配算法,选择一个合适的构造器创建bean对象并返回
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// ...
		
		// ★ 如果存在factory method, 则返回factory method创建的对象
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// -----------第二次创建同一个bean----------------

		// Shortcut when re-creating the same bean...
		// 只有第二次创建时才有用
		boolean resolved = false; // 是否已经被解析过了
		boolean autowireNecessary = false;
		// ...

		// ------------否则,是首次创建该bean对象---------------------------

		// Candidate constructors for autowiring?
		// 【注意】在基于XML的配置中, 此时还没有注册BeanPostProcessor, 因此ctors一定为null
		//        但是, 如果在XML配置中制定了autowire="constructor",仍然可以执行下面的if语句
		//        的autowireConstructor
		Constructor<?>[] ctors
				= determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		// 在基于注解的配置中:
		// - 第2个参数: 除非我们显式修改bd (RootBeanDefinition类型), 否则下面的autowire mode恒为AUTOWIRED_NO(0), 也就是第二个条件恒不成立
		// - 第3个参数: 除非我们显式修改bd, 否则下面的hasConstructorArgumentValues恒为false, 也就是第三个条件恒不成立
		// - 第4个条件: 好像大多数情况下也不成立?
		// 所以,可以理解为,【大多数情况下只有第一个条件有用!!!】
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			// 对构造器参数进行自动装配,并创建bean对象 (如果构造器对应的对象不存在,
			// 会尝试创建,同样是走getBean的逻辑)
			return autowireConstructor(beanName, mbd, ctors, args);
		}

		// ...
		
		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}

总结

总结一下单例对象创建的主线逻辑getBean() -> doGetBean() -> getSingleton(beanName, singletonFactory) -> createBean() -> doCreateBean()

其中,doCreateBean()完成的功能最多,它完成(或委托他人完成)了对象的创建、对象早期引用的暴露 (操作三级缓存)、属性的填充、初始化回调的调用、销毁回调的注册等功能。

流程图如下:
在这里插入图片描述

三、Spring是如何解决setter注入的循环依赖的

为了方便讲解,给出下面的示例代码:

package demo3setter.entity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class A {
    private B b;
	
    public B getB() {
        return b;
    }
	
    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

package demo3setter.entity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class B {
    private A a;
	
    public A getA() {
        return a;
    }

    @Autowired
    public void setA(A a) {
        this.a = a;
    }
}

```java
package demo3setter.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("demo3setter.entity")
public class Demo3Config {
}
package demo3setter.config;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

class Demo3ConfigTest {

    @Test
    void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo3Config.class);
    }
}

setter方法何时被执行呢?答案是在属性填充阶段(populateBean())。在容器初始化时,容器会调用getBean("a")创建A类单例对象,依次经过上面流程图中的步骤走到属性填充阶段,该阶段会执行setB(B b),为了执行该方法,Spring首先调用getBean("b")需要将依赖B b注入。

为了详细了解属性填充的过程,我们可以在org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法内设置一个条件断点:

在这里插入图片描述
然后debug程序,程序停在该断点处,查看调用栈:

在这里插入图片描述
上面箭头标注的方法上面都分析过了,点开populateBean的栈帧你会发现,getBean("b")确实是因为要填充A的属性而调用的:

在这里插入图片描述
同样的道理,此时执行到getBean("b")了,B也会走到属性填充那一步,同样也会调用getBean("a"),走到下面的代码处:
在这里插入图片描述
现在的问题是,getSingleton(beanName)返回的是null吗?当然不是。相信弄懂了上一小节的步骤,你很容易得到这一结论,下面结合getSingleton()的代码来讲解:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 1. 尝试从一级缓存获取【完备的】单例bean对象, 即属性填充完全(或者说完成了初始化)的bean对象
	Object singletonObject = this.singletonObjects.get(beanName);
	// 2. 不存在完备的bean对象,尝试从二级/三级缓存获取不完备的bean对象
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // <1>
		synchronized (this.singletonObjects) {
			// 3. 从二级缓存获取不完备的bean对象
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 4. 没有从二级缓存获取到
			if (singletonObject == null && allowEarlyReference) {
				// 5. 尝试从三级缓存singletonFactories中获取这个单例bean对应的
				//    ObjectFactory对象
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					// 6. 三级缓存非空,使用三级缓存获取不完备的bean对象,放入二级缓存,
					// 并情况二级缓存,确保三级缓存中只有一级持有bean对象
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

此时,A类对象处于属性填充阶段,根据流程图, 此刻:

  • 此刻A类的bean对象的ObjectFactory已经暴露在了三级缓存,可以从三级缓存获取不完备的bean对象
  • 一级缓存singletonObject中上不存在该bean对象,因为尚未完成属性填充、初始化等步骤,所以singletonObject == nulltrue
  • 此刻A类bean对象已经被标记为了SingletonCurrentlyInCreation,即isSingletonCurrentlyInCreation(beanName) == true
  • 因此可以进入if语句,进入if语句后,借助三级缓存获取到不完备的A类对象,并放入二级缓存,同时清空三级缓存
  • 最后返回这个不完备的bean对象

所以说,B类对象因为属性填充而调用getBean("a")时,getBean("a")中可以直接从三级缓存中得到(不完备的)bean对象并返回,而不会再次调用getBean("b")而陷入无休止的递归调用,从而解决了循环依赖

总结

总的来说,Spring之所以能解决基于setter和field注入的循环依赖,主要是借助了三级缓存SingletonCurrentlyInCreation标记的帮助:

  • 在创建对象之前,首先标记为SingletonCurrentlyInCreation(即将要创建的bean对象的beanName放入singletonCurrentlyInCreation集合)。该步骤使得再次对同一个beanName调用getBean时,我们可以进入getSingleton(beanName, allowEarlyReference)方法的if语句,寻求二级/三级缓存的帮助
  • 在创建对象之后、属性填充之前,先把该对象暴露到三级缓存。该步骤使得再次对同一个beanName调用getBean时,我们可以直接从三级缓存获取bean对象并返回,无需再次调用getSingleton(beanName, singletonFactory)走创建对象的逻辑,陷入无限的递归调用。
  • 始终确保只有一级缓存有bean对象或bean对象的ObjectFactory

总之,Spring就是靠三级缓存SingletonCurrentlyInCreation标记的配合解决了循环依赖问题。

四、为何基于构造器的循环依赖没有解决

@Component
public class A {
    private B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }
}
@Component
public class B {
    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }
}

如果尝试往容器中注册上述两个存在构造器循环依赖的bean对象,虽然不会陷入无限递归调用而导致栈溢出,但是会抛异常:UnsatisfiedDependencyException。所以说Spring没有解决基于构造器注入的循环依赖。

理解Spring为什么没有解决基于构造器的循环依赖还是得借助之前的流程和流程图。执行构造器(构造器注入)的过程对应流程图中的创建bean对象步骤。

根据代码,A的构造器依赖于B,那么在执行A的构造器之前,需要先满足它的依赖,会调用到getBean("b")

而执行b创建bean对象步骤时,又会反过来调用getBean("a")

然后会走到doGetBean("a", ...),因为还没有提前暴露a的引用,所以从三级缓存获取不到东西,即doGetBean开始的getSingleton(beanName)调用返回null。所以只能继续往下执行,最终走到beforeSingletonCreation(beanName)

protected void beforeSingletonCreation(String beanName) {
	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
		throw new BeanCurrentlyInCreationException(beanName);
	}
}

在这里,由于singletonsCurrentlyInCreation中已经存在a了,所以this.singletonsCurrentlyInCreation.add(beanName)返回false,进入if语句抛出异常!流程结束。

所以,Spring没有解决基于构造器的循环依赖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值