聊聊Spring循环依赖

275 阅读6分钟

1. 前言

这是网上比较火的一个八股文,现在springboot高的版本都直接禁用了循环依赖。所以我个人觉得循环依赖本身就是设计上的一种缺陷,如果出现了循环依赖,得看看自己的设计是否合理。那么为什么要聊这个话题呢?其实spring解决循环依赖的思想还是值得借鉴。关于这个问题,网上其实很多文章都没有讲到根本,也都只是八股而已。

2. 自己如何解决循环依赖?

@Component
public class ClassA {

    @Autowired
    private ClassB classB;
}

@Component
public class ClassB {

    @Autowired
    private ClassA classA;
}

如何设计代码去解决循环依赖?

如果只有一个容器,很难解决循环依赖。

因为容器的概念,就是装状态确定的东西。所以如果只有一个容器,那么这个容器里面装的一定是完成初始化后的bean。

一个bean的生命周期是:

  1. 实例化
  2. 属性赋值
  3. 初始化
  4. 销毁

如果 ClassA 和 classB 发生了循环依赖。

只使用一个容器


public static void main(String[] args) {
    // 创建 A
    ClassA a = new ClassA();

    // 进入 a 的属性赋值阶段

    // 找 a 的属性 b 的实例
    Object b = singletonObjects.get("b");

    if (b == null) {
        // 创建对象b
        b = new ClassB();

        // 进入 b 的属性赋值阶段

        // 寻找 b 的属性 a 的示例
        Object aObj = singletonObjects.get("a");


        // 找不到 a 的属性实例  程序无法执行下去....
    }
}

我们可以看到如果只有一个容器(这个容器专注保存完成初始化的bean),那么显然不能完成

那么我们可以加一个容器,用来保存完成实例化,但是没有完成属性赋值 + 初始化的bean

/**
     * 保存完成初始化的bean
     */
final static Map<String, Object> singletonObjects = new HashMap<>();
/**
     * 保存完成实例化,但是没有完成属性赋值 + 初始化的bean
     */
final static Map<String, Object> earlySingletonObjects = new HashMap<>();

public static void main(String[] args) {
    // 创建 A
    ClassA a = new ClassA();

    // 将 A 放入 earlyFactories
    earlySingletonObjects.put("a", a);

    // 进入 A 的属性赋值阶段

    // 找 A 的属性 b 的实例
    Object bObj = singletonObjects.get("b");

    if (bObj == null) {
        // 创建对象 B
        ClassB b = new ClassB();
        // 将 B 放入
        earlySingletonObjects.put("b", b);

        // 进入 B 的属性赋值阶段

        // 寻找 B 的属性 A 的实例
        ClassA aObj = ((ClassA) earlySingletonObjects.get("a"));
        // 属性赋值
        b.setA(aObj);
        // b 进行初始化....
        // b 移除 earlyFactories 放入 singletonObjects
        earlySingletonObjects.remove("b");
        singletonObjects.put("b", b);
    }

    // A 进行初始化...
    // A 移除 earlyFactories 放入 singletonObjects
    earlySingletonObjects.remove("a");
    singletonObjects.put("a", a);

    ClassB b = (ClassB) singletonObjects.get("b");
    System.out.println(b.getA() == a);
}

总结:

解决循环依赖需要借助于状态不同的容器

3. spring里面解决循环依赖的思路

直接看图吧:

循环依赖.jpg

4. 思考

为什么spring用了三个容器?

  1. singletonObjects 存放成品bean
  2. earlySingletonObjects 存放实例化后的bean,但是没有完成属性赋值 + 初始化
  3. singletonFactories 存放的是一个对象工厂,可以用来创建bean

我们知道,每个容器代表的是一个种状态。

现在来考虑一种场景,A 需要 AOP 动态代理,A依赖B,B依赖A。

过程:

实例化A

A塞一个对象工厂到 singletonFactories

A属性赋值

需要获得属性B

实例化B

B赛一个对象工厂到 singletonFactories

B属性赋值

注意,到了B属性赋值阶段,B 会从 singletonFactories里面获得 A 的 对象工厂,然后对象工厂里面是封装了一段逻辑,这段逻辑是判断 A 是否需要AOP动态代理,如果需要AOP动态代理,那么这里通过对象工厂获得的A就是经过代理的A,这样做的目的是为了 这种场景下 B 拿到的是 A 的动态代理对象,而不是原始对象!。做完这些之后,B会把A塞到 earlySingletonObjects.

B初始化

B从三级缓存中把自己移除,加入到 singletonObjects;

A 完成属性赋值阶段

A 完成初始化阶段

注意,A在返回对象之前,会检查自己是否已经被AOP代理了,如果是,那么这里会返回代理对象。

假设现在是 B 要设置 属性 A 的值,所以会调用 getSingleton("a", ture)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 这里会通过 工厂对象 获得 A 的代理对象
                            singletonObject = singletonFactory.getObject();
                            // 加入到二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 从三级缓存中删除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

对象a在初始化的时候,会检查自己是否已经创建了代理对象,如果是,那么直接用代理对象。

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

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                                "Post-processing of merged bean definition failed", ex);
            }
            mbd.markAsPostProcessed();
        }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like 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));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
            throw bce;
        }
        else {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
        }
    }
   
   
   // 注意这里
    if (earlySingletonExposure) {
        // 这里会检查自身已经发生了AOP动态代理,这里实际上是从 二级缓存 earlySingletonObjects 里面取出来的,如果是,那么直接使用代理对象
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName,
                                                               "Bean with name '" + beanName + "' has been injected into other beans [" +
                                                               StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                                               "] in its raw version as part of a circular reference, but has eventually been " +
                                                               "wrapped. This means that said other beans do not use the final version of the " +
                                                               "bean. This is often the result of over-eager type matching - consider using " +
                                                               "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    // Register bean as disposable.
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

所以使用三个缓存的目的就是:三个缓存是三个不同的状态。

  1. 一级缓存:成品bean
  2. 二级缓存:完成了实例化,但是没有完成属性赋值 + 初始化的bean,但是在二级缓存的bean一定确定了是否需要AOP
  3. 三级缓存,这里面放的不是bean,而是一段逻辑,这段逻辑里面可以判断某个对象是否需要AOP动态代理,如果需要,为其生成代理对象。

总结

为什么需要三级缓存?三级缓存为什么是单例工厂?

前提:

1)创建的bean可能需要进行如AOP这样的代理增强

2)正常的代理逻辑是在bean初始化阶段,通过bean后置处理器完成

  1. 如果bean需要进行代理(AOP),同时又存在循环引用,那么必须提前(在属性填充前)进行代理

问题是A在填充属性时,无法得知存在循环依赖的情况, 只有当B进行填充属性发现一二级缓存没有A,并且A是创建中的状态,那么B就知道我和A是存在循环依赖,此时通过A的单例工厂完成A的提前代理(AOP)的功能,至于A是否真正需要进行代理,由单例工厂自身的逻辑决定。

单例工厂的本质是什么?如何理解单例工厂?

单例工厂是封装创建逻辑或者增强逻辑的地方,并具有延时调用和条件触发的功能(回调),类似于延时锦囊牌。