Spring IoC之处理Bean创建循环依赖

什么是循环依赖

循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:

image.png

循环依赖,其实就是一个死循环的过程,在初始化 A 的时候发现引用了 B,这时就会去初始化 B,然后又发现 B 引用 C,跑去初始化 C,初始化 C 的时候发现引用了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件。

Spring-Bean循环依赖

Spring 循环依赖的场景有两种:

  1. 构造器的循环依赖
  2. 字段属性的循环依赖

对于构造器的循环依赖,Spring 是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖,所以Spring只解决基于属性的循环依赖,而且Spring只解决 scope 为 singleton 的循环依赖。对于scope 为 prototype 的 bean ,Spring无法解决,直接抛出 BeanCurrentlyInCreationException 异常。Spring为什么不处理 prototype bean 的循环依赖呢?其实如果理解 Spring 是如何解决 singleton bean 的循环依赖就明白了,所以我们先来关注 Spring 是如何解决 singleton bean 的循环依赖的。

Spring-解决循环依赖

我们先从加载 bean 最初始的方法AbstractBeanFactory#doGetBean方法开始。在 #doGetBean(...) 方法中,首先会根据 beanName 从单例 bean 缓存中获取,如果不为空则直接返回。从缓存中获取单例bean调用的是DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference) 方法,代码如下:

// DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从单例缓存中加载 bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果缓存中的 bean 为空,且当前 bean 正在创建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 单例缓存加锁
        synchronized (this.singletonObjects) {
            // 尝试从 earlySingletonObjects 获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // earlySingletonObjects 中没有,且允许提前创建
            if (singletonObject == null && allowEarlyReference) {
                // 从 singletonFactories 中获取对应的 ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 获得 bean
                    singletonObject = singletonFactory.getObject();
                    // 添加 bean 到 earlySingletonObjects 中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 从 singletonFactories 中移除对应的 ObjectFactory
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

这个方法主要是从三个缓存中获取,分别是:singletonObjects、singletonFactories、earlySingletonObjects 。三者定义如下:

/**
 * 缓存单例bean: bean name --> bean instance.
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/**
 * 缓存单例bean的factory: bean name --> ObjectFactory.
 * 这个 Map 也是解决【循环依赖】的关键所在。
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/**
 * 缓存[早期]单例bean: bean name --> bean instance.
 * [早期]单例bean可以理解为还没有还没有初始化完整的bean。
 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

这三个缓存是 Spring 解决 singleton bean循环依赖的关键因素所在,称他们为三级缓存:

  1. 第一级为 singletonObjects:单例对象的 Cache
  2. 第二级为 earlySingletonObjects:单例对象工厂的 Cache
  3. 第三级为 singletonFactories:提前曝光的单例对象的 Cache

这里,我们已经通过 #getSingleton(String beanName, boolean allowEarlyReference) 方法,看到他们是如何配合的。详细分析该方法之前,提下其中的 #isSingletonCurrentlyInCreation(String beanName) 方法和 allowEarlyReference 变量:

  • isSingletonCurrentlyInCreation(String beanName) 方法:判断当前 singleton bean 是否处于创建中。bean 处于创建中,也就是说 bean 在初始化但是没有完成初始化,这样一个过程与Spring 解决 bean 循环依赖的理念相辅相成。因为 Spring 解决 singleton bean 循环依赖的核心就是在bean已经开始初始化但还未初始化完成时就将其曝光。
  • allowEarlyReference 变量:从字面意思上面理解就是允许提前拿到引用。其实真正的意思是,是否允许从 singletonFactories 缓存中通过 #getObject() 方法,拿到已经开始初始化但未初始化完成的对象。为什么会有这样一个字段呢?原因就在于 singletonFactories 是 Spring 解决 singleton bean 循环依赖的诀窍所在,这个我们后续分析。

#getSingleton(String beanName, boolean allowEarlyReference) 方法,整个过程如下:

  • 首先,从一级缓存 singletonObjects 获取。
  • 如果,没有且当前指定的 beanName 正在创建,就再从二级缓存 earlySingletonObjects 中获取。
  • 如果,还是没有获取到且允许singletonFactories通过#getObject()获取,则从三级缓存singletonFactories获取。如果获取到,则通过其#getObject()方法,获取对象,并将其加入到二级缓存earlySingletonObjects中,并从三级缓存singletonFactories删除。代码如下:
// DefaultSingletonBeanRegistry.java
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

所以,二级缓存存在的意义,就是缓存三级缓存中的 ObjectFactory 的 #getObject() 方法的执行结果,提早曝光的单例 bean 对象。

SingletonFactory

上面是从缓存中获取,但是缓存中的数据从哪里添加进来的呢?一直往下跟会发现在AbstractAutowireCapableBeanFactory 的 #doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) 方法中,有这么一段代码:

// AbstractAutowireCapableBeanFactory.java

// 提早缓存单例,以便即使在诸如BeanFactoryAware之类的生命周期接口触发时也能够解析循环引用。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

当一个 Bean 满足三个条件时,则调用 #addSingletonFactory(...) 方法,将它添加到缓存中。三个条件如下:

  • 单例
  • 运行提前暴露 bean
  • 当前 bean 正在创建中

跟进addSingletonFactory()方法,从这段代码可以看出,singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的诀窍所在。同时这段代码发生在 #createBeanInstance(...) 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了。所以 Spring 在这个时候,选择将该对象提前曝光出来。

// DefaultSingletonBeanRegistry.java

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 放入三级缓存
            this.singletonFactories.put(beanName, singletonFactory);
            // 从二级缓存移除
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

addSingleton

介绍到这里我们已经知道三级缓存 singletonFactories 和二级缓存 earlySingletonObjects 值的出处,那一级缓存在哪里设置的呢?按照常用的缓存做法(创建后放入缓存),可以猜测将单例放入缓存的位置应该是在创建单例的时,往下看,在类 AbstractBeanFactory 中,可以发现有这么一段代码:

// AbstractBeanFactory.java
// 创建单例bean
if (mbd.isSingleton()) {
    // <>跟进去看看<>
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        } catch (BeansException ex) {
            // Explicitly remove instance from singleton cache: It might have been put there
            // eagerly by the creation process, to allow for circular reference resolution.
            // Also remove any beans that received a temporary reference to the bean.
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

<>跟进去看看<> 查看调用的方法getSingleton(),代码如下:

// DefaultSingletonBeanRegistry.java

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //....
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            //.....
            if (newSingleton) {
                // 调用addSingleton()
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

注意,此处的 #getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法与开始介绍的#getSingleton(String beanName, boolean allowEarlyReference)不同。

 

#addSingleton(String beanName, Object singletonObject) 方法,代码如下:

// DefaultSingletonBeanRegistry.java

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 添加至一级缓存,同时从二级、三级缓存中删除
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

小结

至此,Spring 关于 singleton bean 循环依赖已经分析完毕了。所以我们基本上可以确定 Spring 解决循环依赖的方案了:

  • Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 缓存中)。
  • 这样,一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 ObjectFactory 的 #getObject() 方法来获取了。

到这里,关于 Spring 解决 bean 循环依赖就已经分析完毕了。最后来描述下就上面那个循环依赖 Spring 解决的过程:

  • 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来
  • 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来
  • 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories ),通过 ObjectFactory 提前曝光,所以可以通过 ObjectFactory#getObject() 方法来拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中
  • 回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了

 

Spring为什么不处理 prototypebean呢?

最后回答一下开始遗留的问题,相信大家看了上面的描述之后,应该知道Spring解决循环依赖的关键是singletonFactories ,而其映射关系是beanName-->ObjectFactory,只有单例我们才能够根据beanName获取唯一的ObjectFactory,而作用域为prototype是没有做到的。[个人理解]


版权声明:本文为shallynever原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。