SpringBoot中使⽤@Async注解导致循环依赖的原因及解决⽅案
前⾔
在写这篇⽂章之前,我写了⼀篇关于循环依赖的⽂章,为什么这篇⽂章我⼜说和循环依赖有关的话题呢,其实,是因为我原本想写⼀篇关于@Async 原理分析的⽂章的,后来为了能更深⼊理解 @Async 以便我接下来的写的⽂章,⽆意之间看到了 @Async 也会导致循环依赖的问题。
关于循环依赖怎么解决以及源码分析可以看我这⼀篇⽂章:
Spring 是允许循环依赖的,换句话说,Spring ⾃⾝是已经解决了循环依赖这个问题,但是在这⾥竟然⼜出现了。⽐如以下添加 @Async 注解的代码:
执⾏完会报以下的错误:
在我们分析产⽣循环依赖的原因以及解决的⽅法之前,我们看⼀下 @Async 是什么。
@Async 是什么
@Async注解是Spring为我们提供的异步调⽤的注解,@Async可以作⽤到类或者⽅法上,标记了@Async注解的⽅法将会在独⽴的线程中被执⾏,调⽤者⽆需等待它的完成,即可继续其他的操作。
原因分析
下⾯我们根据报错,定位⼀下异常错误的地⽅:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
...
boolean earlySingletonExposure =(mbd.isSingleton()&& this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if(earlySingletonExposure){
addSingletonFactory(beanName,()->getEarlyBeanReference(beanName, mbd, bean));
}
...
//属性装配,属性赋值的时候,如果有发现属性引⽤了另外⼀个 bean,则调⽤ getBean ⽅法
populateBean(beanName, mbd, instanceWrapper);
//标注有 @Async 的 bean 的代理对象在此处会被⽣成, 重点的类:AsyncAnnotationBeanPostProcessor:它是⼀个后置处理器
//所以此句执⾏完成后 exposedObject 就会是个代理对象⽽⾮原始对象了
exposedObject =initializeBean(beanName, exposedObject, mbd);
...
//这⾥是报错的重点~~~
//如果 bean 允许被早期暴露,进⼊代码
if(earlySingletonExposure){
//这⾥主要把实例放⼊⼆级缓存中
Object earlySingletonReference =getSingleton(beanName, false);
if(earlySingletonReference != null){
//上⾯分析了exposedObject 是被 @Aysnc 代理过的对象,⽽ bean 是原始对象所以此处不相等,跳到 else 逻辑
if(exposedObject == bean){
exposedObject = earlySingletonReference;
}
//allowRawInjectionDespiteWrapping 标注是否允许此 bean 的原始类型被注⼊到其它 bean ⾥⾯,即使⾃⼰最终会被包装(代理)
//默认是 false 表⽰不允许,如果改为 true 表⽰允许,就不会报错了
//另外 hasDependentBean 记录着每个 bean 它所依赖的 bean 的 Map
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 ["+
"] 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 注⼊时机
我们从源码的⾓度来看⼀下被 @Async 标记的 bean 是如何注⼊到 Spring 容器⾥的。在我们开启 @EnableAsync 注解之后代表可以向Spring 容器中注⼊ AsyncAnnotationBeanPostProcessor,它是⼀个后置处理器,我们看⼀下他的类图:
AsyncAnnotationBeanPostProcessor 它是⼀个 BeanPostProcessor,实现了 postProcessAfterInitialization ⽅法。此处我们看源码,建⽴代理的动做在抽象⽗类 AbstractAdvisingBeanPostProcessor 上(以下代码会删减,只保留核⼼逻辑代码):
// 这个 map ⽤来缓存所有被 postProcessAfterInitialization 这个⽅法处理的 bean
private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);
// 这个⽅法主要是为打了@Async 注解的 bean ⽣成代理对象
@Override
public Object postProcessAfterInitialization(Object bean, String beanName){
// 这⾥是重点,这⾥返回true
if(isEligible(bean, beanName)){
//copy属性 pyFrom(this); ⼯⼚模式⽣成⼀个新的 ProxyFactory
ProxyFactory proxyFactory =prepareProxyFactory(bean, beanName);
//如果没有强制采⽤CGLIB 去探测它的接⼝
if(!proxyFactory.isProxyTargetClass()){
Class(), proxyFactory);
}
//切⼊切⾯并创建⼀个getProxy 代理对象
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
Proxy(getProxyClassLoader());
}
// No proxy needed.
return bean;
}
protected boolean isEligible(Class<?> targetClass){
//⾸次从 eligibleBeans 这个 map 中获取值肯定为 null
Boolean eligible = (targetClass);
if(eligible != null){
return eligible;
}
//如果根本就没有配置 advisor,也就是切⾯,直接返回 false
spring ioc注解if(this.advisor == null){
return false;
}
/
/最关键的就是 canApply 这个⽅法,这⾥判断 AsyncAnnotationAdvisor 能否切⼊,如果 AsyncAnnotationAdvisor 能切⼊它,那这⾥最终返回 true //本例中⽅法标注有 @Aysnc 注解,所以肯定是可以被切⼊的。
eligible = AopUtils.canApply(this.advisor, targetClass);
this.eligibleBeans.put(targetClass, eligible);
return eligible;
}
⾄此为⽌,标志了 @Aysnc 注解的 bean 就创建完成了,最终是⽣成了⼀个代理对象。
从上⾯源码中,我们知道标志 @Aysnc 注解的 bean 最后⽣成了⼀个代理对象,下⾯我们分析⼀下这次的问题:
A 开始初始化,A 实例化完成后给 A 的依赖属性
B 进⾏赋值
B 开始初始化,B 实例化完成后给 B 的依赖属性 A 进⾏赋值
因为 A 是⽀持循环依赖的,所以可以在 earlySingletonObjects 中可以拿到 A 的早期引⽤的,但是因为 B 标志了 @Aysnc 注解并不能在earlySingletonObjects 中可以拿到早期引⽤
此时 B 完成初始化,完成属性的赋值,此时属性 field 持有的是 bean A 原始类型的引⽤
接下来执⾏执⾏ initializeBean(Object existingBean, String beanName) ⽅法,这⾥ A 可以正常实例化完成,但是因为 B 标志了
@Aysnc 注解,所以向 Spring IOC 容器中增加了⼀个代理对象,也就是说 A 的 B 并不是⼀个原始对象,⽽是⼀个代理对象。
解决 @Aysnc 导致循环依赖的⽅法
加上 @Lazy 注解
代码如下:
执⾏的结果:
那为什么加上 @Lazy 之后就能正常访问了呢?
⽐如我们写了以下的代码:
public class A {
private B b;
@Async
public A(@Lazy B b){
this.b = b;
}
}
这⾥假设 A 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是⼀个被 @Lazy 标记过的属性。那么就不会去直接加载 B,⽽是产⽣⼀个代理对象注⼊到了 A 中,这样 A 就能正常的初始化完成放⼊⼀级缓存了。当然,B 加载时,再去注⼊ A 就能直接从⼀级缓存中获取到 A,这样 B 也能正常初始化完成了。所以,循环依赖的问题就能解决了。
最后,到此为⽌,我们就知道为什么会发⽣循环依赖这种问题了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论