参考文档:
Spring 解决循环依赖必须要三级缓存吗?
聊聊Spring循环依赖三级缓存是否可以减少为二级缓存的情况
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?

此文比较难懂,可以先看一下参考文档

循环依赖

什么是循环依赖?

e4345b1aeca741bcb5c8ffd076b235c2_tplv-k3u1fbpfcp-watermark

通过上图,我们可以看出:

  • A 依赖于 B
  • B 依赖于 C
  • C 依赖于 A
public class A {
    private B b;
}

public class B {
    private C c;
}

public class C {
    private A a;
}

这种依赖关系形成了一种闭环,从而造成了循环依赖的局面。

下面是未解决循环依赖的常规步骤:

  1. 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行。
  2. A 对象发现需要注入 B 对象,但是容器中并没有 B 对象(如果对象创建完成并且属性注入完成和执行完初始化方法就会放入容器中)。
  3. 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行。
  4. B 对象发现需要注入 C 对象,但是容器中并没有 C 对象。
  5. 实例化 C,此时 C 还未完成属性填充和初始化方法(@PostConstruct)的执行。
  6. C 对象发现需要注入 A 对象,但是容器中并没有 A 对象。
  7. 重复步骤 1。

以上,造成循环依赖。

三级缓存

三级缓存作用

spring 的三级缓存就是用来解决循环依赖的。

Spring 解决循环依赖的核心就是提前暴露对象,而提前暴露的对象就是放置于第二级缓存中。下表是三级缓存的说明:

名称描述
singletonObjects一级缓存,存放完整的 Bean。
earlySingletonObjects二级缓存,存放提前暴露的Bean,Bean 是不完整的,未完成属性注入和执行 init 方法。
singletonFactories三级缓存,存放的是 Bean 工厂,主要是生产 Bean,存放到二级缓存中。

源码如下:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
 
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); 
 
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • 所有被 Spring 管理的 Bean,最终都会存放在 singletonObjects 中,这里面存放的 Bean 是经历了所有生命周期的(除了销毁的生命周期),完整的,可以给用户使用的。
  • earlySingletonObjects 存放的是已经被实例化,但是还没有注入属性和执行 init 方法的 Bean。
  • singletonFactories 存放的是生产 Bean 的工厂。

解决循环依赖

Spring 是如何通过上面介绍的三级缓存来解决循环依赖的呢?这里只用 A,B 形成的循环依赖来举例:

  1. 实例化 A,此时 A 还未完成属性填充和初始化方法(@PostConstruct)的执行,A 只是一个半成品。
  2. 为 A 创建一个 Bean 工厂,并放入到 singletonFactories(三级缓存) 中。
  3. 发现 A 需要注入 B 对象,但是一级、二级、三级缓存均为发现对象 B。
  4. 实例化 B,此时 B 还未完成属性填充和初始化方法(@PostConstruct)的执行,B 只是一个半成品。
  5. 为 B 创建一个 Bean 工厂,并放入到 singletonFactories(三级缓存) 中。
  6. 发现 B 需要注入 A 对象,此时在一级、二级未发现对象 A,但是在三级缓存中发现了对象 A,从三级缓存中得到对象 A,并将对象 A 放入二级缓存中,同时删除三级缓存中的对象 A。(注意,此时的 A 还是一个半成品,并没有完成属性填充和执行初始化方法)
  7. 将对象 A 注入到对象 B 中。
  8. 对象 B 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 B。(此时对象 B 已经是一个成品)
  9. 对象 A 得到对象 B,将对象 B 注入到对象 A 中。(对象 A 得到的是一个完整的对象 B)
  10. 对象 A 完成属性填充,执行初始化方法,并放入到一级缓存中,同时删除二级缓存中的对象 A。

从源码分析(参考)

创建 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
	
    if (instanceWrapper == null) {
        // ① 实例化对象
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }

    final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
    Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
   
    // ② 判断是否允许提前暴露对象,如果允许,则直接添加一个 ObjectFactory 到三级缓存
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 添加三级缓存的方法详情在下方
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // ③ 填充属性
    this.populateBean(beanName, mbd, instanceWrapper);
    // ④ 执行初始化方法,并创建代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);
   
    return exposedObject;
}

创建对象时,执行 getObject() 方法来获取到 Bean。实际上,它真正执行的方法如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 如果需要代理,这里会返回代理对象;否则返回原始对象
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

添加三级缓存的方法如下:

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);
        }
    }
}

@FunctionalInterface
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}

通过这段代码,我们可以知道 Spring 在实例化对象的之后,就会为其创建一个 Bean 工厂,并将此工厂加入到三级缓存中。

因此,Spring 一开始提前暴露的并不是实例化的 Bean,而是将 Bean 包装起来的 ObjectFactory。为什么要这么做呢?此问题下面介绍

如何获取依赖

我们目前已经知道了 Spring 的三级依赖的作用,但是 Spring 在注入属性的时候是如何去获取依赖的呢?

他是通过一个 getSingleton() 方法去获取所需要的 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) {
                    // Bean 工厂中获取 Bean
                    singletonObject = singletonFactory.getObject();
                    // 放入到二级缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

当 Spring 为某个 Bean 填充属性的时候,它首先会寻找需要注入对象的名称,然后依次执行 getSingleton() 方法得到所需注入的对象,而获取对象的过程就是先从一级缓存中获取,一级缓存中没有就从二级缓存中获取,二级缓存中没有就从三级缓存中获取,如果三级缓存中也没有,那么就会去执行 doCreateBean() 方法创建这个 Bean。

为什么需要三级缓存

有个以上大致了解,我们来处理下遗留的问题。

  • 为什么要包装一层 ObjectFactory 对象?
  • 去掉三级缓存,只用两级是否可行?

去除三级缓存

这两个问题网上很多说和 AOP 相关,有关系,但不全面,我做个补充。

如果创建的 Bean 有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是 Spring 无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring 都是在创建好完成品 Bean 之后才创建对应的代理。这时候 Spring 有两个选择:

  1. 去除三级缓存,不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入,即 在 earlySingletonObjects 直接放代理对象就行了。
  2. 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况下,Bean 就可以按着 Spring 设计原则的步骤来创建。

方式一(使用两级缓存的方式)是可行的,但不符合 spring 延迟创建的设计原则, 所以 spring 选择方式二,使用三级缓存,在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。

去除二级缓存

如果去掉了二级缓存,则需要直接在 singletonFactory.getObject() 阶段初始化完毕,并放到一级缓存中。

我们看这种场景

v2-d4226dbd2302405834420ed86f16e9b8_1440w

这种情况下,又会陷入依赖循环。