Spring 如何解决循环依赖这是一个非常经典的面试问题,那么 Spring 是如何解决循环依赖问题的呢?又是否能够让其解决循环依赖的方法失效呢?

一、JAVA 原生的循环依赖

JAVA 原生中遇到循环依赖时可以通过如下步骤解决。

实例化 A 对象

实例化 B 对象

往 A 对象中设置 B 对象

往 B 对象中设置 A 对象

但是有另外一种特殊情况,A 的构造方法参数中包含了 B,B 的构造方法参数中包含了 A,这种情况称为构造方法循环依赖。由于 A 和 B 都需要在实例化对象时提供参数,所以这种循环依赖是无解的。

二、Spring 中的循环依赖

如上所述,Spring 也是无法解决构造方法循环依赖的,但是属性循环依赖在实际使用中我们可以看到 Spring 是可以解决的。

Spring 的解决流程与我们上述的步骤一致:

getBean——取得 A Bean,在 doCreateBean 方法中开始创建 Bean 操作。

createBeanInstance——实例化 A Bean。

populateBean——为 A Bean 设置参数,并调用 getBean 方法创建 B Bean。

== createBeanInstance——实例化 B Bean。

== populateBean——为 B Bean 设置参数,并调用 getBean 方法获得未构造完全的 A Bean。

经过以上流程,Spring 就解决了 Bean 的循环依赖,这里面涉及到一个比较关键的方法 getSingleton(String beanName, boolean allowEarlyReference)

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) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

以上代码涉及到了三级缓存的概念,singletonObjects 是第一级缓存,存储了创建好的 Bean;earlySingletonObjects 是第二级缓存,存储了未初始化完成的 Bean;singletonFactories 是第三级缓存,存储了 Bean 工厂。

要解决循环依赖问题,就需要提前暴露未完成属性设值注入的未完成 Bean,否则就无法解决循环依赖。而从上述代码可知,allowEarlyReference 参数是决定是否提前暴露未完成 Bean 的关键参数。

从上述代码也可知,其实只需要二级缓存 earlySingletonObjects 就可以解决循环依赖问题,并不需要用到第三级缓存。

其实三级缓存是为了解决一个特殊场景的应用的,那就是在 AOP 代理情况下的循环依赖,详细内容在下文继续介绍。

三、循环依赖失效

默认的 Bean 工厂 allowEarlyReference 参数默认是 true,所以可以提前暴露未完成 Bean,进而可以解决循环依赖问题。

public Object getSingleton(String beanName) {
	return getSingleton(beanName, true);
}

如果我们复写这个 Bean 工厂,将 allowEarlyReference 修改为 false,那么 Spring 就无法解决循环依赖问题了。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(new DefaultListableBeanFactory(){
    @Override
    public Object getSingleton(String beanName) {
        return super.getSingleton(beanName, false);
    }
});

还有一种更简单的方法,其实 SpringBeanFactory 提供了关闭循环依赖的接口。

setAllowCircularReferences 设置为 false 之后,Spring 不会往三级缓存中注入 Bean 工厂,所以也就无法解决循环依赖。

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

配置方法如下:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.setAllowCircularReferences(false);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);

以上修改都将导致循环依赖抛出异常:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'oneBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'twoBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'oneBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1425)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1175)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
	at com.nineya.spring.bean.cycle.BeanCycleMain.run(BeanCycleMain.java:16)
	at com.nineya.spring.bean.cycle.BeanCycleMain.main(BeanCycleMain.java:31)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'twoBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'oneBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1425)
    ......