Spring处理循环依赖
什么是循环依赖
循环依赖指的是多个对象之间的依赖关系形成一个闭环。
下图展示了两个对象 A 和 B 形成的一个循环依赖
6925922e7afc55851ca7089a8ef4a749.png下面是个例子
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
下图展示了多个对象形成的一个循环依赖
fc5d08b97d8e6324a6ac9d022e5d748e.png如下例
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService3 testService3;
public void test2() {
}
}
@Service
public class TestService3 {
@Autowired
private TestService4 testService4;
public void test3() {
}
}
@Service
public class TestService4 {
@Autowired
private TestService1 testService1;
public void test4() {
}
}
一个检测循环依赖的方法
在我们具体分析 Spring 的 Field 注入是如何解决循环依赖时, 我们来看看如何到检测循环依赖。在一个循环依赖的场景中,我们可以确定以下约束
-
依赖关系是一个图的结构
-
依赖是有向的
-
循环依赖说明依赖关系产生了环
明确后,我们就能知道检测循环依赖本质就是在检测一个图中是否出现了环, 这是一个很简单的算法问题。
利用一个 HashSet 依次记录这个依赖关系方向中出现的元素, 当出现重复元素时就说明产生了环, 而且这个重复元素就是环的起点。
参考下图, 红色的节点就代表是循环出现的点
image.png以第一个图为例,依赖方向为 A->B->C->A ,很容易检测到 A 就是环状点。
我画了一个简化的流程图来展示一个 Bean 的创建(省略了 Spring 的 BeanPostProcessor,Aware 等事件)过程, 希望你过一遍,然后我们再去看源码。
入口直接从 getBean(String) 方法开始, 以 populateBean 结束, 用于分析循环依赖的处理是足够的了
image.pnggetBean(String) 是 AbstractBeanFactory 的方法, 它内部调用了doGetBean 方法, 下面是源码:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@Overridepublic Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
protected T doGetBean(final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly){
...// #1
Object sharedInstance = getSingleton(beanName);...
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (mbd.isSingleton()) {
// #2
sharedInstance = getSingleton(beanName, new ObjectFactory() {
@Overridepublic Object getObject() throws BeansException {
// #3
return createBean(beanName, mbd, args);
}
});
}
...
return (T)bean;
}
}
我简化了 doGetBean 的方法体,与流程图对应起来,使得我们可以轻松找到下面的调用流程
doGetBean -> getSingleton(String) -> getSingleton(String, ObjectFactory)
getSingleton 是 DefaultSingletonBeanRegistry 的重载方法
DefaultSingletonBeanRegistry 维护了三个 Map 用于缓存不同状态的 Bean, 稍后我们分析 getSingleton 时会用到
/** 维护着所有创建完成的Bean */
private final MapObject> singletonObjects = new ConcurrentHashMapObject>(256);
/** 维护着创建中Bean的ObjectFactory */
private final MapObjectFactory>> singletonFactories = new HashMapObjectFactory>>(16);
/** 维护着所有半成品的Bean */
private final MapObject> earlySingletonObjects = new HashMapObject>(16);
singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用;
earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖;
singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖;
为啥Spring需要设计成三级缓存?
一级缓存需要的原因,大家应该都已了解,现在简单说下为啥Spring需要设计成三级缓存。
(1) 为啥需要二级缓存?
一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。所以,我们就要加一个map,这个map,用来存放那种不完整的bean。也就是需要二级缓存。
(2) 为啥需要三级缓存?
怎么理解呢? 以io流举例,我们一开始都是用的原始字节流,然后给别人用的也是字节流,但是,最后,我感觉不方便,我自己悄悄弄了个缓存字符流(类比代理对象),我是方便了,但是,别人用的,还是原始的字节流啊。你bean不是单例吗?不能这么玩吧?所以,这就是二级缓存,不能解决的问题。
注:为什么不直接将类的代理对象生成,然后放入二级缓存?
因为类的代理对象必须是在类的实例对象已生成的基础上去生成的,如果中间存在类的代理对象的循环依赖,是无法先生成类的代理对象,然后放入二级缓存。也就是二级缓存只能解决普通实例对象的循环依赖,如果存在代理对象的循环依赖,是无法解决的。
getSingleton(String) 调用了重载方法 getSingleton(String, boolean) , 而该方法实际就是一个查询 Bean 的实现, 先看图再看代码:
image.png
从图中我们可以看见如下查询层次
singletonObjects => earlySingletonObjects => singletonFactories
再结合源码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
通过 getSingleton(String) 没有找到Bean的话就会继续往下调用 getSingleton(String, ObjectFactory) , 这也是个重载方法, 源码如下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
流程很清晰,就没必要再画图了,简单来说就是根据 beanName 找不到 Bean 的话就使用传入的 ObjectFactory 创建一个 Bean。
从最开始的代码片段我们可以知道这个 ObjectFactory 的 getObject 方法实际就是调用了 createBean 方法
sharedInstance = getSingleton(beanName, new ObjectFactory() {@Overridepublic Object getObject() throws BeansException {// #3return createBean(beanName, mbd, args);}});
createBean 是 AbstractAutowireCapableBeanFactory 实现的,内部调用了doCreateBean 方法doCreateBean 承担了 bean 的实例化,依赖注入等职责。
参考下图
image.pngcreateBeanInstance 负责实例化一个 Bean 对象。addSingletonFactory 会将单例对象的引用通过 ObjectFactory 保存下来, 然后将该 ObjectFactory 缓存在 Map 中(该方法在依赖注入之前执行)。
populateBean 主要是执行依赖注入。
下面是源码, 基本与上面的流程图保持一致, 细节的地方我也标了注释了
如果你仔细看了上面的代码片段,相信你已经找到 Spring 处理循环依赖的关键点了。我们以 A,B 循环依赖注入为例,画了一个完整的注入流程
image.png注意上图的 黄色节点, 我们再来过一下这个流程
在创建 A 的时候,会将 实例化的A 通过 addSingleFactory(黄色节点)方法缓存, 然后执行依赖注入B。
注入会走创建流程, 最后B又会执行依赖注入A。
由于第一步已经缓存了 A 的引用, 再次创建 A 时可以通过 getSingleton方法得到这个 A 的提前引用(拿到最开始缓存的 objectFactory, 通过它取得对象引用), 这样 B 的依赖注入就完成了。
B 创建完成后, 代表 A 的依赖注入也完成了,那么 A 也创建成功了 (实际上 Spring 还有 initial 等步骤,不过与我们这次的讨论主题相关性不大)
这样整个依赖注入的流程就完成了
Spring处理循环依赖的基本思路是这样的:
虽说要初始化一个Bean,必须要注入Bean里的依赖,才算初始化成功,但并不要求此时依赖的依赖也都注入成功,只要依赖对象的构造方法执行完了,这个依赖对象就算存在了,注入就算成功了,至于依赖的依赖,以后再初始化也来得及(参考Java的内存模型)。
因此,我们初始化一个Bean时,先调用Bean的构造方法,这个对象就在内存中存在了(对象里面的依赖还没有被注入),然后把这个对象保存下来,当循环依赖产生时,直接拿到之前保存的对象,于是循环依赖就被终止了,依赖注入也就顺利完成了。
举个例子:
假设对象A中有属性是对象B,对象B中也有属性是对象A,即A和B循环依赖。
创建对象A,调用A的构造,并把A保存下来。
然后准备注入对象A中的依赖,发现对象A依赖对象B,那么开始创建对象B。
调用B的构造,并把B保存下来。
然后准备注入B的构造,发现B依赖对象A,对象A之前已经创建了,直接获取A并把A注入B(注意此时的对象A还没有完全注入成功,对象A中的对象B还没有注入),于是B创建成功。
把创建成功的B注入A,于是A也创建成功了。
于是循环依赖就被解决了。
下面从Spring源码的角度看一下,具体是个什么逻辑。
在注入一个对象的过程中,调用了这样一个方法:
Object sharedInstance = this.getSingleton(beanName);
这段代码在AbstractBeanFactory类的doGetBean()方法中。
这里得到的Object就是试图是要创建的对象,beanName就是要创建的对象的类名,这里getSingleton()方法的代码如下:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
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;
这个方法是Spring解决循环依赖的关键方法,在这个方法中,使用了三层列表来查询的方式,这三层列表分别是:
-
singletonObjects
-
earlySingletonObjects
-
singletonFactories
这个方法中用到的几个判断逻辑,体现了Spring解决循环依赖的思路,不过实际上对象被放入这三层的顺序是和方法查询的循序相反的,也就是说,在循环依赖出现时,对象往往会先进入singletonFactories,然后earlySingletonObjects,然后singletonObjects。
下面看一下这个方法的代码逻辑:
-
Object singletonObject = this.singletonObjects.get(beanName);
方法首先从singletonObjects中获取对象,当Spring准备新建一个对象时,singletonObjects列表中是没有这个对象的,然后进入下一步。 -
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
除了判断null之外,有一个isSingletonCurrentlyInCreation的判断,实际上当Spring初始化了一个依赖注入的对象,但还没注入对象属性的时候,Spring会把这个bean加入singletonsCurrentlyInCreation这个set中,也就是把这个对象标记为正在创建的状态,这样,如果Spring发现要创建的bean在singletonObjects中没有,但在singletonsCurrentlyInCreation中有,基本上就可以认定为循环依赖了(在创建bean的过程中发现又要创建这个bean,说明bean的某个依赖又依赖了这个bean,即循环依赖)。
举个例子:对象A和对象B循环依赖,那么初始化对象A之后(执行了构造方法),要把A放入singletonsCurrentlyInCreation,对象A依赖了对象B,那么就要再初始化对象B,如果这个对象B又依赖了对象A,也就是形成了循环依赖,那么当我们注入对象B中的属性A时,进入这个代码逻辑,就会发现,我们要注入的对象A已经在singletonsCurrentlyInCreation中了,后面的逻辑就该处理这种循环依赖了。
-
singletonObject = this.earlySingletonObjects.get(beanName);
这里引入了earlySingletonObjects列表,这是个为了循环依赖而存在的列表,从名字就可以看到,是个预创建的对象列表,刚刚创建的对象在这个列表里一般也没有。 -
get factory
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
}
earlySingletonObjects也没有则从singletonFactories中获取,前面说到singletonFactories是对象保存的第一步,实际上对象初始化后,可能还没有注入对象的依赖,就把对象放入了这个列表。
如果是循环依赖,此时的singletonFactories中一般是会存在目标对象的,举个例子:对象A和对象B循环依赖,那么初始化了对象A(执行了构造方法),还没有注入对象A的依赖时,就会把A放入singletonFactories,然后开始注入A的依赖,发现A依赖B,那么需要构对象B,构造过程也是执行了B的构造后就把B放到singletonFactories,然后开始注入B的依赖,发现B依赖A,在第二步中提到,此时A已经在singletonsCurrentlyInCreation列表里了,所以会进入此段代码逻辑,而且此时时对象A在singletonFactories中确实存在,因为这已经是第二次试图创建对象A了。
- factory creates object
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
代码到这里基本已经确定我们要创建的这个对象已经发生循环依赖了,然后Spring进行了这样的操作,把这个对象加入到earlySingletonObjects中,然后把该对象从singletonFactories中删掉。
- 其实上面5步已经执行完了该方法的代码,这里加的第6步是为了解释循环依赖的结果。在这个方法的代码之后,会把bean完整的进行初始化和依赖的注入,在完成了bean的初始化后,后面代码逻辑中会调用一个这样的方法:
getSingleton(String beanName, ObjectFactory<?> singletonFactory)
这个方法中有个小小的子方法addSingleton(),他的代码是这样的:
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
这个方法处理的是已经注入完依赖的bean,把bean放入singletonObjects中,并把bean从earlySingletonObjects和singletonFactories中删除,这个方法和上面分析的方法组成了Spring处理循环依赖的逻辑。
综上,Spring处理循环依赖的流程大概就是以下这样,假设对象A和对象B循环依赖:
image.png