0


Spring 源码解析 - @Async 注解下的循环依赖问题原理

一、@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

方法中,这时返回的就是代理类了。

在这里插入图片描述

标签: spring java mybatis

本文转载自: https://blog.csdn.net/qq_43692950/article/details/129882144
版权归原作者 小毕超 所有, 如有侵权,请联系我们删除。

“Spring 源码解析 - @Async 注解下的循环依赖问题原理”的评论:

还没有评论