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

循环依赖

所谓循环依赖,是指在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对象。

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。

猜你喜欢

转载自blog.csdn.net/z55887/article/details/81274441