一、@Async 注解下的循环依赖问题
我们都知道
Spring IOC
单例模式下可以帮助我们解决循环依赖问题,比如下面自己依赖自己循环依赖的场景:
@ComponentpublicclassTestAsync{@ResourceTestAsync async;publicvoidtest(){System.out.println("test....");}}
从容器中获取到该
bean
执行测试方法:
publicclassApp{publicstaticvoidmain(String[] args){ApplicationContext context =newAnnotationConfigApplicationContext("com.demo.test");TestAsync testAsync = context.getBean("testAsync",TestAsync.class);
testAsync.test();}}
可以看到正常执行,但当我们加上
@Async
注解后:
@Component@EnableAsyncpublicclassTestAsync{@ResourceTestAsync async;@Asyncpublicvoidtest(){System.out.println("test....");}}
再次执行发现报错了:
是不是很奇怪,难道代理对象就会有问题吗,如果换成
@Transactional
呢:
@Component@EnableTransactionManagementpublicclassTestAsync{@ResourceTestAsync async;@Transactionalpublicvoidtest(){System.out.println("test....");}}
再次执行,发现可以正常运行:
那为什么
@Async
会有问题呢,其实和我们上篇文章中讲解的
BeanPostProcessor
扩展接口有关,这里先说一下解决方法:
将依赖注入换成懒加载的方式即可:
@Component@EnableAsyncpublicclassTestAsync{@Resource@LazyTestAsync async;@Asyncpublicvoidtest(){System.out.println("test...."+Thread.currentThread().getName());}}
可以看到恢复正常了,下面从源码角度分析下出现该问题的原因。
如果不了解
BeanPostProcessor
扩展接口,可以先看下下面这篇文章:
Spring 源码解析 - BeanPostProcessor 扩展接口
二、源码分析
2.1 @EnableAsync
当使用
@EnableAsync
开启异步支持时,会向
Spring
容器中注入
AsyncConfigurationSelector.class
类:
在该类中,
selectImports
下根据
adviceMode
选择注入配置类,
adviceMode
默认为
PROXY
,会注入
ProxyAsyncConfiguration.class
配置类:
在
ProxyAsyncConfiguration.class
配置类下,注入了一个
AsyncAnnotationBeanPostProcessor
扩展类:
下面看下
AsyncAnnotationBeanPostProcessor
扩展类的继承树:
AsyncAnnotationBeanPostProcessor
从
BeanPostProcessor
获得
bean
初始化前后的扩展能力,从
ProxyProcessorSupport
获取代理能力。
这里重点看
BeanPostProcessor
扩展,在
BeanPostProcessor
中有两个核心的扩展方法如下:
publicinterfaceBeanPostProcessor{/**
* 实例化及依赖注入完成后、bean 初始化方法触发之前执行
*/ObjectpostProcessBeforeInitialization(Object bean,String beanName)throwsBeansException;/**
* bean 初始化方法触发后执行
*/ObjectpostProcessAfterInitialization(Object bean,String beanName)throwsBeansException;}
下面看挨个看
AsyncAnnotationBeanPostProcessor
中这两个方法干了什么:
在
AsyncAnnotationBeanPostProcessor
没有直接实现
postProcessBeforeInitialization
,实现在
AbstractAdvisingBeanPostProcessor
下的
postProcessBeforeInitialization
方法:
没有做任何操作,直接返回的原
bean
,同样
postProcessAfterInitialization
方法也在
AbstractAdvisingBeanPostProcessor
下:
在这里实际生成了代理
bean
进行返回。
到这我们需要记住
AsyncAnnotationBeanPostProcessor
的
postProcessBeforeInitialization
前通知没有做任何操作,
postProcessAfterInitialization
后通知创建了代理实例。
2.2 getEarlyBeanReference
了解过循环依赖的应该知道
Spring
中使用三级缓存来解决循环依赖问题,其中实例化
bean
后,会首先曝光至第三级缓存中,该逻辑在
AbstractAutowireCapableBeanFactory
类的
doCreateBean
方法下:
在
doCreateBean
方法下的
populateBean
则是进行了依赖的注入:
在进行依赖注入时,会递归尝试从三级缓存中获取
bean
,由于这里是循环依赖,已经放入了第三级缓存中,因此可以命中,这快的源码逻辑在
DefaultSingletonBeanRegistry
类下的
getSingleton
方法:
这里可以看出三级缓存命中会执行
ObjectFactory.getObject()
方法获取一个早期的实例,获取之后存入二级缓存中,从前面放入三级缓存可以看出其实是触发的
AbstractAutowireCapableBeanFactory
下的
getEarlyBeanReference
方法:
这里会判断是否存在
SmartInstantiationAwareBeanPostProcessor
类型的
BeanPostProcessor
扩展,然后尝试使用
getEarlyBeanReference
获取一个早期的实例,从前面
AsyncAnnotationBeanPostProcessor
的继承树可以看出并没有实现
SmartInstantiationAwareBeanPostProcessor
因此这里拿到的就是原来的
bean
实例:
到这里我们需要记住,
Spring
单例缓存中存储的是
TestAsync
真正的实例对象。
2.3 initializeBean
在依赖注入后会触发
initializeBean
方法,进行
bean
的初始化操作:
在
initializeBean
方法中,执行初始化前,先执行
BeanPostProcessors
的前置方法,并且将前置方法返回的
bean
代替原先创建的
bean
,在
bean
初始化后执行
BeanPostProcessors
的后置方法,并将后置方法返回的
bean
代替原先的
bean
,从上面对
AsyncAnnotationBeanPostProcessor
的简单分析得出,前置方法没做任何处理,后置方法会生成一个代理对象,因此
initializeBean
方法最终返回的是代理对象:
可以看出最后生成的是一个代理对象,现在应该就会发现一个问题了,在
Spring
单例容器中和依赖注入中的都是
TestAsync
真正的实例,而这里返回的是代理实例,现在相当于单例的
bean
存在了两个不同的实例。
2.4 判断是否出现重复实例
在回到
doCreateBean
方法下
initializeBean
执行后:
如果是单例的话,则尝试从容器中获取当前的
beanName
实例,由于前面已经曝光到了二级缓存中,因此这里可以获取到,但容器中的
bean
实例和当前的
bean
实例已经不是一个实例了,因此会进入到
else if
中,这里获取到该
beanName
所有的依赖,通过
removeSingletonIfCreatedForTypeCheckOnly
删除已经创建好的
bean
实例,因为单例模式下
Spring
仅允许有一个实例,先创建的要被后创建的覆盖,但这个覆盖的前提是实例已经完全创建成功,这里可以看下
removeSingletonIfCreatedForTypeCheckOnly
方法的逻辑:
由于前面
TestAsync
存在循环依赖,仅放入二级缓存中,并没有实例化完成,因此这里会返回
false
,在回到
doCreateBean
中继续看,由于
removeSingletonIfCreatedForTypeCheckOnly
返回
false
,正好符合条件被加入了
actualDependentBeans
集合中,再下面如果
actualDependentBeans
集合不为空则抛出异常,这个异常是不是和之前报错的异样一样:
三、为什么 @Transactional 不会出现这种问题呢
从上面的分析可以得出结论假如
getEarlyBeanReference
可以获取到代理实例,是不是就不会发生后面的问题,这恰恰也是
@Transactional
情况下的不会出现该问题的关键点。
@Transactional
代理使用的是
InfrastructureAdvisorAutoProxyCreator
从
InfrastructureAdvisorAutoProxyCreator
的继承树可以看到,其继承了
SmartInstantiationAwareBeanPostProcessor
方法。并且在父类
AbstractAutoProxyCreator
中重写了
getEarlyBeanReference
方法:
下面可以
debug
一下
AbstractAutowireCapableBeanFactory
中的
getEarlyBeanReference
方法:
可以看到这里还是真正的实例对象,下面会进到
AbstractAutoProxyCreator
中重写的
getEarlyBeanReference
方法,最终进到当前类的
wrapIfNecessary
方法:
到这里就已经生成了一个代理类了,再回到
AbstractAutowireCapableBeanFactory
中的
getEarlyBeanReference
方法中,这时返回的就是代理类了。
版权归原作者 小毕超 所有, 如有侵权,请联系我们删除。