前言

在关于Spring的面试中,我们经常会被问到一个问题:Spring是如何解决循环依赖问题的?这个问题
算是关于Spring的一个高频面试题,如果不刻意研读,即使度过源码,面试者也不一定能够一下子回答得上。

本文主要针对这个问题,从源码角度对其实现原理进行剖析。

1.什么是循环依赖?

循环依赖其实就是对象之间的循环引用,即两个或两个以上的Bean互相持有对方,最终形成闭环。

spring boot 模块循环依赖 springboot循环依赖原理_实例化

用代码的形式演示,更容易理解,如下是ClassA和ClassB的声明:

public class ClassA {

	private ClassB classB;

	public ClassB getClassB() {
		return classB;
	}

	public void setClassB(ClassB classB) {
		this.classB = classB;
	}
}

public class ClassB {

	private ClassA classA;

	public ClassA getClassA() {
		return classA;
	}

	public void setClassA(ClassA classA) {
		this.classA = classA;
	}
}
2.循环依赖处理机制?
  • 单例bean构造器参数方式循环依赖(无法解决)
    首先先来演示通过构造器参数方式的循环依赖,结合源码来分析,为什么构造器参数的方式无法解决循环依赖。
// 触发所有非延迟加载单例bean的初始化,主要步骤为getBean
for (String beanName : beanNames) {
	// 合并父BeanDefinition对象
	// map.get(beanName)
	RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
	if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
		if (isFactoryBean(beanName)) {
			Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
			// 如果是FactoryBean则加&
			if (bean instanceof FactoryBean) {
				final FactoryBean<?> factory = (FactoryBean<?>) bean;
				boolean isEagerInit;
				if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
					isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
									((SmartFactoryBean<?>) factory)::isEagerInit,
							getAccessControlContext());
				}
				else {
					isEagerInit = (factory instanceof SmartFactoryBean &&
							((SmartFactoryBean<?>) factory).isEagerInit());
				}
				if (isEagerInit) {
					getBean(beanName);
				}
			}
		}
		else {
			// 实例化当前bean
			getBean(beanName);
		}
	}
}

spring boot 模块循环依赖 springboot循环依赖原理_spring_02

进入getBean()方法

public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}

再次进入到doGetBean()方法

spring boot 模块循环依赖 springboot循环依赖原理_面试_03

首先会尝试从缓存中获取对象,此时还没有这个对象,接着往下看

spring boot 模块循环依赖 springboot循环依赖原理_java_04

spring boot 模块循环依赖 springboot循环依赖原理_实例化_05

spring boot 模块循环依赖 springboot循环依赖原理_实例化_06

这里会将每一个正在创建的BeanName放入singletonsCurrentlyInCreation集合中,该集合在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry中定义。

private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));

接着往下走,执行到singletonObject = singletonFactory.getObject(),会进入到前面的lamda表达式中的createBean方法中。

spring boot 模块循环依赖 springboot循环依赖原理_面试_07

进入doCreateBean方法中,这里会有几个比较重要的几个部分,此步骤是将对象实例化。

spring boot 模块循环依赖 springboot循环依赖原理_面试_08

接下将实例化的对象提前暴露到singletonFactories(也就是三级缓存)

spring boot 模块循环依赖 springboot循环依赖原理_spring boot 模块循环依赖_09

进入addSingletonFactory方法里

spring boot 模块循环依赖 springboot循环依赖原理_spring_10

接着往下执行,来到populateBean方法,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,就会递归的调用getBean()方法尝试获取目标bean

spring boot 模块循环依赖 springboot循环依赖原理_面试_11

进入方法内部,执行到applyPropertyValues

spring boot 模块循环依赖 springboot循环依赖原理_面试_12

进入applyPropertyValues方法内部

spring boot 模块循环依赖 springboot循环依赖原理_spring boot 模块循环依赖_13


spring boot 模块循环依赖 springboot循环依赖原理_实例化_14

接下来就是重点了,进入方法内部,找到bean = this.beanFactory.getBean(refName)这行代码。

spring boot 模块循环依赖 springboot循环依赖原理_java_15

这里发现ClassA对象依赖于ClassB对象,又回到getBean()方法,但是要注意的是,此时getBean里的参数是classB,此时容器中还不存在ClassB这个对象,又回到创建ClassB的过程,执行到属性装配的步骤时,ClassB对象又依赖于ClassA对象,又回到之前的步骤,执行到getSingleton中的beforeSingletonCreation(beanName)方法时,发现此时ClassA已经存在singletonsCurrentlyInCreation这个集合中了,接着会抛出异常,整个过程结束。(这也是为什么构造器参数形式不支持循环依赖的原因)

Error creating bean with name 'classA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classB' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'classB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?
  • prototype原型bean方式循环依赖(无法解决)

    ClassA对象在创建之前,会进行标记这个bean正在被创建,等创建之后会将标记删除。

进入beforePrototypeCreation方法,看一下此方法都做了哪些事。

spring boot 模块循环依赖 springboot循环依赖原理_面试_16

这里可以看到,prototypesCurrentlyInCreation是一个ThreadLocal,ThreadLocal是什么呢?其实它就是一个线程局部变量,它的功能就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。也就是说,此处会将beanName存放到线程局部变量中,后面创建对象的时候,首先会查看这个局部变量是否有值,如果没有值,说明这个对象还没有进行创建,会进入到创建对象的步骤,如果检测到这个beanName已存在,就会抛出异常。这也是为什么prototype原型bean方式循环依赖无法解决的原因。

spring boot 模块循环依赖 springboot循环依赖原理_spring_17

Error creating bean with name 'classA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classB' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'classB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?
  • singleton单例bean的setter方式循环依赖
    再来看一下singleton单例bean的settter方式的循环依赖,它是通过提前暴露一个ObjectFactory对象来完成的,简单来说ClassA在调用构造器完成对象初始化之后,在调用ClassA的setClassB方法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中,ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在,Spring容器初始化ClassB,同时也将ClassB暴露到Spring容器中,ClassB调用setClassA方法,Spring从容器中获取ClassA,因为之前已经提前暴露了ClassA,因此可以获取到ClassA实例,ClassA通过给Spring容器获取到ClassB,完成对象初始化操作,这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
3.Spring如何解决循环依赖问题?

Spring正是通过singleton单例bean的setter方式解决循环依赖的,ClassA在创建的时候,会将自己放入到singletonObjectFactories三级缓存中,在属性装配的时候,检测到ClassA对象依赖于ClassB对象,Spring首先尝试从容器中获取ClassB,此时ClassB不存在,Spring容器会初始化ClassB,同时也将ClassB暴露到容器中,接着就是对ClassB进行属性装配,Spring从容器中获取ClassA,因为之前已经将ClassA暴露到容器中了,因此可以获取到ClassA实例,并将ClassA升级放入二级缓存earlySingletonObjects中,ClassA通过给Spring容器获取到ClassB,完成了ClassB的初始化操作,这样ClassA和ClassB都完成了对象初始化操作,便解决了循环依赖问题。

ClassA升级放入earlySingletonObjects的代码

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	// 判断当前单例bean是否正在创建中
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 是否允许从singletonFactories中通过getObject拿到对象
			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;
}

ClassB初始化完成放入单例池的代码(此步骤是对ClassA进行属性装配时,发现ClassB不存在,将会对ClassB

进行创建,属性装配,对象初始化等一系列步骤执行完之后)

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
	// 将此bean(已成型的Spring Bean)移入一级缓存(单例池)
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

那么ClassB对象完成了ClassB的初始化操作,这样ClassA和ClassB都完成了对象初始化操作。ClassA形成最终的Spring Bean对象之后,会将其放入单例池中,代码同上。

到这里,Spring整个解决循环依赖问题的实现思路已经比较清楚了。对于整体过程,读者朋友只要理解两点:

  • Spring是通过递归的方式获取目标bean及其所依赖的bean的;
  • Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。

也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。