Spring 是如何解决循环依赖

226 阅读2分钟

通过前面的文章大家也许能看出些端倪,循环依赖在整个bean的获取流程中出现了多次,而且也是高频面试题,我们今天就该问题做个简单的介绍

1 Spring循环依赖背景

1.1 循环依赖场景

  • 属性注入
  • 构造器注入

1.2 Spring如何应对

Spring只能解决单例模式下的 属性注入的循环依赖,至于构造器注入和原型模式的依赖注入,Spring会抛出BeanCurrentlyInCreationException 异常

2 Spring如何解决

2.1 三级缓存

阅读过前几篇文章的胖友们,应该对getSingleton 这个方法不陌生吧,下面附上源码哈

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个Map。又称之为三级缓存,其含义以及功能如下
名称功能
singletonObjects存储关系为 beanName-> bean instance,存放单例的bean
earlySingletonObjects存储关系为 beanName->bean instance,它和 singletonObjects的区别是singletonObjects是存放的完整的bean instance,earlySingletonObjects存放的是早期的bean,不一定是完整的(因为spring允许提前暴露,这也是解决循环依赖的关键哈)
singletonFactories关系为 beanName-> BeanFactory,存放单例的bean 的 factory ,是解决循环依赖的关键
  • 2个参数 | 名称 | 功能 | | --- | --- | | isSingletonCurrentlyInCreation |判断当前 singleton bean 是否处于创建中。 Spring 解决 singleton bean 的核心就在于提前曝光 bean | | allowEarlyReference |是否允许从 singletonFactories 缓存中通过 getObject() 拿到对象|

代码含义就是从一级逐步查找,知道第三级缓存,但是如果真的走到第三级别了,会从三级上升至2级,对应代码如下

if (singletonFactory != null) {
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}

而存放三级缓存的地方,在Spring IOC 源码之 createBean 一文中游提及,代码如下

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

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

那么存放一级缓存的触发时机呢 Spring IOC 源码之 getBean(二) 一文也有出现过


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(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

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