前言
在关于Spring的面试中,我们经常会被问到一个问题:Spring是如何解决循环依赖问题的?这个问题
算是关于Spring的一个高频面试题,如果不刻意研读,即使度过源码,面试者也不一定能够一下子回答得上。
本文主要针对这个问题,从源码角度对其实现原理进行剖析。
1.什么是循环依赖?
循环依赖其实就是对象之间的循环引用,即两个或两个以上的Bean互相持有对方,最终形成闭环。
用代码的形式演示,更容易理解,如下是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); } } }
进入getBean()
方法
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
再次进入到doGetBean()
方法
首先会尝试从缓存中获取对象,此时还没有这个对象,接着往下看
这里会将每一个正在创建的BeanName放入singletonsCurrentlyInCreation
集合中,该集合在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
中定义。
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
接着往下走,执行到singletonObject = singletonFactory.getObject()
,会进入到前面的lamda表达式中的createBean
方法中。
进入doCreateBean
方法中,这里会有几个比较重要的几个部分,此步骤是将对象实例化。
接下将实例化的对象提前暴露到singletonFactories(也就是三级缓存)
中
进入addSingletonFactory
方法里
接着往下执行,来到populateBean
方法,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,就会递归的调用getBean()方法尝试获取目标bean
进入方法内部,执行到applyPropertyValues
进入applyPropertyValues
方法内部
接下来就是重点了,进入方法内部,找到bean = this.beanFactory.getBean(refName)
这行代码。
这里发现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
方法,看一下此方法都做了哪些事。
这里可以看到,prototypesCurrentlyInCreation
是一个ThreadLocal,ThreadLocal是什么呢?其实它就是一个线程局部变量,它的功能就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。也就是说,此处会将beanName存放到线程局部变量中,后面创建对象的时候,首先会查看这个局部变量是否有值,如果没有值,说明这个对象还没有进行创建,会进入到创建对象的步骤,如果检测到这个beanName已存在,就会抛出异常。这也是为什么prototype原型bean方式循环依赖无法解决的原因。
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的属性的。