一、学习指引
Spring中的循环依赖问题,你真的彻底了解过吗?
Spring的循环依赖问题可以说是面试过程中出现的非常频繁的问题。比如,在面试过程中面试官可能会问:有了解过Spring中的循环依赖问题吗?或者会问:什么是循环依赖问题?或者会问:Spring中在哪些场景下会产生循环依赖问题?或者会问:Spring是如何解决循环依赖问题的?看似轻描淡写的一句话,实际要考察的内容也是比较多的。面试官可以通过这个问题考察面试者是否研究过Spring的源码,有没有了解过Spring中的循环依赖问题。
文末扫码加星球立减¥200,仅限2023年12月,仅限前300名,先到先得。
本章,我们就一起来聊聊Spring中的循环依赖问题。
二、循环依赖概述
什么是循环依赖?
循环依赖其实也很好理解,可以将这个词拆分成两部分,一个是循环,一个是依赖。循环,顾名思义就是指形成了一个闭合环路,也就是闭环。依赖就是指某个事件的发生要依赖另一个事件。
在Spring中的循环依赖就是指一个或者多个Bean之间存在着互相依赖的关系,并且形成了循环调用。例如,在Spring中,A依赖B,B又依赖A,A和B之间就形成了相互依赖的关系。创建A对象时,发现A对象依赖了B对象,此时先去创建B对象。创建B对象时,发现B对象又依赖了A对象,此时又去创建A对象。创建A对象时,发现A对象依赖了B对象....如果Spring不去处理这种情况,就会发生死循环,一直会创建A对象和B对象,直到抛出异常为止。
同理,在Spring中多个对象之间也有可能存在循环依赖,例如,A依赖B,B依赖C,C又依赖A,A、B、C之间形成了互相依赖的关系,这也是一种循环依赖。
三、循环依赖类型
循环依赖有这些类型呢?
循环依赖总体上可以分成:自我依赖、直接依赖和间接依赖三种类型,如图20-1所示。
图片
3.1 自我依赖
自我依赖就是自己依赖自己,从而形成的循环依赖,一般情况下不会发生这种循环依赖,如图20-2所示。
图片
3.2 直接依赖
直接依赖一般是发生在两个对象之间,例如对象A依赖对象B,对象B又依赖对象A,对象A和对象B之间形成了依赖关系,如图20-3所示。
图片
3.3 间接依赖
间接依赖一般是发生在三个或三个以上对象之间互相依赖的场景,例如对象A依赖对象B,对象B依赖对象C,对象C又依赖对象A,对象A、对象B和对象C之间就形成了循环依赖,如图20-4所示。
图片
四、循环依赖场景
Spring中有哪些循环依赖的场景?
Spring中的循环依赖场景总体上可以分成单例Bean的setter循环依赖、多例Bean的setter循环依赖、代理对象的setter循环依赖、构造方法的循环依赖和DependsOn的循环依赖。如图20-5所示。
图片
4.1 单例Bean的setter循环依赖的特殊情况
Spring是支持基于单例Bean的setter方法的循环依赖的,不过有一种特殊情况需要注意。本节,我们就一起实现基于单例Bean的setter方法的循环依赖的特殊情况,具体实现步骤如下所示。
(1)新增SpecialCircularBeanA类
SpecialCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanA。
可以看到,在SpecialCircularBeanA类上只标注了@Component注解,所以在IOC容器启动时,会在IOC容器中创建SpecialCircularBeanA类型的单例Bean,并且在SpecialCircularBeanA类型的Bean对象中,会依赖SpecialCircularBeanB类型的Bean对象。同时,在SpecialCircularBeanA类中重写了toString()方法,打印了依赖的SpecialCircularBeanB类型的对象。
(2)新增SpecialCircularBeanB类
SpecialCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanB。
可以看到,在SpecialCircularBeanB类上只标注了@Component注解,所以在IOC容器启动时,会在IOC容器中创建SpecialCircularBeanB类型的单例Bean,并且在SpecialCircularBeanB类型的Bean对象中,会依赖SpecialCircularBeanA类型的Bean对象。同时,在SpecialCircularBeanB类中重写了toString()方法,打印了依赖的SpecialCircularBeanA类型的对象。
(3)新增SpecialCircularConfig类
SpecialCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.config.SpecialCircularConfig。
可以看到,在SpecialCircularConfig类上标注了@Configuration注解,说明SpecialCircularConfig类是案例程序的配置类,并且使用@ComponentScan注解指定了扫描的包。
(4)新增SpecialCircularTest类
SpecialCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.SpecialCircularTest。
可以看到,在SpecialCircularTest类的main()方法中,传入SpecialCircularConfig类的Class对象后,创建IOC容器,随后从IOC容器中获取SpecialCircularBeanA类型的Bean对象并进行打印,最后关闭IOC容器。
(5)运行SpecialCircularTest类
运行SpecialCircularTest类的main()方法,输出的结果信息如下所示。
可以看到,程序抛出了StackOverflowError异常。
其实,从本质上讲,这个异常不是Spring抛出的,而是JVM抛出的栈溢出错误。
4.2 多例Bean的setter循环依赖
Spring是不支持基于多例Bean,也就是原型模式下的Bean的setter方法的循环依赖。本节,我们一起实现一个基于多例Bean的set方法的循环依赖案例,具体实现步骤如下所示。
(1)新增PrototypeCircularBeanA类
PrototypeCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanA。
可以看到,PrototypeCircularBeanA类的对象在Spring中是多例Bean,并且依赖了PrototypeCircularBeanB类型的Bean对象,并提供了getPrototypeCircularBeanB()方法返回PrototypeCircularBeanB类型的Bean对象。
(2)新增PrototypeCircularBeanB类
PrototypeCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanB。
可以看到,PrototypeCircularBeanB类的对象在Spring中是多例Bean,并且依赖了PrototypeCircularBeanA类型的Bean对象,并提供了getPrototypeCircularBeanA()方法返回PrototypeCircularBeanA类型的Bean对象。
(3)新增PrototypeCircularConfig类
PrototypeCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.config.PrototypeCircularConfig。
可以看到,在PrototypeCircularConfig类上标注了@Configuration注解,说明PrototypeCircularConfig类是案例程序的配置类,并且使用@ComponentScan注解指定了要扫描的包名。
(4)新增PrototypeCircularTest类
PrototypeCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.PrototypeCircularTest。
可以看到,在PrototypeCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取PrototypeCircularBeanA类型的Bean对象,并打印PrototypeCircularBeanA类型的Bean对象和依赖的PrototypeCircularBeanB类型的Bean对象。
(5)运行PrototypeCircularTest类
运行PrototypeCircularTest类的main()方法,输出的结果信息如下所示。
可以看到,输出结果中打印出了循环依赖的异常信息,说明Spring不支持多例Bean的setter方法循环依赖。
4.3 代理对象的setter循环依赖
Spring默认是不支持基于代理对象的setter方法的循环依赖,本节,就简单实现一个基于代理对象的setter方法的循环依赖案例。具体实现步骤如下所示。
(1)新增ProxyCircularBeanA类
ProxyCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanA。
可以看到,ProxyCircularBeanA类型的Bean对象会依赖ProxyCircularBeanB类型的Bean对象,并且在getProxyCircularBeanB()方法上标注了@Async注解,当调用getProxyCircularBeanB()方法时,会通过AOP自动生成代理对象。
(2)新增ProxyCircularBeanB类
ProxyCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanB。
可以看到,ProxyCircularBeanB类型的Bean对象会依赖ProxyCircularBeanA类型的Bean对象,并且在getProxyCircularBeanA()方法上标注了@Async注解,当调用getProxyCircularBeanA()方法时,会通过AOP自动生成代理对象。
(3)新增ProxyCircularConfig类
ProxyCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.config.ProxyCircularConfig。
可以看到,在ProxyCircularConfig类上标注了@Configuration注解,说明ProxyCircularConfig类是案例程序的配置类。并且在ProxyCircularConfig类上使用@ComponentScan注解指定了要扫描的包名。同时,使用@EnableAsync注解开启了异步调用。
(4)新增ProxyCircularTest类
ProxyCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.ProxyCircularTest。
可以看到,在ProxyCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取ProxyCircularBeanA类型的Bean对象,并打印ProxyCircularBeanA类型的Bean对象和依赖的ProxyCircularBeanB类型的Bean对象。
(5)运行ProxyCircularTest类
运行ProxyCircularTest类的main()方法,输出的结果信息如下所示。
从输出的结果信息可以看出,Spring抛出了循环依赖的异常。说明Spring默认不支持基于代理对象的setter方法的循环依赖。
4.4 构造方法的循环依赖
Spring不支持基于构造方法的循环依赖。本节,就简单实现一个基于构造方法的循环依赖,具体实现步骤如下所示。
(1)新增ConstructCircularBeanA类
ConstructCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanA。
可以看到,在ConstructCircularBeanA类中通过构造方法依赖了ConstructCircularBeanB类型的Bean对象,并提供了getConstructCircularBeanB()方法来获取ConstructCircularBeanB类型的Bean对象。
(2)新增ConstructCircularBeanB类
ConstructCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanB。
可以看到,在ConstructCircularBeanB类中通过构造方法依赖了ConstructCircularBeanA类型的Bean对象,并提供了getConstructCircularBeanA()方法来获取ConstructCircularBeanA类型的Bean对象。
(3)新增ConstructCircularConfig类
ConstructCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.config.ConstructCircularConfig。
可以看到,在ConstructCircularConfig类上标注了@Configuration注解,说明ConstructCircularConfig类是案例程序的配置类,并且使用@ComponentScan注解指定了要扫描的包名。
(4)新增ConstructCircularTest类
ConstructCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.ConstructCircularTest。
可以看到,在ConstructCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取ConstructCircularBeanA类型的Bean对象,并打印ConstructCircularBeanA类型的Bean对象和依赖的ConstructCircularBeanB类型的Bean对象。
(5)运行ConstructCircularTest类
运行ConstructCircularTest类的main()方法,输出的结果信息如下所示。
可以看到,Spring抛出了循环依赖的异常,说明Spring不支持基于构造方法的循环依赖。
4.5 @DependsOn的循环依赖
@DependsOn注解主要用于指定Bean的实例化顺序,Spring默认是不支持基于@DependsOn注解的循环依赖。本节,就实现基于@DependsOn注解的循环依赖。具体实现步骤如下所示。
(1)新增DependsOnCircularBeanA类
DependsOnCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanA。
可以看到,在DependsOnCircularBeanA类上不仅标注了@Component注解,也标注了@DependsOn注解指定依赖dependsOnCircularBeanB,并且在DependsOnCircularBeanA类中使用@Autowired注解注入了DependsOnCircularBeanB类型的Bean对象。同时,在DependsOnCircularBeanA类中提供了getDependsOnCircularBeanB()方法获取DependsOnCircularBeanB类型的Bean对象。
(2)新增DependsOnCircularBeanB类
DependsOnCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanB。
可以看到,在DependsOnCircularBeanB类上不仅标注了@Component注解,也标注了@DependsOn注解指定依赖dependsOnCircularBeanA,并且在DependsOnCircularBeanB类中使用@Autowired注解注入了DependsOnCircularBeanA类型的Bean对象。同时,在DependsOnCircularBeanB类中提供了getDependsOnCircularBeanA()方法获取DependsOnCircularBeanA类型的Bean对象。
(3)新增DependsOnCircularConfig类
DependsOnCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.config.DependsOnCircularConfig。
可以看到,在DependsOnCircularConfig类上标注了@Configuration注解,说明DependsOnCircularConfig类是案例程序的配置类。同时,使用@ComponentScan注解指定了要扫描的包名。
(4)新增DependsOnCircularTest类
DependsOnCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.DependsOnCircularTest。
可以看到,在DependsOnCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取DependsOnCircularBeanA类型的Bean对象,并打印DependsOnCircularBeanA类型的Bean对象和依赖的DependsOnCircularBeanB类型的Bean对象。
(5)运行DependsOnCircularTest类
运行DependsOnCircularTest类的main()方法,输出的结果信息如下所示。
从输出的结果信息可以看出,Spring抛出了循环依赖的异常。说明Spring不支持@DependsOn注解的循环依赖。
4.6 单例Bean的setter循环依赖
Spring支持基于单例Bean的setter方法的循环依赖。本节,就实现基于单例Bean的setter方法的循环依赖案例。具体实现步骤如下所示。
(1)新增SingletonCircularBeanA类
SingletonCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA。
可以看到,在SingletonCircularBeanA类中依赖了SingletonCircularBeanB类型的Bean对象,并提供了getSingletonCircularBeanB()方法获取SingletonCircularBeanB类型的Bean对象。
(2)新增SingletonCircularBeanB类
SingletonCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB。
可以看到,在SingletonCircularBeanB类中依赖了SingletonCircularBeanA类型的Bean对象,并提供了getSingletonCircularBeanA()方法获取SingletonCircularBeanA类型的Bean对象。
(3)新增SingletonCircularConfig类
SingletonCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.config.SingletonCircularConfig。
可以看到,在SingletonCircularConfig类上标注了@Configuration注解,说明SingletonCircularConfig类是案例程序的配置类,并使用@ComponentScan注解指定了要扫描的包名。
(4)新增SingletonCircularTest类
SingletonCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.SingletonCircularTest。
可以看到,在SingletonCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取SingletonCircularBeanA类型的Bean对象,并打印SingletonCircularBeanA类型的Bean对象和依赖的SingletonCircularBeanB类型的Bean对象。
(5)运行SingletonCircularTest类
运行SingletonCircularTest类的main()方法,输出的结果信息如下所示。
可以看到,正确输出了SingletonCircularBeanA类型的Bean对象和SingletonCircularBeanB类型的Bean对象。**说明Spring支持基于单例Bean的setter方法的循环依赖。
五、Spring循环依赖底层解决方案分析
Spring底层是如何解决循环依赖问题的?
由循环依赖场景的分析得知:Spring除了默认支持基于单例Bean的setter方法的循环依赖外,默认均不支持其他情况下的循环依赖。但是,如果是通过标注@Async注解生成的代理对象,则可以通过将标注了@Async注解的类排到后面加载的IOC容器中即可解决循环依赖的问题。
5.1 不支持单例Bean的setter循环依赖的特殊情况
运行4.1节中SpecialCircularTest类的main()方法,输出的结果信息如下所示。
可以看到,实际抛出的是StackOverflowError错误。这个错误信息本质上不是Spring抛出的,而是JVM抛出的。根本原因其实是出在SpecialCircularBeanA和SpecialCircularBeanB两个类的toString()方法上。
当在SpecialCircularTest类的main()方法中打印specialCircularBeanA时,默认会调用SpecialCircularBeanA类的toString()方法,在SpecialCircularBeanA类的toString()方法中,会拼接specialCircularBeanB对象,此时又会调用SpecialCircularBeanB类的toString()方法。而在SpecialCircularBeanB类的toString()方法中,又会拼接specialCircularBeanA对象,此时又会调用SpecialCircularBeanA类的toString()方法。在SpecialCircularBeanA类的toString()方法中,又会拼接specialCircularBeanB对象,继而调用SpecialCircularBeanB类的toString()方法....如此反复,造成了死循环。
简单点说,就是在SpecialCircularBeanA类的toString()方法中调用了SpecialCircularBeanB类的toString()方法,在SpecialCircularBeanB类的toString()方法中调用了SpecialCircularBeanA类的toString()方法,造成了死循环,最终抛出StackOverflowError错误。
5.2 不支持多例Bean的setter循环依赖
运行4.2节中PrototypeCircularTest类的main()方法,输出的结果信息如下所示。
Spring抛出了循环依赖的异常,说明Spring不支持多例Bean的setter循环依赖。接下来就分析下Spring中为啥不支持多例Bean的setter循环依赖。具体分析步骤如下所示。
(1)解析AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法
源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。先来看在doGetBean()方法中创建多例Bean对象的逻辑,如下所示。
可以看到,在多例Bean模式下,创建Bean对象之前会调用beforePrototypeCreation()方法,在创建Bean对象之后会调用afterPrototypeCreation()方法。
(2)解析AbstractBeanFactory类的beforePrototypeCreation(String beanName)方法
源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#beforePrototypeCreation(String beanName)。
可以看到,Spring在创建多例Bean时,会在beforePrototypeCreation()方法中,使用prototypesCurrentlyInCreation记录正在创建中的Bean,那prototypesCurrentlyInCreation又是个什么鬼呢?
prototypesCurrentlyInCreation的源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#prototypesCurrentlyInCreation,如下所示。
也就是说,Spring在创建多例Bean时,会使用一个ThreadLocal类型的变量prototypesCurrentlyInCreation来记录当前线程正在创建中的Bean。并且根据beforePrototypeCreation()方法的源码又可以看出,在prototypesCurrentlyInCreation变量中使用一个Set集合来存储正在创建中的Bean。由于Set集合不存在重复对象,所以这样就能够保证在一个线程中只能有一个相同的Bean正在被创建。
(3)解析AbstractBeanFactory类的afterPrototypeCreation(String beanName)方法
源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#afterPrototypeCreation(String beanName)。
可以看到,afterPrototypeCreation()方法中主要是对prototypesCurrentlyInCreation中存储的Bean进行移除操作。
综合beforePrototypeCreation()方法和afterPrototypeCreation()方法可以看出,Spring在创建多例Bean之前,会将当前线程正在创建的Bean存入prototypesCurrentlyInCreation中,待Bean对象实例化完成后,就从prototypesCurrentlyInCreation中移除正在创建的Bean。
(4)返回AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法。此时重点关注doGetBean()方法开始部分的代码片段。
在AbstractBeanFactory类的doGetBean()方法开始的部分,会调用getSingleton()方法从缓存中获取单例Bean对象,首先会执行if条件进行判断,如果获取到的单例Bean对象为空,说明此时可能是第一次执行doGetBean()方法,也可能是创建的多例Bean。接下来会进入else分支逻辑,在else分支逻辑中,首先会判断当前线程是否已经存在正在创建的Bean,如果存在,则直接抛出BeanCurrentlyInCreationException异常。
(5)解析AbstractBeanFactory类的isPrototypeCurrentlyInCreation(String beanName)方法
源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation(String beanName)。
所以,在Spring创建多例Bean时,无法解决Bean的循环依赖。如果在创建多例Bean的过程中,发现存在循环依赖,则直接抛出BeanCurrentlyInCreationException异常。
5.3 不支持代理对象的setter循环依赖
运行4.3节中ProxyCircularTest类的main()方法,输出的结果信息如下所示。
从输出的结果信息可以看出,Spring抛出了循环依赖的异常。接下来,具体分析Spring为何不支持代理对象的setter循环依赖。
(1)解析AbstractAutowireCapableBeanFactory类的doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法。
通过前面章节对于创建Bean的流程分析可知,无论是创建单例Bean还是创建多例Bean,Spring都会执行到AbstractAutowireCapableBeanFactory类的doCreateBean()方法中。
源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。重点关注如下代码片段。
可以看到,在AbstractAutowireCapableBeanFactory类的doCreateBean()方法中,会判断从二级缓存中获取到的对象是否等于原始对象,代码片段如下所示。
此时,如果是代理对象的话,二级缓存中会存放由AOP生成出来的代理对象,与原始对象不相等。所以,会抛出BeanCurrentlyInCreationException异常。
另外,仔细观察AbstractAutowireCapableBeanFactory类的doCreateBean()方法时,会发现如果从二级缓存中获取到的earlySingletonReference对象为空,就会直接返回,不会抛出BeanCurrentlyInCreationException异常。由于Spring默认会按照文件全路径递归搜索,并且会按照路径+文件名的方式进行排序,排序靠前的Bean先被加载。所以,将标注了@Async注解的类排在后面即可解决循环依赖的问题。
5.4 不支持构造方法的循环依赖
运行4.4节中ConstructCircularTest类的main()方法,输出的结果信息如下所示。
可以看到,Spring抛出了循环依赖的异常,说明Spring不支持基于构造方法的循环依赖。接下来,具体分析下Spring不支持基于构造方法的循环依赖的原因。具体分析步骤如下所示。
(1)解析AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法
源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。
此时重点关注如下代码片段。
可以看到,在AbstractBeanFactory类的doGetBean()方法中,会调用getSingleton()方法。
(2)解析DefaultSingletonBeanRegistry类的getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法
源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory)。
可以看到,在getSingleton()方法创建Bean之前会调用beforeSingletonCreation()方法,在创建Bean之后会调用afterSingletonCreation()方法。
(3)解析DefaultSingletonBeanRegistry类的beforeSingletonCreation(String beanName)方法
源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation(String beanName)。
可以看到,在创建单例Bean之前,会将要创建的Bean的名称添加到singletonsCurrentlyInCreation中,添加失败,说明singletonsCurrentlyInCreation集合中已经存在当前Bean的名称,发生了循环依赖,就会抛出BeanCurrentlyInCreationException异常。那么singletonsCurrentlyInCreation又是个什么鬼呢?
singletonsCurrentlyInCreation的源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonsCurrentlyInCreation。
可以看到,singletonsCurrentlyInCreation其实就是一个Set集合。也就是说,Spring会在创建单例Bean之前,将正在创建的Bean的名称添加到singletonsCurrentlyInCreation集合中。
(4)解析DefaultSingletonBeanRegistry类的afterSingletonCreation(String beanName)方法
源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#afterSingletonCreation(String beanName)。
可以看到,在afterSingletonCreation()方法中,主要是移除singletonsCurrentlyInCreation中对应的Bean的名称。
综合beforeSingletonCreation()和afterSingletonCreation()两个方法可以看出,Spring在创建单例Bean之前,会将Bean的名称存入singletonsCurrentlyInCreation集合中,实例化Bean之后,会将Bean的名称从singletonsCurrentlyInCreation集合中移除。并且,Spring在创建单例Bean之前,会调用beforeSingletonCreation()方法将要创建的Bean的名称添加到singletonsCurrentlyInCreation中,添加失败,说明singletonsCurrentlyInCreation集合中已经存在当前Bean的名称,发生了循环依赖,就会抛出BeanCurrentlyInCreationException异常。
5.5 不支持@DependsOn的循环依赖
运行4.5节中DependsOnCircularTest类的main()方法,输出的结果信息如下所示。
从输出的结果信息可以看出,Spring抛出了循环依赖的异常。说明Spring不支持@DependsOn注解的循环依赖。接下来,就分析下Spring不支持@DependsOn注解的循环依赖的原因。具体分析步骤如下所示。
解析AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法
源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。重点关注如下代码片段。
可以看到,在AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法中,会判断是否存在@DependsOn注解的循环依赖,如果存在则抛出BeanCreationException异常。所以,Spring不支持@DependsOn注解的循环依赖。
5.6 支持单例Bean的setter循环依赖
运行4.6节中SingletonCircularTest类的main()方法,输出的结果信息如下所示。
可以看到,正确输出了SingletonCircularBeanA类型的Bean对象和SingletonCircularBeanB类型的Bean对象。说明Spring支持基于单例Bean的setter方法的循环依赖。接下来,就分析下Spring为何支持单例Bean的setter循环依赖。
(1)三级缓存
Spring使用了三级缓存来解决循环依赖的问题,三级缓存的源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry。
其中,每个Map的含义如下所示。
- singletonObjects:一级缓存,存储所有实例化,并且为属性赋值的单实例Bean。
- earlySingletonObjects:二级缓存,存储实例化后还没来得及为属性赋值的单实例Bean。
- singletonFactories:三级缓存,存储生产单实例Bean的工厂。
关于三级缓存,面试中经常会问如下两个问题,这里也给大家分享下。
- Spring解决循环依赖为什么需要二级缓存?
二级缓存主要是为了分离创建出的完整的Bean和未对属性赋值的Bean,二级缓存中实际主要存储的是未对属性赋值的Bean,这样做的目的就是为了防止在多线程并发的场景中,读取到还未创建完成的Bean。所以,为了保证在多线程并发的环境中,读取到的Bean是完整的(已经为属性赋值),不会读取到未对属性赋值的Bean,需要使用二级缓存解决循环依赖。
另外,就一、二、三级缓存而言,二级缓存主要存储的是三级缓存创建出来的,并且未对属性赋值的Bean,这样做的目的也是为了防止三级缓存中的工厂类重复执行创建对象的逻辑。
- Spring只用二级缓存能否解决循环依赖?为什么一定要用三级缓存来解决循环依赖呢?
其实,Spring使用二级缓存就完全能够解决循环依赖的问题,也可以支持Spring基于BeanPostProcessor的扩展能力。但是,由于Spring中的方法在设计上遵循了单一职责的原则,一个方法通常只做一件事情,getBean()方法就是获取Bean对象。但是,调用BeanPostProcessor创建动态代理是处于创建Bean的过程,如果在getBean()中实现这个逻辑,显然代码逻辑比较耦合。为了解决代码耦合的问题,保持方法的职责单一,方面后期维护。需要将创建动态代理的BeanPostProcessor放在创建Bean的方法中。并且将判断是否存在循环依赖的逻辑放在getSingleton()方法中。此时就需要三级缓存,在三级缓存中存放一个工厂接口,在接口的实现类中调用BeanPostProcessor创建动态代理对象。为了防止重复创建代理对象,将三级缓存中创建的代理对象存入二级缓存。在Spring中使用三级缓存完美解决了解耦、性能、扩展的问题。
(2)创建单例工厂
Spring在创建Bean对象时,会先创建一个和Bean的名称相同的单例工厂,并将Bean先放入单例工厂中。
源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。
在AbstractAutowireCapableBeanFactory类的doCreateBean()方法中调用了addSingletonFactory()方法。
addSingletonFactory()方法的源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)。
可以看到,addSingletonFactory()方法的作用是将正在创建中的Bean的单例工厂,存放在三级缓存里,这样就保证了在循环依赖查找的时候是可以找到Bean的引用的。
(3)读取缓存数据
具体读取缓存获取Bean的过程在类DefaultSingletonBeanRegistry的getSingleton()方法中,源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)。
通过上面的源码我们可以看到,在获取单例Bean的时候,会先从一级缓存singletonObjects里获取,如果没有获取到(说明不存在或没有实例化完成),会去第二级缓存earlySingletonObjects中去找,如果还是没有找到的话,就会三级缓存中获取单例工厂singletonFactory,通过从singletonFactory中获取正在创建中的引用,将singletonFactory存储在earlySingletonObjects 二级缓存中,这样就将创建中的单例引用从三级缓存中升级到了二级缓存中,二级缓存earlySingletonObjects,是会提前暴露已完成构造,还未执行属性注入的单例bean的。这个时候如何还有其他的bean也是需要属性注入,那么就可以直接从earlySingletonObjects中获取了。
注意:为了防止多线程并发环境下重复执行三级缓存中创建Bean的过程,在对singletonObjects加锁后,还会先从一级缓存singletonObjects中获取数据,如果数据不存在则从二级缓存earlySingletonObjects中获取数据,如果数据仍然不存在,才会从三级缓存singletonFactories中获取singletonFactory,调用singletonFactory的getObject()方法获取实例化但未对属性赋值的Bean对象,将其存入二级缓存,并且从三级缓存中移除对应的singletonFactory。
(4)解决循环依赖的完整流程图
最后给出Spring支持单例Bean的setter循环依赖的完整流程图,如图20-6所示。
大家可以按照20-6的逻辑分析Spring解决循环依赖的代码,就相对比较清晰了。这里,就不再分析具体源码了。
六、总结
Spring的循环依赖问题介绍完了,我们一起总结下吧!
本章,主要详细分析了Spring的循环依赖问题,首先介绍了缓存依赖的基本概念和循环依赖的类型。随后以案例的形式详细介绍了循环依赖的场景,并详细分析了Spring循环依赖的底层解决方案。通过分析得知:Spring默认会支持单例Bean的setter循环依赖,对于其他情况下的循环依赖,Spring默认是不支持的。并且,最后给出了Spring解决循环依赖的流程图。
七、思考
既然学完了,就开始思考几个问题吧?
关于Spring的循环依赖,通常会有如下几个经典面试题:
- 什么是循环依赖问题?
- 循环依赖有哪些类型?
- 在Spring中支持哪种循环依赖?
- 列举几种Spring不支持的循环依赖的场景,为什么不支持?
- Spring解决循环依赖的流程是什么?
- Spring解决缓存依赖时,二级缓存的作用是什么?
- Spring只用二级缓存能否解决循环依赖?为什么一定要用三级缓存来解决循环依赖呢?
- 你从Spring解决循环依赖的设计中得到了哪些启发?