April 20, 2018

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方法改变返回的实例,创建代理。