所谓循环引用是指,多个类的相互依赖最终形成了一个依赖环。比如下面的这种情况,

@Service
public class ClassA {
    @Autowired
    private ClassB classB;

    public ClassB getClassB() {
        return classB;
    }

    public void setClassB(ClassB classB) {
        this.classB = classB;
    }
}

@Service
public class ClassB {
    @Autowired
    private ClassC classC;

    public ClassC getClassC() {
        return classC;
    }

    public void setClassC(ClassC classC) {
        this.classC = classC;
    }
}

@Service
public class ClassC {
    @Autowired
    private ClassA classA;

    public ClassA getClassA() {
        return classA;
    }

    public void setClassA(ClassA classA) {
        this.classA = classA;
    }
}

上面的代码形成了下面的循环依赖,

ClassA—->ClassB—->ClassC—->ClassA。

在多数情况下产生循环依赖,都代表了系统的设计出现了问题。根据分层原理,类之间往往是有上层类和底层类之分的。循环依赖的产生往往是下面的两种情况,

  1. 底层类引用了上层类,这种情况下部分功能应该下沉。
  2. 同级别类之间相互引用,这种情况下代表了功能模块划分不清,导致强耦合,应该解耦。

但是万事都不是绝对的,有些情况下的循环引用也是可以接受的。Spring框架在进行对象实例化的时候默认是允许循环依赖的,如果你不允许你的项目出现循环依赖可以通过下面的方式禁止,

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"banma_applicationContext.xml"}, false);
context.setAllowCircularReferences(false);
context.refresh();

上面的代码运行会抛出异常。

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘classA’: Requested bean is currently in creation: Is there an unresolvable circular reference?

有些时候,循环依赖的存在会导致项目在启动的时候抛出一些比较特殊的错误。比如公司最近的项目上线的时候抛出了异常,

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘xxxx’: Bean with name ‘xxxx’ has been injected into other beans [yyyy] 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 ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.

通过上面的异常描述,我们可以大概猜出是跟出现了循环依赖有关系,那这就很奇怪了,正常情况下Spring是允许循环依赖的存在的。仔细排查后发现,新加的代码的确存在循环依赖,而报错的bean在创建的时候使用了公司内部的一个框架,该框架在bean创建的时候使用BeanPostProcessor来生成一个新的代理类。于是猜想是否跟这个有关系,为了复现异常,我们可以在上面的代码中添加一个BeanPostProcessor来处理ClassA,

@Service
public class ClassAPostProcessor implements BeanPostProcessor  {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass() == ClassA.class) {
            return new ClassA();
        }

        return bean;
    }
}

上面的BeanPostProcessor虽然没有什么实际用处,但是对于我们复现问题来说够用了。运行代码果然抛出了上面的异常。 为了一探究竟,找出了Spring的源码,

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			if (exposedObject != null) {
				exposedObject = initializeBean(beanName, exposedObject, mbd);
			}
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		if (earlySingletonExposure) {
			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<String>(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 " +
								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

触发异常需要满足几个条件:

  1. earlySingletonExposure == true
  2. earlySingletonReference != null
  3. exposedObject != bean
  4. actualDependentBeans不为空

我们依次来解释下上面的几个变量代表了什么

  1. earlySingletonExposure 允许提前暴露单例,也就是说该对象虽然还没有初始完成,但是其他对象可以提前引用该对象。允许循环引用时该值才可能为true。
  2. earlySingletonReference
    被提前引用的对象,也就是同名的已经被引用的对象。
  3. exposedObject
    调用init函数,以及pre,post processor生成的最终对象。
  4. bean 调用init函数,以及pre,post processor之前的创建的对象。也就是通过反射生成的最初的对象。
  5. actualDependentBeans 已经创建了,并且对当前对象有引用的对象。

将上面的内容串联起来我们可以到如下结论,

如果允许提前暴露实例(earlySingletonExposure),该对象(earlySingletonReference)早已经被引用(earlySingletonReference、actualDependentBeans),同时初始的对象(bean)和最终生成的对象(exposedObject)不一致,就抛出异常。

将上面的结论应用在我们之前的那个例子,也就是说

beanC引用了初始的beanA,而beanA经过ClassAPostProcessor后变成了另一个对象,这导致了系统出现了不一致。如果不抛出异常,则会出现beanC引用的对象和factory最终存放的对象不是一个。

为了验证我们的结论,打断点调试上面的程序,在抛出异常的时候查看beanC里面的beanA和最终生成的beanA果然不是一个对象。

出现这个的根本原因是beanC在创建的时候,需要通过autowire设置beanA,而beanA的获取是通过singletonFactory来获取的,

        // 注册singletonFactory
		if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, new ObjectFactory<Object>() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});
		}
	/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
	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 != NULL_OBJECT ? singletonObject : null);
	}

而singletonFactory在返回对象的时候,没有做特殊的处理,返回了原始的对象。

到这里异常的原因终于查明白了。

最后的最后,给大家一个提醒,虽然Spring允许循环引用,但是不代表你的系统出现循环引用的情况是合理的,我们需要不断的审视我们代码的合理性。