@Async引起循环依赖报错
@Async会通过代理,通过BeanPostProcessor扩展点增强类的功能,提供类方法异步的能力,新生成的代理类会改变原始类对象的引用。我们来看看具体的实现类AsyncAnnotationBeanPostProcessor。
好家伙,完全没有重写getEarlyBeanReference,结果报错,出处如下。
//earlySingletonReference 循环引用生成的代理类
//exposedObject 本方法生成的代理类
//bean 原始引用
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
//如果走到这里,说明触发了循环依赖
if (earlySingletonReference != null) {
//如果相等,这里是指发生了aop根据cacheKey判定到幂等
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
//走到这里说明有其他钩子(后置处理器)把bean替换了,所以要检查在此之前是否已经发生过该bean的依赖注入,如果发生,就导致一个bean的不同版本被注入,针对这种情况,会抛出异常
} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(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 " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
在doCreateBean核心方法处以前看不懂的代码也可以解释了,代理对象的引用发生变化,导致spring认为有两个不同版本的bean加入到了容器内,对于这种情况spring也无能为力,只得抛出一个错。
@Component
public class A{
@Autowired
private B b;
@Async
@Override
public void testA() {
}
}
@Component
public class ClassB{
@Autowired
private A a;
@Override
public void testB() {
a.testA();
}
}
1.context.getBean(A)开始创建A,A实例化完成后给A的依赖属性b开始赋值
2.context.getBean(B)开始创建B,B实例化完成后给B的依赖属性a开始赋值
3.重点:此时因为A支持循环依赖,所以会执行A的getEarlyBeanReference方法得到它的早期引用。而执行getEarlyBeanReference()的时候因为@Async根本还没执行,所以最终返回的仍旧是原始对象的地址
4.B完成初始化、完成属性的赋值,此时属性field持有的是Bean A原始类型的引用
5.完成了A的属性的赋值(此时已持有B的实例的引用),继续执行初始化方法initializeBean(…),在此处会解析@Aysnc注解,从而生成一个代理对象,所以最终exposedObject是一个代理对象(而非原始对象)最终加入到容器里
6.尴尬场面出现了:B引用的属性A是个原始对象,而此处准备return的实例A竟然是个代理对象,也就是说B引用的并非是最终对象(不是最终放进容器里的对象)
7.执行自检程序:由于allowRawInjectionDespiteWrapping默认值是false,表示不允许上面不一致的情况发生,so最终就抛错了
如何解决
1.使用@Lazy或者 @ComponentScan(lazyInit = true)
@Component
public class ClassB{
@Autowired
@Lazy
private A a;
@Override
public void testB() {
a.testA();
}
}
2.使用setter注入
@Component
public class ClassA{
private B b;
public void setB(B b) {
this.b = b;
}
@Async
@Override
public void testA() {
b.testb();
}
}
@Component
public class ClassB{
private A a;
public void setA(A a) {
this.a = a;
}
@Override
public void testB() {
a.testA();
}
}
3.使用@Autowired注解
@Component
public class A{
private B b;
@Autowired
public void SetB(B b) {
this.b= b;
}
@Async
@Override
public void testA() {
//TODO
}
}
4.重构方法
新建class,把@Async的方法放在新的类中,从根本上消除循环依赖