美文网首页
【啃啊啃 Spring5 源码】细碎二:bean的循环依赖

【啃啊啃 Spring5 源码】细碎二:bean的循环依赖

作者: MrDTree | 来源:发表于2018-07-29 19:25 被阅读49次

    循环依赖

    所谓循环依赖,是指在spring中,类A中有一个成员变量为类B,而类B中也有一个成员变量为类A。这会造成在spring初始化容器中的bean时,bean A的初始化需要bean B的初始化的完成,而bean B的初始化又需要bean A初始化的完成。

    A与B间形成了循环依赖:


    循环依赖的解决

    spring中,循环依赖的解决是有条件的:仅限于singleton(单例)作用域的bean,且依赖的成员变量不是在构造函数中初始化的

    不能解决的情况一:

    原型prototype作用域的bean发生循环依赖会直接抛出异常。

    /**AbstractBeanFactory.doGetBean()方法*/
    // 如果原型作用域的bean在获取时,发现正在创建中
    // 即发生了循环依赖,直接抛出异常
    if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
    }
    

    这种情况好理解,原型的bean如果允许循环依赖,那一定会处在不断创建依赖类的循环中,最终导致堆溢出

    不能解决的情况二:

    单例bean,但依赖的成员变量在构造函数中初始化。如:

    public class A {
        private B b;
    
        public A(B b) {
            this.b = b;
        }
    }
    
    public class B {
        @Autowired
        private A a;
    }
    
    //配置文件:
    <bean id="a" class="test.spring.bean.A" >
        <constructor-arg ref="b" index="0" />
    </bean>
    <bean id="b" class="test.spring.bean.B" />
    

    简单说明一下原因,spring中的循环依赖的解决关键在于:
        缓存 实例化成功,但还没注入成员变量的bean,等发生循环依赖时,直接引用缓存中只实例化过的bean,这样就不用等待依赖bean继续注入成员变量,然后发生循环依赖。

    如果实例化过程中就发生了循环依赖,还没来得及缓存,那就只有抛出异常了。

    循环依赖的解决:

    当我们调用context.getBean()方法来获取一个单例bean时,如果这个bean在容器中还没有,会进行创建(AbstractAutowireCapableBeanFactory.createBean())。

    创建这个bean的过程简单可分为两步:实例化(instantiate,使用构造函数创建bean对象)和初始化(initialize,注入bean的成员变量):

    1. 存入map缓存

    当实例化完后,会将构造好的bean对象存在singletonFactories缓存map中,见AbstractAutowireCapableBeanFactory.doCreateBean()

              // 如果bean为单例且允许循环依赖,将该bean的singletonFactory存在map中暴露
            boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                    isSingletonCurrentlyInCreation(beanName));
            if (earlySingletonExposure) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Eagerly caching bean '" + beanName +
                            "' to allow for resolving potential circular references");
                }
                //重点!!!
                addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
            }
    

    addSingletonFactory()方法中,beanName-ObjectFactory这样的key-value被放进了singletonFactoriesmap缓存集合。

    这里注意,实例化的bean并不是直接存入缓存map,而是通过ObjectFactory工厂对象被暴露在了缓存里,后面循环依赖时,也是以ObjectFactory形式被取出。然后调用ObjectFactory.getObject()——》getEarlyBeanReference()方法,来获取到被SmartInstantiationAwareBeanPostProcessor后处理器处理过的刚刚实例化结束的bean对象。

    这里的ObjectFactory发生过替换。bean在第一次获取时,生成的ObjectFactory对象,getObject()方法实际上会调用createBean()方法创建bean。而bean在创建的过程中,实例化完成后会重新创建ObjectFactory对象,getObject()方法会变成调用getEarlyBeanReference(),用于获取的缓存的实例化的bean。

    2. 循环依赖时,从缓存中取出

    当实例化bean A存入缓存后,我们初始化A ,这时会注入依赖类B(getBean()方法)。

    依赖类B注入时进行创建,实例化——》初始化时,又循环依赖了bean A,但这时,缓存中已经保存了实例化的bean A,我们通过getSingleton()方法直接获取A对象注入后返回。循环依赖被破解。

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
            final String beanName = transformedBeanName(name);
            Object bean;
    
            // 从缓存中获取单例bean,缓存中的bean只被实例化,还未初始化
            Object sharedInstance = getSingleton(beanName);
            //获取成功,直接返回bean
            if (sharedInstance != null && args == null) {
                      …………
            }    
                    ………………
    }
    
    //从singletonFactories中获取该beanName的ObjectFactory,然后通过ObjectFactory获取bean
    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;
    }
    

    3. 整个流程

    最后梳理下整个循环依赖解决的流程,以上面的A、B为例:

    1. context.getBean(A.class)获取容器中的单例A,此时bean A对象还不存在,进行创建。
    2. 实例化bean A,将其放入缓存
    3. 初始化A,注入依赖类B
    4. 通过getBean(B.class)获取依赖类B,发现bean B在容器中不存在,进行创建
    5. 实例化B,将其放入缓存
    6. 初始化B,注入依赖类A
    7. 通过getBean(A.class)获取依赖类A,此时发现缓存中已经存在实例化的A,获取后返回
    8. B初始化成功,返回
    9. A初始化成功, 返回bean A

    注意:整个流程结束后,B持有的也不再是实例化的A,而是初始化结束的A。

    相关文章

      网友评论

          本文标题:【啃啊啃 Spring5 源码】细碎二:bean的循环依赖

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