所谓循环引用是指,多个类的相互依赖最终形成了一个依赖环。比如下面的这种情况,
@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。
在多数情况下产生循环依赖,都代表了系统的设计出现了问题。根据分层原理,类之间往往是有上层类和底层类之分的。循环依赖的产生往往是下面的两种情况,
- 底层类引用了上层类,这种情况下部分功能应该下沉。
- 同级别类之间相互引用,这种情况下代表了功能模块划分不清,导致强耦合,应该解耦。
但是万事都不是绝对的,有些情况下的循环引用也是可以接受的。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.");
}
}
}
}
触发异常需要满足几个条件:
- earlySingletonExposure == true
- earlySingletonReference != null
- exposedObject != bean
- actualDependentBeans不为空
我们依次来解释下上面的几个变量代表了什么
- earlySingletonExposure 允许提前暴露单例,也就是说该对象虽然还没有初始完成,但是其他对象可以提前引用该对象。允许循环引用时该值才可能为true。
- earlySingletonReference
被提前引用的对象,也就是同名的已经被引用的对象。 - exposedObject
调用init函数,以及pre,post processor生成的最终对象。 - bean 调用init函数,以及pre,post processor之前的创建的对象。也就是通过反射生成的最初的对象。
- 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允许循环引用,但是不代表你的系统出现循环引用的情况是合理的,我们需要不断的审视我们代码的合理性。