0%

Async的循环依赖

前言

用了Spring这么多年,以为我对循环依赖的问题已经了如指掌,但是现实还是给我了一巴掌。

最近线上服务报了错误:

Bean with name xx has been injected into other beans [ x ] 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.

Exception是BeanCurrentlyInCreationException,看起来是个循环依赖的问题,但是日志的报错是我从来没见过的。

正常的循环依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class A {
private B b;

public A(B b) {
this.b = b;
}
}


@Service
public class B {
private A a;

public B(A a) {
this.a = a;
}
}

如上,我们创建了两个Service,并且通过构造函数的方式注入。

启动时,Spring会报错:

Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?

并且还会贴心的会你画个图:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
| a defined in file [A.class]
↑ ↓
| b defined in file [B.class]
└─────┘

这种也是我理解中的循环依赖问题,解决方案也是比较简单,我们换成set方法注入,或者成员变量注入就行。

复现上文的异常

上文的异常,我一开始以为是AOP导致的问题,于是在A上加了个Aop的方法,发现Aop能够很好的解决这种循环依赖。

解决方案是三级缓存:

参考文章: https://segmentfault.com/a/1190000039134606

那我就疑惑了,到底是什么情况导致的呢?

后来搜到了文章:https://segmentfault.com/a/1190000018835760

发现@Async可以复现,于是我修改了代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
public class A {
private B b;

@Autowired
public void setB(B b) {
this.b = b;
}

@Async
public void doAsync() {
System.out.println("hello");
}
}

@Service
public class B {
private A a;

@Autowired
public void setA(A a) {
this.a = a;
}
}

发现确实可以复现了,这就暴露出我的知识盲区了,难道@Async不是通过Aop去解决的吗?

为什么@Aspect注解切的方法,不会报循环依赖,但是@Async的方法会呢?

带着疑惑,我又尝试了@Transectional会不会导致循环依赖问题,发现并不会。

所以,为啥@Async如此特殊呢?

对比

于是我得对比,@Async和@Transectional在实现上的区别

为此,我们把A的方法声明称这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class A {
private B b;

@Autowired
public void setB(B b) {
this.b = b;
}

@Transactional
@Async
public void doAsync() {
System.out.println("hello");
}
}

同时配上一个手写的Aspect:

1
2
3
4
5
6
7
8
9
@Aspect
@Component
public class AspectA {

@Before("execution(public A.doAsync())")
public void beforeA() {
System.out.println("beforeA 执行");
}
}

在DefaultAdvisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法中

我们可以查到这个方法所有的Advisor。

可以看到一共有三个

  1. ExposeInvocationInterceptor:先不管,和我们的业务无关
  2. BeanFactoryTransactionAttributeSourceAdvisor:就是我们@Transactional的相关Aop
  3. AspectA:自定义的Aop

可以发现,并没有@Async相关的Aop代码。

而通过对DefaultAdvisorChainFactory的调用链,可以分析到,这里的执行,对原来类的Aop织入,其实在

1
2
doCreateBean#
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

的逻辑中已经生成

所以也就导致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
//这里拿到的exposedObject和Bean其实是一样的,不会抛出异常。
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
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 " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

那为什么加了@Async就会不一样呢?

通过上文,我们知道了@Async的逻辑和传统的Aop逻辑不太一样。

@Async是通过AsyncAnnotationBeanPostProcessor去织入自己的逻辑。

这个BeanPostProcessor在applyBeanPostProcessorsAfterInitialization方法中被调用

导致生成的是一个新的代理Bean,和原来的Bean就会不一样,也就是走到了抛出异常的逻辑。

总结

还是不能想当然,Aop是一个广泛的概念,但是在Spring中,其实还是冗余了不同的实现。

并不是所有加了Annotation的实现,都是一样的。

有可能是Aspect的实现,也有可能是类似BeanPostProcessor的实现。

需要看源码具体原因具体分析。