这个以前分析过了,但是再看的时候感觉写的太乱了(流水账),这个是精简版本。
bug复现
先上bug,众所周知,spring通过实例化和属性注入分开解决了循环依赖,理论上也不会有问题。但是意外就那么来了,经过排查是@Async
导致,报错如下所示。
java.lang.IllegalStateException: Failed to load ApplicationContextat org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)at org.junit.runners.ParentRunner.run(ParentRunner.java:413)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)at org.junit.runner.JUnitCore.run(JUnitCore.java:137)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testA': Bean with name 'testA' has been injected into other beans [testB] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:631)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:769)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:761)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426)at org.springframework.boot.SpringApplication.run(SpringApplication.java:326)at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123)at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)... 25 more
源码分析
引发原因
简单的说:BeanA
为了解决循环依赖,实例化后注入给了BeanB
,BeanB
实例化,属性注入完后,轮到BeanA
属性注入,注入完后的引用和注入给BeanB
的引用不一致了,封装了一层代理,spring就报错了
Aop为没有
不走循环依赖的Aop分析
抛开源码不看,async如果有问题,理论上aop也有问题,都是返回代理,你说对吧。但是,如果aop没有问题,aop对循环依赖做了特殊处理,这里,先分析下正常aop代理,懂哥可以跳过
如果没有循环依赖,那么也是通过AbstractAutoProxyCreator::applyBeanPostProcessorsAfterInitialization
来创建的
走依循环依赖的Aop分析
如果遇到依赖循环,那么就会在实例化的时候就创建代理了,这里还有一个重要的就是,他会把依赖循环中创建代理的bean给缓存起来,图中已经标注了,后续执行到applyBeanPostProcessorsAfterInitialization
的时候也不会再创建一遍代理,而是跳过
再回到最上层,简单的分析下
- 属性注入完成
- 引用地址是一样的
- 代理对象引用在缓存中
通过一个简单的if判断,换下指针,就可以给spring容器交差了。
至此aop分析完成
@Async分析
翻阅了AsyncAnnotationBeanPostProcessor
没有实现SmartInstantiationAwareBeanPostProcessor
创建代理对象都是在applyBeanPostProcessorsAfterInitialization
中创建的
总结
原理总结
AsyncAnnotationBeanPostProcessor
未做特殊处理
解决方案
网上解决方案很多
- @Lazy
- 不使用@Async,自己用线程池实现
- 解决循环依赖