spring如何解决循环依赖

178 阅读6分钟

循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错。下面说一下Spring是如果解决循环依赖的。

public class A{
    private B b;
    public void setB(B b){
        this.b = b;
    }
    public void getB(){
        return b;
    }
}

public class B{
    private A a;
    public void setA(A a){
        this.a = a;
    }
    public void getA(){
        return a;
    }
}

下面简单的通过一个测试类来描述,spring是如何解决循环依赖的?

public class CircleTest {

    public static void main(String[] args) {
        A a = new A();
        // a里面需要属性注入b,所以要去实例化b
        B b = new B();
        // b里面又需要依赖a,所以就去提前暴露的地方拿到对象a,然后完成属性注入
        b.setA(a);
        // b已经完成了实例化,属性注入等相关的过程,直接把b对象通过调用setB就可以了
        a.setB(b);
        System.out.println("产生的对象A是" + a);
        System.out.println("对象A的属性b是" + a.getB());
        System.out.println("产生的对象B是" + b);
        System.out.println("对象B的属性a是" + b.getA());
    }
    
}

那让我们看看这代代码的运行结果吧,可以看到的是,A中的b就是我们new出的class B的一个对象,证明上面这段代码就是可以解决循环依赖的

当然,spring解决循环依赖主要是依赖于提前暴露A对象,如果一旦对象A提前暴露出去之后,我们再在B获取属性a的时候拿到这个提前暴露出去的A对象,那么循环依赖的问题就迎刃而解了。这里仅仅是一个简单逻辑的如何实现循环依赖的,那么我门再次贴近于Spring的源码观察一下,spring如何解决循环依赖。 首先我们来看看DefaultSingletonBeanRegistry类中的getSingleton方法,可以

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 尝试从一级缓存中拿到beanName对应的对象
		Object singletonObject = this.singletonObjects.get(beanName);
		// 如果从一级缓存中拿不到对象的话,则证明spring容器没有完成对这个对象的注入
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				// 尝试从三级缓存中获取对象,如果获取不到则尝试从二级缓存中获取
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					// 如果三级缓存中获取不到bean实例的话&并且允许循环依赖的话,
					// 那么尝试从二级缓存singletonFactories中获取,一般如果spring允许循环依赖的话,
					// 那么我们的上个例子里面的A对象就会存在于singletonFactories中,
					// 所以这个时候我们就可以拿到A对象,然后执行B中的setA(A a)这个方法来实现B对象中拿到A对象的操作,
					// 当然这个时候A对象中的属性b的值肯定还是空的
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

由上面的getSingleton方法则可以验证出来,A对象可以通过提前暴露的方式来存在某个地方,当B需要A对象的时候,则可以先看下是否A被存放在了某个地方,当然这个地方B是知道它的位置的,这样的话,就可以通过一个中间对象来,解决循环依赖的问题了,那么现在我们就带大家看一下,A对象是怎么被提前暴露出来的,提前暴露的地方在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean的方法中,这里就抽取一部分代码来做讲解,这里可以看到spring是通过几个条件来判断是否需要提前暴露对象(已经完成了实例化,当时没有完成属性注入)的,可以看到依赖于三个条件
1、beanDefinition是单例
2、允许循环依赖,spring是默认支持循环依赖的,当然也可以设置不支持循环依赖
3、当前bean对象是否正在创建 可以看到的是如果三个条件都满足的话,那么spring容器将会把我们入世未深的bean对象调用一个addSignletonFactory方法放到上段代码中的singletonFactories对象里面去的吧

// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		// 提前暴露对象的前提原因 1、beanDefinition是单例 && 允许循环依赖 && bean正在创建
		// 提前暴露是为了可以处理循环依赖
		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");
			}
			// 对bean再一次依赖引用,主要应用SmartInstantiationAwareBeanPostProcessor,
			// 我們熟悉的AOP就是在这里将advice动态织入bean中,若没有则直接返回bean,不做任何处理
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

那我们现在继续分析下addSinletonFactory这个方法吧

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				// 如果系统允许循环依赖,会进入到这里来把工厂添加进来,三级缓存去掉
				// singletonFactories里面的东西
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

这个方法就相当简单了,就是一些集合类的相关操作了,可以看到其中很重要的一步呢,就是把我们的singletonFactory放到我们的singletonFactories里面去了,这里的singletonFactories就是所有spring对象都知道的一个特别的位置。大家都知道spring支持构造器注入和set方法注入,那么构造器注入会导致循环依赖失败,这里的原因就是,我们暴露对象是在对象实例化成功之后,所以我们在实例化的逻辑里面是没有这个特殊的位置的。

总结一下今天的内容,简单的来说,当spring容器需要去生成A对象的时候,我们会先从一系列的缓存中拿对象,那么我们现在简单介绍一下,三级缓存都在哪里完成赋值的事情的
singletonObjects:当bean信息完成了实例化、属性注入、spring相关生命周期的操作之后,才能从这里面获取到bean信息
singletonFactories:当我们调用了getBean方法,对象实例化成功之后,且支持循环依赖的条件下,我们会把beanName存放到这个特殊的位置
earlySingletonObjects:当我们通过singletonFactories拿到真正的对象之后,我们会从singletonFactories移除当前对象,并且把对象存放到这里。关于spring解决循环依赖的问题我们就介绍到这里,期待下次再会!