­

如何通俗易懂地解释循环依赖?(理论+源码)

  • 2020 年 2 月 19 日
  • 笔记

说起Spring,通常面试官都会问循环依赖怎么解决?

如果你没看过Spring IoC的相关源码,也不必惊慌,听我娓娓道来!

其实,解决循环依赖并没有想像得那么困难。

Spring IoC是干什么事的,你肯定知道,无非就是创建Bean放到IoC容器中,至于这个容器是什么,你也不必太Care。

既然,要解决循环依赖,那肯定存在着依赖,我们假设有两个类:

A和B,A->B,B->A,且二者是通过@Autowired相互注入的。

既然,我没看过IoC的源码,那我就从我的角度来分析一个创建Bean的过程。(一般你说没看过,面试官都会说,如果你是Spring的作者,你会怎么处理?)

我们假设从A开始创建,那就是先创建A对象,然后创建B对象,再通过反射把B对象set到A对象的属性上去(fieldB.set(a, b)),在创建B的时候发现它又依赖于A,这时候同样地,我要寻找一个A对象set到B的属性上去,但是我们的系统中只能存在一个A对象(单例),我该怎么办呢?

很简单,前面创建A对象的时候就把它保存起来不就行了么?嗯想想,是不是这个道理?我们假设保存在缓存中,后面B对象要使用的时候先去缓存中查找一下不就OK了嘛?!

所以,解决循环依赖的方法就是保存所有创建的对象,后面创建对象的时候有依赖的情况先去缓存中找一下,找到了直接set到那个正在创建的对象的属性上,没找到就创建一个新的对象给那个正在创建的对象,并保存到缓存中。

实际上,Spring中也是这么干的,只不过它的缓存不只一个,而是有四个,让我们来看一看下面的方法。

首先,先从singletonObjects缓存中寻找存不存在,如果不存在,再看看是不是符合正在创建的过程中(isSingletonCurrentlyInCreation()方法中使用的是singletonsCurrentlyInCreation缓存),如果是正在创建中,再看看earlySingletonObjects缓存中是不是存在,如果还不存在且允许早期引用,就从singletonFactories缓存中寻找有没有创建这个Bean的工厂,如果找到了,就调用这个工厂的getObject()方法,并把返回值加入到earlySingletonObjects中且把这个工厂移除,从而防止重复调用这个工厂,保证单例。

整个过程一共用到了四个Map:

  • singletonObjects
  • singletonsCurrentlyInCreation
  • singletonFactories
  • earlySingletonObjects

你可能会问,为什么要搞这么复杂?

那是因为Spring本身就很复杂,它返回的对象可并不是简简单单的对象,很大可能是代理对象,比如,你用到了事务,它会通过AOP代理给你返回一个包含事务的代理对象(通过XxxAutoProxyCreator这种BeanPostProcessor处理),这种对象在早期是不存在的,而我们代码中用到的也应该是这个代理对象,那么,Spring又是怎么实现的呢?

答案就在singletonFactories这个缓存中。

在一个Bean实例化之后,还未执行属性注入等其它BeanPostProcessor之前,会先往singletonFactories中添加一个对象:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

这里就加入了一个叫做早期引用相关的东西:

  protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {      Object exposedObject = bean;      if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {        for (BeanPostProcessor bp : getBeanPostProcessors()) {          if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);          }        }      }      return exposedObject;    }

上面取出来的singletonFactory执行getObject()的时候就会调用到上面这个方法,然后就会执行这里的BeanPostProcessor,生成一个代理对象返回去,同样地,这个代理对象也会缓存起来,全局只有这么一份,等到后面再需要这个类的代理对象的时候直接从缓存里面拿就可以了。

整个过程大概就是这样,光看上面的描述可能有点糊涂,还是要打断点自己慢慢调试才能理解比较深刻。