spring明明解决了循环依赖,为什么还会报circular reference?
我们都知道spring应用启动时有一个bean创建的过程,在这个过程中,如果classA依赖了classB,而
classB又依赖了classA,就会有一个循环依赖的问题。
这个问题如果不处理,就会产生逻辑上的死循环。因此spring通过它的类加载机制解决了这个问题,所以我们才可以随意的使用@Autowired,而不用考虑循环依赖的问题。关于这个加载机制,有很多文章讲解。比如这篇,十分简洁明了:
理论上spring已经解决了这个问题,可很多人在实际的项目中,仍会碰到这样的报错:
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘classA’: Bean with name ‘classB’ has been injected into other beans [classB] 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.
机翻一下:
org.springframework.beans.factory.BeanCurrentlyIncrementationException:创建名为“aService”的bean时出错:名为“aService”的bean已作为循环引用的一部分注入其原始版本中的其他bean[bService],但最终已被包装。这意味着其他bean不使用bean的最终版本。这通常是过度渴望类型匹配的结果-例如,考虑在关闭“allowEagerInit”标志的情况下使用“getBeanNamesOfType”。
难道spring对于循环依赖的解决有bug?其实这是由于@Async注解引起的。
我们点开异常堆栈,源码是这样的:
protected Object doCreateBean( ... ){
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...
// populateBean这一句特别的关键,它需要给A的属性赋值,所以此处会去实例化B~~
// 而B我们从上可以看到它就是个普通的Bean(并不需要创建代理对象),实例化完成之后,继续给他的属性A赋值,而此时它会去拿到A的早期引用
// 也就在此处在给B的属性a赋值的时候,会执行到上面放进去的Bean A流程中的getEarlyBeanReference()方法 从而拿到A的早期引用~~
// 执行A的getEarlyBeanReference()方法的时候,会执行自动代理创建器,但是由于A没有标注事务,所以最终不会创建代理,so B合格属性引用会是A的**原始对象**
// 需要注意的是:@Async的代理对象不是在getEarlyBeanReference()中创建的,是在postProcessAfterInitialization创建的代理
// 从这我们也可以看出@Async的代理它默认并不支持你去循环引用,因为它并没有把代理对象的早期引用提供出来~~~(注意这点和自动代理创建器的区别~)
// 结论:此处给A的依赖属性字段B赋值为了B的实例(因为B不需要创建代理,所以就是原始对象)
// 而此处实例B里面依赖的A注入的仍旧为Bean A的普通实例对象(注意 是原始对象非代理对象) 注:此时exposedObject也依旧为原始对象
populateBean(beanName, mbd, instanceWrapper);
// 标注有@Async的Bean的代理对象在此处会被生成~~~ 参照类:AsyncAnnotationBeanPostProcessor
// 所以此句执行完成后 exposedObject就会是个代理对象而非原始对象了
exposedObject = initializeBean(beanName, exposedObject, mbd);
...
// 这里是报错的重点~~~
if (earlySingletonExposure) {
// 上面说了A被B循环依赖进去了,所以此时A是被放进了二级缓存的,所以此处earlySingletonReference 是A的原始对象的引用
// (这也就解释了为何我说:如果A没有被循环依赖,是不会报错不会有问题的 因为若没有循环依赖earlySingletonReference =null后面就直接return了)
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 上面分析了exposedObject 是被@Aysnc代理过的对象, 而bean是原始对象 所以此处不相等 走else逻辑
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// allowRawInjectionDespiteWrapping 标注是否允许此Bean的原始类型被注入到其它Bean里面,即使自己最终会被包装(代理)
// 默认是false表示不允许,如果改为true表示允许,就不会报错啦。这是我们后面讲的决方案的其中一个方案~~~
// 另外dependentBeanMap记录着每个Bean它所依赖的Bean的Map~~~~
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 我们的Bean A依赖于B,so此处值为["b"]
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
// 对所有的依赖进行一一检查~ 比如此处B就会有问题
// “b”它经过removeSingletonIfCreatedForTypeCheckOnly最终返返回false 因为alreadyCreated里面已经有它了表示B已经完全创建完成了~~~
// 而b都完成了,所以属性a也赋值完成儿聊 但是B里面引用的a和主流程我这个A竟然不相等,那肯定就有问题(说明不是最终的)~~~
// so最终会被加入到actualDependentBeans里面去,表示A真正的依赖~~~
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.");
}
}
}
}
...
}
我们知道spring为了循环依赖,采用了三级缓存机制。
因而在创建bean的时候,如果ClassA和ClassB互相依赖,在初始化ClassA的时候,会先生成它的一个不完整的earlyBeanReference;
随后,注入ClassA的属性classB,因为classB又依赖了classA,所以在初始化ClassA的成员属性ClassB的时候,又会去缓存中寻找ClassA,这就找到了ClassA的不完整对象,并将此bean注入给了ClassB。
这样,对ClassA依赖的成员变量ClassB就初始化完毕了;然后继续对ClassA进行一系列的初始化操作。并最终通过AsyncAnnotationBeanPostProcessor生成了ClassA的代理。
这时候问题出现了:classA已经被注入到ClassB中了,而且是一个没有被代理的原始对象,这就可能会出问题。因此spring的自检机制发现之后,抛出了这个异常:
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘classA’: Bean with name ‘classB’ has been injected into other beans [classB] 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.
解决
最好的解决方式就是,在所有与包含了@Async注解的bean循环依赖的地方,加个@Lazy注解。这样就避免了在加载类时注入未被代理的原始对象。从而避免报错。
参考
https://segmentfault.com/a/1190000021217176