【日期】:2022/01/07

【问题】: 早上更新了下代码预备调试个接口却发现项目发动报了一个古怪的错误,如下图

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
Error creating bean with name ‘a’: Bean with name ‘a’ has been injected into other beans [c] 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

【寻觅原因】: 从报错来看,大致意思是bean【a】和bean【c】存在循环依靠,bean【a】终究被包装,其他依靠的c并不是最后c的最后版别。 看了下项目里,类A,B,C的依靠关系为A -> B -> C -> A,的确存在间接循环依靠,但三个类里 都运用的特点注入方式,也就是set注入,在spring三级缓存的加持下应该会主动处理才对。 所以按照提示点击报错方位进入AbstractAutowireCapableBeanFactory中,报错方位如下

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误

往上找,到createBean办法里,打上条件断点

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误

以debug形式发动项目,进入断点

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
往下履行,跳过前面类解析环节,到doCreateBean办法

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
进入doCreateBean办法,往下履行,到这个地方

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
可以看到现已将a的前期引证加入三级缓存傍边,当时bean a为一般目标,为了便于调查,将exposedObject加入watch中,接下来进入bean填充,populateBean办法中, 通过内部的后置处理器填充特点依靠,由于运用的是@Resource注解,此处是由CommonAnnotationBeanPostProcessor处理a中注入的b特点依靠

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误

开端进行bean 【b】的创立

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
流程和a相同,b中的c也是通过CommonAnnotationBeanPostProcessor处理b中注入的c特点依靠

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
随后履行c的创立流程,由于a现已存在于三级缓存傍边,所以c依靠的是a的前期依靠,由于没有其他依靠,c履行完整个创立周期,创立完结之后注入到b中继续b的创立,b履行完创立后回到a中,进行后边的流程

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
直接跳到下一步

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
很显着能看出a露出的目标从原始目标变成了署理目标,再往后履行

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
这里会判别终究露出的目标和前期目标是否是同一个,假如持平,则流程履行结束,此处显着不持平,所以进入后边的else句子。后边的代码中 hasDependentBean办法是判别是否被其他目标依靠,会有个map来记载a的一切引证目标,allowRawInjectionDespiteWrapping大约意思是是否答应引证原始目标,假如条件满意,进入后边的代码, removeSingletonIfCreatedForTypeCheckOnly办法作用是根据传入的bean名判别假如现已创立结束,则清其相关的一切缓存并回来true

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误

由于bean【c】的创立现已完结,所以此处回来为true,并把c加入actualDependentBeans中,最后actualDependentBeans集合肯定是不为空的,所以报错了。走完整个流程我们发现,终究报错的原因是bean 【c】依靠的目标【a】并不是a的终究版别,debug进程中也知道bean【a】的目标版别是在initializeBean中被改变,所以再次debug进入initializeBean办法,initializeBean中主要履行相关后置处理器在bean初始化前后做一些事情,包含applyBeanPostProcessorsBeforeInitialization和applyBeanPostProcessorsAfterInitialization两块,在前面的applyBeanPostProcessorsBeforeInitialization中回来的bean没有变化

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误

而在后边的applyBeanPostProcessorsAfterInitialization办法中,当履行到AsyncAnnotationBeanPostProcessor处理器时,生成了署理目标,而实践生成署理的代码是在其父类AbstractAdvisingBeanPostProcessor中, 此刻大约猜到是由于@Async异步注解引起,项目里正好敞开了@EnableAsync(@EnableAsync敞开时它会向容器内注入AsyncAnnotationBeanPostProcessor), 且a类中刚好有一个办法带有此注解。

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误

首先会判别假如已创立过署理(被事务署理等),isFrozen为false且切入点适配则新增一个切面即可,此处的切面AsyncAnnotationAdvisor完结,由于a类中存在标示了@Async的办法,所以是匹配的。当时a类还没创立过署理,走到后续创立署理流程

记一次SpringBoot项目中@Async注解使用不当导致的循环依赖错误
一个全新的a目标署理创立完结

【处理方案】

⓵ 把带有@Async注解的bean剔除循环依靠

⓶ 把allowRawInjectionDespiteWrapping设置为true

⓷ 运用@Lazy或@ComponentScan(lazyInit = true)处理

【总结】

此处运用的SpringBoot版别为2.2.5, 其他版别待验证

假如bean存在以下关系并且A中含有标示@Async注解的办法:

  • A,B两bean直接循环依靠, 如A->B->A
  • A,B,C间接循环依靠, 如A->B->C->A

由于项目发动记载文件的次序不固定,在某些情况下

  • 先createBean(A), 此刻把A的前期引证放入三级缓存
  • populateBean(A), 开端创立A的依靠B
  • createBean(B), populateBean(B), 开端创立B的依靠C
  • createBean(C), populateBean(C), 此刻C将拿到的依靠A将是A存在三级缓存中的前期引证
  • 完结C后续的创立,回到B的进程继续B的创立, B创立完结后回到A的进程
  • 履行initializeBean(A), 在履行到applyBeanPostProcessorsAfterInitialization()阶段,循环到后置处理器AsyncAnnotationBeanPostProcessor时,由其父类对A进行类增强并生成新的署理目标露出给spring容器
  • 到最后检测阶段发现C依靠的A并不是终究版别,导致报错。