spring Async 任务启动慢 spring async原理
一、@Async 注解下的循环依赖问题
我们都知道 Spring IOC
单例模式下可以帮助我们解决循环依赖问题,比如下面自己依赖自己循环依赖的场景:
从容器中获取到该 bean
执行测试方法:
可以看到正常执行,但当我们加上 @Async
注解后:
再次执行发现报错了:
是不是很奇怪,难道代理对象就会有问题吗,如果换成 @Transactional
呢:
再次执行,发现可以正常运行:
那为什么 @Async
会有问题呢,其实和我们上篇文章中讲解的 BeanPostProcessor
扩展接口有关,这里先说一下解决方法:
将依赖注入换成懒加载的方式即可:
可以看到恢复正常了,下面从源码角度分析下出现该问题的原因。
如果不了解 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
中有两个核心的扩展方法如下:
下面看挨个看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
方法的逻辑:
这里主要判断 alreadyCreated
中是否存在,如果不存在则删除单例缓存中的实例,那 alreadyCreated
什么时候放入的呢,其实在AbstractAutowireCapableBeanFactory
类的 doGetBean
方法中触发的 markBeanAsCreated
方法:
因此 removeSingletonIfCreatedForTypeCheckOnly
方法这里会返回 false
,在回到 doCreateBean
中继续看,由于 removeSingletonIfCreatedForTypeCheckOnly
返回 false
,正好符合条件被加入了 actualDependentBeans
集合中,再下面如果actualDependentBeans
集合不为空则抛出异常,这个异常是不是和之前报错的异样一样:
三、为什么 @Transactional 不会出现这种问题呢
从上面的分析可以得出结论假如 getEarlyBeanReference
可以获取到代理实例,是不是就不会发生后面的问题,这恰恰也是 @Transactional
情况下的不会出现该问题的关键点。
@Transactional
代理使用的是 InfrastructureAdvisorAutoProxyCreator
从 InfrastructureAdvisorAutoProxyCreator
的继承树可以看到,其继承了 SmartInstantiationAwareBeanPostProcessor
方法。并且在父类 AbstractAutoProxyCreator
中重写了 getEarlyBeanReference
方法:
下面可以 debug
一下 AbstractAutowireCapableBeanFactory
中的 getEarlyBeanReference
方法:
可以看到这里还是真正的实例对象,下面会进到 AbstractAutoProxyCreator
中重写的 getEarlyBeanReference
方法,最终进到当前类的 wrapIfNecessary
方法:
到这里就已经生成了一个代理类了,再回到 AbstractAutowireCapableBeanFactory
中的 getEarlyBeanReference
方法中,这时返回的就是代理类了。