美文网首页
从源码分析Spring是如何解决循环依赖的

从源码分析Spring是如何解决循环依赖的

作者: Darius杝人 | 来源:发表于2020-02-14 01:03 被阅读0次

循环依赖问题

什么是循环依赖

首先看一下下面的Spring配置文件

<!-- beanA依赖于beanB -->
<bean id="beanA" class="top.okay3r.ClassA">
    <property name="beanB" ref="beanB"/>
</bean>
<!-- beanB依赖于beanA -->
<bean id="beanB" class="top.okay3r.ClassB">
    <property name="beanA" ref="beanA"/>
</bean>

当IOC容器读取上面的配置时,就会先对beanA进行加载;在对beanA进行属性填充时,会发现beanA依赖于beanB,然后就会对beanB进行加载;当对beanB进行属性填充时,又会发现beanB依赖于beanA,于是就加载beanA...
可以想到,如果Spring的容器对于这种循环依赖问题不作出响应的处理,那么就会无限执行上面的过程。最终的结果就可能造成OOM从而导致程序崩溃


WX20200213-173232@2x.png

Spring中bean注入的方式

我们知道在Spring中,注入bean的方式有【构造器注入】和【setter注入】两种方式。但在我们使用Spring管理bean时,可能会遇到一种特殊的情况,那么就是上面所说的循环依赖问题
我们再看一下Spring创建bean的过程

Spring创建bean的过程

如果阅读过IOC相关的源码就会知道,创建bean的过程大体可以分为初始化bean对bean的属性进行填充对bean进行初始化三个步骤

  • 初始化bean:即new一个bean实例,是通过反射调用构造器实现的
  • 对bean的属性进行填充:可以理解为对<property>标签相应的属性进行赋值
  • 对bean进行初始化:即调用事先配置好的init-method方法,所以可以将一些初始化的行为写到这个方法中

然后就来分析一下两种注入方式

构造器注入

在普通的java程序中,如果已经new出了一个对象,我们就知道这个对象已经是可用的了,不论它的属性是否完整。
但在Spring中,创建出来的bean必须要完成三个步骤才能被认为是可用的,才会将这个“完整”的bean放入到IOC容器中。
因为构造器注入是在实例化对象时反射调用构造器去注入参数,所以既然beanA、beanB的都拿不到完整的依赖,就会进行无限的循环调用,从而无法解决【循环依赖问题】。解决办法就只有是修改依赖关系了

setter注入

再看一下setter注入方式
setter注入方式就是new出一个对象后,调用该对象的set方法对属性进行赋值。此时对象已经被new出来了,只不过是不完整而已。
如果出现了循环依赖的问题,这就要比构造器注入的方式好的多
所以Spring对于循环依赖问题的解决就是针对于setter方法的

接下来就开始分析Spring是如何解决循环依赖问题的

Spring对于循环依赖的解决

先提前知道一下问题大概是怎样解决的

首先我们要知道,Spring对于循环依赖的问题是采用【缓存】的方式解决的
看一下Spring源码中的DefaultSingletonBeanRegistry类(注:SingletonBeanRegistry接口提供了关于访问单例bean的功能,DefaultSingletonBeanRegistry就是该接口的默认实现)

    /** Cache of singleton objects: bean name to bean instance. */
    // 用于存储完整的bean,接下来称之为【一级缓存】
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** Cache of early singleton objects: bean name to bean instance. */
    // 用于存储不完整的bean,即只是new出来,并没有属性值的bean,接下来称之为【二级缓存】
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    /** Cache of singleton factories: bean name to ObjectFactory. */
    //用于存储bean工厂对象,接下来称之为【三级缓存】
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

大概捋一遍bean的获取、创建过程

因为循环依赖都是产生在获取bean时,所以我们直接从AbstractBeanFactory的getBean()方法开始

  1. AbstractBeanFactory#getBean()没什么自身的实现,只调用了doGetBean()
  2. AbstractBeanFactory#doGetBean(),在这个方法中调用了getSingleton(beanName)获取实例:Object sharedInstance = getSingleton(beanName);
  3. 判断sharedInstance是否为null,如果不为null则调用getObjectForBeanInstance处理,然后返回。也就是IOC容器获取bean成功,可以拿去使用了。如果sharedInstance为null,则调用getSingleton(beanName,Object{...})方法
  4. DefaultSingletonBeanRegistry#getSingleton中,首先会从【一级缓存】中get一下bean,如果获取不到,则会进入创建bean的流程
  5. 创建bean的主要逻辑就是走AbstractAutowireCapableBeanFactory#doCreateBean,先是使用createBeanInstance方法创建bean的实例,然后对bean进行初始化,再进行属性填充....然后返回bean
  6. 获取到bean,完成

上面并没有涉及到循环依赖和二级、三级缓存的问题,因为对于循环依赖的处理,都表现在代码中的细节之处

对应上面的过程,从源码上开始分析

首先看doGetBean方法

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException{
        // 从缓存中获取单例bean
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) { //如果获取到单例bean,则走下面代码
            //......
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }else {//如果没有获取到单例bean,则走下面代码   
                //......        
                // 如果是单例的Bean,请下面的代码
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            // 创建单例Bean的主要方法,返回的bean是完整的
                            return createBean(beanName, mbd, args);
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                //......
        }
        return (T) bean;
    }
}

上面的代码中,sharedInstance是通过getSingleton()方法获得的,实际上getSingleton(beanName)方法没什么逻辑,内部调用了getSingleton(beanName, boolean)这个方法,所以接下来就进入到这个方法中

getSingleton(beanName, boolean)的实现

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 从一级缓存中获取单例对象
        Object singletonObject = this.singletonObjects.get(beanName);
        // isSingletonCurrentlyInCreation : 判断当前单例bean是否正在创建中,也就是没有初始化完成。比如beanA的构造器依赖了beanB对象所以得先去创建B对象,或者在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的beanA就是处于创建中的状态
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                // 从二级缓存中获取单例bean
                singletonObject = this.earlySingletonObjects.get(beanName);
                // allowEarlyReference :是否允许从singletonFactories中通过getObject拿到对象
                if (singletonObject == null && allowEarlyReference) {
                    // 从三级缓存中获取单例bean
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        // 通过单例工厂获取单例bean
                        singletonObject = singletonFactory.getObject();
                        // 从三级缓存移动到了二级缓存,并移除singletonFactory
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

从上面的代码中可以总结出以下几点:

  1. 先从【一级缓存】中查找,有则直接返回
  2. 如果在【一级缓存】中获取不到,并且对象正在创建中(beanName包含在singletonsCurrentlyInCreation),那么就再从【二级缓存】中查找,有则直接返回
  3. 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取,就从【三级缓存】中获取(singletonFactory.getObject())。通过ObjectFactory获取到的对象,是进行代理后的对象(假设有AOP)。将从【三级缓存】中获取到的对象放到【二级缓存】中,同时删除此beanName对应的【三级缓存数据】

再看一下doGetBean()方法中刚刚没有讲到的“if-else”部分

如果getSingleton()方法获取到了bean,即sharedInstance不为null,则对其进行处理然后返回
如果sharedInstance为null,就要走else中的代码了
首先判断一下是否为单例,(mbd是通过读取配置文件中bean标签生成的bean的定义信息,具体获得的方法这里不详细说了)。因为多例的bean是不需要放入到IOC容器中的,所以这里只处理单例bean
如果为单例,则调用getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        // ......
        // 创建 bean 实例
        singletonObject = singletonFactory.getObject();
        newSingleton = true;
        if (newSingleton) {
            // 添加新创建的bean添加到【一级缓存】中,并删除其他缓存中对应的bean
            addSingleton(beanName, singletonObject);
        }
        // ......
        // 返回 singletonObject
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 将新创建的bean添加到【一级缓存】中
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

        // 从其他缓存中移除相关的bean
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

上面的代码主要包含了两个功能

  1. 获取完整的bean实例
  2. 将新的bean添加到【一级缓存】中,以后getBean的时候就可以直接获取了

可以看到bean实例是由singletonFactory.getObject()拿到的,也就是通过doGetBean()方法中判断是否单例后的匿名内部类获取到的,从而知道获取到的bean是由createBean()方法创建的

creatBean()方法调用了doCreatBean()方法,所以实际的创建逻辑就再doCreatBean()中

doCreatBean()

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {

        // 默认调用无参构造实例化Bean
        // 构造方法的依赖注入,就是发生在这一步
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        // 实例化后的Bean对象,这里获取到的是一个原始对象,即没有进行属性填充的对象
        final Object bean = instanceWrapper.getWrappedInstance();
        Class<?> beanType = instanceWrapper.getWrappedClass();
        
        //......

        // 解决循环依赖的关键步骤
        // earlySingletonExposure:是否”提前暴露“原始对象的引用
        // 因为不论这个bean是否完整,他前后的引用都是一样的,所以提前暴露的引用到后来也指向完整的bean
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        // 如果需要提前暴露单例bean,则将该bean工厂放入【三级缓存】中
        if (earlySingletonExposure) {
            // 将刚创建的bean工厂放入三级缓存中singleFactories(key是beanName,value是FactoryBean)
            // 同样也会移除【二级缓存】中对应的bean,即便没有
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            //填充属性(依赖注入)
            populateBean(beanName, mbd, instanceWrapper);
            //调用初始化方法,完成bean的初始化操作
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        //......

        return exposedObject;
    }

ok,看到这里,整个在有循环依赖问题下创建、获取bean的流程就结束了
举个例子,从头串一下流程。假设beanA->beanB, beanB->beanA,即A、B相互依赖

  1. 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
  2. 在getSingleton()方法中,从一级缓存中查找,没有,返回null
  3. doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
    (ps:现在是2020.2.14 凌晨1点07分,情人节了,因为疫情不能和小杨一起,在我的第一篇博客中纪念一下这个节日😂,祝所有人情人节快乐)
  4. 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
  5. 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第四步的集合中)。判断为true,则将beanA添加到【三级缓存】中
  6. 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
  7. 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
  8. 此时beanB依赖于beanA,调用getSingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
  9. 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
  10. 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getSingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

最后

整个过程大概就是这样了,由于spring的源码比较多,就只挑选了重点部分进行注释
其实主要思想就是利用二级、三级缓存对未初始化完成的bean进行提前的引用暴露,也就是将其设置为可引用的,这样当依赖于他的bean在进行属性填充时就可以直接拿到引用,解决了死循环的问题
============================================================
还有几个比较重要的点,在这里指出位置,可以根据这些去查找看

  • 这三个级别的缓存,在同一时间,同一beanName对应的bean只会存在于一个缓存中
  • 如果没有循环依赖的问题,二级、三级缓存是没有用处的,体现在AbstractAutowireCapableBeanFactory#doCreateBean的判断earlySingletonExposure这个地方
  • 判断循环依赖,是用一个Set集合实现的,正在创建中的beanName会加到这个集合中
  • 三级缓存其实还有创建AOP代理的功能,在AbstractAutowireCapableBeanFactory#createBean调用resolveBeforeInstantiation的位置。而如果没有循环依赖问题,那么代理就是在调用init-method过程中创建的
  • bean实例化之后,属性填充之前,如果有循环依赖,就将这个bean封装到一个ObjectFactory然后放到三级缓存中(为了提前暴露引用)
  • 三级缓存中的ObjectFactory第一次拿出被他保存bean后,这个bean就会进入二级缓存
  • bean被创建完整后,进入一级缓存

》》》》》》》》》》》》》》》》》》》》》

有些东西不知道怎么转述成语言表达出来,还有如果有不好的或者说错的地方希望看过的大佬能帮忙指正,谢谢~~

相关文章

网友评论

      本文标题:从源码分析Spring是如何解决循环依赖的

      本文链接:https://www.haomeiwen.com/subject/eyirfhtx.html