在写这篇文章之前,我写了一篇关于循环依赖的文章,为什么这篇文章我又说和循环依赖有关的话题呢,其实,是因为我原本想写一篇关于 @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); SetactualDependentBeans = 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, 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 也能正常初始化完成了。所以,循环依赖的问题就能解决了。
最后,到此为止,我们就知道为什么会发生循环依赖这种问题了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)