spring IOC原理之:解决循环依赖
上一章我们讲完了创建实例的过程,接下我们继续来讲解决循环依赖。
1. 什么是循环依赖?
举个例子,这里有三个类 A、B、C,它们的依赖关系是:A<---B<---C,这就形成了一个循环依赖。

2. spring具体如何解决循环依赖?
首先我们构造一个最简单的循环依赖场景,Demo1Service、Demo2Service互相依赖对方:


DefaultListableBeanFactory#preInstantiateSingletons方法中,断点到demo1Service实例初始化的开始之处:

接着调用路径是:
--->AbstractBeanFactory#getBean
--->AbstractBeanFactory#doGetBean
--->DefaultSingletonBeanRegistry#getSingleton
--->AbstractAutowireCapableBeanFactory#createBean
--->AbstractAutowireCapableBeanFactory#doCreateBean:

createBeanInstance方法负责使用构造器创建了demo1Service实例,接着调用populateBean方法注入demo2Service属性。
populateBean方法遍历所有InstantiationAwareBeanPostProcessor类型的后置处理器,来完成依赖注入:

因为demo2Service属性使用@Resource注解,所以由CommonAnnotationBeanPostProcessor完成依赖注入:

调用路径是:
--->CommonAnnotationBeanPostProcessor#postProcessProperties
--->InjectionMetadata#inject
--->CommonAnnotationBeanPostProcessor#getResourceToInject
--->CommonAnnotationBeanPostProcessor#getResource
--->CommonAnnotationBeanPostProcessor#autowireResource

最终又回到AbstractBeanFactory#getBean方法:

demo2Service实例同样通过以下过程创建:getBean--->doGetBean--->createBean--->doCreateBean--->createBeanInstance--->populateBean
在populateBean方法中,demo2Service的demo1Service属性同样依靠CommonAnnotationBeanPostProcessor完成注入:

不同之处在于,通过getBean方法获取demo1Service实例时,已经能从spring三级缓存中获取demo1Servicebean。这是因为demo1Servicebean已经初始化过,只是其依赖的demo2Service属性暂未注入。

2.1. 如何判断demo1Servicebean已经初始化过?
答案是通过这里的isSingletonCurrentlyInCreation方法,该方法中singletonsCurrentlyInCreation保存了已经创建过的bean信息:


在doGetBean--->createBean方法之间,会使用singletonsCurrentlyInCreation来记录正在创建中的beanName:

singletonsCurrentlyInCreation的数据结构是Set,是不允许重复元素的,所以一旦前面记录了,这里的add操作将会返回失败,抛出BeanCurrentlyInCreationException:

2.2. singletonFactory.getObject()如何获取demo1Service实例?
可以看到getObject方法是函数式接口,这里其实是可以直接返回demo1Service实例引用的,但它却调用了getEarlyBeanReference方法:

这是为了后置处理器能通过getEarlyBeanReference方法改变返回的实例,例如创建代理:

于是从singletonFactories缓存的ObjectFactory中得到了之前创建的demo1Service实例:

完整的调用链是这样的:

于是在populateBean方法中,demo2Service愉快的完成了demo1Service的注入:

调用链回到demo1Service实例创建时调用populateBean方法时,可以看到demo2Service属性也注入成功:

至此,spring成功利用三级缓存解决本例循环依赖。
3. spring为什么使用三级缓存而不是两级?
3.1. 二级缓存无法保证多次循环依赖每次获取的对象是同一个对象
其实简单的依赖场景使用二级缓存就可以解决,假如有以下代码:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
这是一个经典的循环依赖,下面用一张图告诉你,spring是如何解决这种循环依赖的:

这种场景完全没有用到第二级缓存。那么问题来了,为什么要用第二级缓存呢?试想一下,如果出现以下这种情况,我们要如何处理?
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Autowired
private TestService3 testService3;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
public void test3() {
}
}
TestService1依赖于TestService2和TestService3
TestService2依赖于TestService1
TestService3依赖于TestService1
按照上图的流程可以把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。
假设不用第二级缓存,TestService1注入到TestService3的流程如图:

TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。这样是不是会有问题?
为了解决这个问题,spring引入的第二级缓存。TestService1实例第一次从三级缓存中被拿出来,接着已经被添加到第二级缓存中了,所以TestService3只需要从第二级缓存中获取该对象即可。

还有个问题,第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?
答案是不行的,因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。
这种情况下,就需要通过getEarlyBeanReference方法改变返回的实例,创建代理。