前言
在写这篇文章之前,我写了一篇关于循环依赖的文章,为什么这篇文章我又说和循环依赖有关的话题呢,其实,是因为我原本想写一篇关于 @Async 原理分析的文章的,后来为了能更深入理解 @Async 以便我接下来的写的文章,无意之间看到了 @Async 也会导致循环依赖的问题。
关于循环依赖怎么解决以及源码分析可以看我这一篇文章:Spring 源码分析如何解决喜欢依赖的问题
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 [" +
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 标记的 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属性 proxyFactory.copyFrom(this); 工厂模式生成一个新的 ProxyFactory
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
//如果没有强制采用CGLIB 去探测它的接口
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
//切入切面并创建一个getProxy 代理对象
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}
// No proxy needed.
return bean;
}
protected boolean isEligible(Class<?> targetClass) {
//首次从 eligibleBeans 这个 map 中获取值肯定为 null
Boolean eligible = this.eligibleBeans.get(targetClass);
if (eligible != null) {
return eligible;
}
//如果根本就没有配置 advisor,也就是切面,直接返回 false
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 也能正常初始化完成了。所以,循环依赖的问题就能解决了。
最后,到此为止,我们就知道为什么会发生循环依赖这种问题了。