前言
最近对Spring的循环依赖比较感兴趣,特地研究了一下并分享给大家。
要说循环依赖,先理解循环引用,如果一个Class如TestA,需要引用TestB,而同时TestB又引用TestA,则可以称这两个类循环引用。
如下:
1 | public class TestA { |
如果这两个类被Spring管理,成为Spring的Bean,就会有循环依赖问题。
对于循环依赖问题,可能会有问题导致Bean创建不成功,我们来看一下。
正文
我们知道,对于Spring的Bean,有singleton(单例)和prototype(多例)两种模式。
对于SpringBean的创建,我们有构造器注入和Setter注入两种方式。
它们都会对循环依赖问题造成影响,我们分别讨论。
Bean都为singleton模式,都通过构造器注入
代码如下:
1 | (value = BeanDefinition.SCOPE_SINGLETON) |
我们使用下面的测试类观测Bean创建情况,后面的都可以使用这个测试类。
1 | (SpringRunner.class) |
对于上面的TestA和TestB我们运行后发现出现异常,Bean创建不成功,部分异常如下:
1 | ... |
Bean都为prototype模式,都通过构造器注入
我们再看看把Bean改为prototype模式后如何。
代码如下:
1 | (value = BeanDefinition.SCOPE_PROTOTYPE) |
经过测试可以发现也是不成功的,抛出BeanCurrentlyInCreationException异常。
其实上面两个例子是比较好理解的,通过构造器注入,也就是Bean TestA在创建的时候就需要TestB,TestB在创建的时候就需要TestA,显然,Spring无法解决这种情况,我们也无法解决这种情况。
Bean都为singleton模式,都通过Setter注入
我们再来看下这种情况,代码如下:
1 | (value = BeanDefinition.SCOPE_SINGLETON) |
我们测试一下可以发现运行成功了,成功输出两个Bean对象信息。
1 | com.zwt.demo.circularreference.TestA@2cc04358 |
其实这种情况也是比较好理解的:
- 当Bean TestA创建的时候需要TestB,TestB会创建一个默认的使用无参构造器的Bean对象,此时TestB里的TestA为空,我们称为TestB(空);
- TestA使用TestB(空)完成依赖注入,生成Bean TestA;
- TestA构造完成,其返回给TestB(空),这时候TestB里的TestA不在为空,TestB构造完成。
其过程犹如如下代码:
1 | public static void main(String[] args) { |
其实可以看到最关键的还是TestA、TestB的无参构造函数,其实这个无参构造函数指的是无TestA或者TestB参与的构造函数,这样初始化实例时不涉及循环依赖类,而在创建成功后通过参数注入。
我们把上面TestA和TestB的无参构造函数删去,只保留有参构造,测试运行可以看到出现了BeanCurrentlyInCreationException异常。
Bean都为prototype模式,都通过Setter注入
这种情况貌似也可以??我们来看下代码:
1 | (value = BeanDefinition.SCOPE_PROTOTYPE) |
我们测试后发现它也抛出BeanCurrentlyInCreationException异常,证明是不可以的。
这种情况如何理解呢?我先上段代码:
1 | public static void main(String[] args) { |
我们明显看到上述代码是错的,明显不存在testB和testA对象,testA1和testB1如何set?
是的,Spring就是这样,对于prototype(多例)模式,它不会保存已经创建的TestA或者TestB对象的引用。
也就是在注入时,我们可以认为完成了若干个(多例)TestA,但是都是“半成品”(没有TestB的注入),这时候TestB(多例)创建,需要TestA,即使拿到TestA(空),也无法将创建好的TestB反作用于TestA(空),使其成为TestA。
我们来看下Spring相关源码。
我们在AbstractBeanFactory
的doGetBean
方法看起,如下图:
可以看到拿Bean前会先检查单例Bean,如果拿到了sharedInstance
并且参数为空,并判断这个Singleton是不是正在被创建中,如果是的话那么就存在循环引用,拿到的是个Bean“半成品”。
如果拿不到,它就是个多例,判断下是不是正在创建中,是的话就直接抛出异常了。
再来看下调用的DefaultSingletonBeanRegistry
类的getSingleton
方法。
可以看到这种Bean都会保存在一个叫earlySingletonObjects
的HashMap
里,如果没有就尝试去一个叫singletonFactories
的HashMap
里去获取。
然后可以看到这个HashMap
的值是通过addSingletonFactory
方法获得的。
继续跟踪可以看到放入条件:单例、允许循环依赖、当前Bean正在被创建,如下图:
最后getEarlyBeanReference
返回了一个比较特殊的Object。
我们也可以通过debug观察代码运行情况,这儿不再过多叙述。
Bean一个为prototype模式,一个为singleton模式,都通过构造器注入
这个根据我们上面等判断应该是不可以的。我们看到代码:
1 | (value = BeanDefinition.SCOPE_SINGLETON) |
我们可以测试同样抛出异常,实际上如果循环依赖的Bean都是通过构造器注入的,那么无论如何都是创建不成功的,与Bean创建顺序和Bean类型没有任何关系。
Bean一个为prototype模式,一个为singleton模式,都通过Setter注入
代码如下:
1 | (value = BeanDefinition.SCOPE_SINGLETON) |
输出日志:
1 | com.zwt.demo.circularreference.TestA@327ed9f5 |
我们可以看到这种情况下运行成功,TestA或者TestB有一个为Singleton的,通过参数注入,就能成功创建Bean。
Bean一个为prototype模式,通过构造器注入,一个为singleton模式,通过Setter注入
如下代码:
1 | (value = BeanDefinition.SCOPE_SINGLETON) |
我们可以看到运行成功,单例的TestA会先创建,名为TestA(空),然后创建TestB,使用了TestA(空),TestB创建成功后,TestA(空)里的testB被属性赋值。
Bean一个为prototype模式,通过Setter注入,一个为singleton模式,通过构造器注入
这种情况根据上面经验,应该是不可以的。
1 | (value = BeanDefinition.SCOPE_SINGLETON) |
我们测试一下,确实也是不可以的,抛出BeanCurrentlyInCreationException异常。
这种情况可以认为TestA(单例)创建需要TestB,但是TestB是多例的,Spring中无法形成TestB的唯一引用作用于TestA(形成了TestB就是单例的了),也就无法创建TestA Bean。
总结
上面说了很多情况,我们简单总结下。
对于TestA和TestB两个类,如果存在循环依赖:
TestA | TestB | Spring创建结果 |
---|---|---|
Singleton模式 Constructor注入 | Singleton模式 Constructor注入 | 失败 |
Prototype模式 Constructor注入 | Prototype模式 Constructor注入 | 失败 |
Singleton模式 Setter注入 | Singleton模式 Setter注入 | 成功 |
Prototype模式 Setter注入 | Prototype模式 Setter注入 | 失败 |
Singleton模式 Constructor注入 | Prototype模式 Constructor注入 | 失败 |
Prototype模式 Constructor注入 | Singleton模式 Constructor注入 | 同上(失败) |
Singleton模式 Setter注入 | Prototype模式 Setter注入 | 成功 |
Prototype模式 Setter注入 | Singleton模式 Setter注入 | 同上(成功) |
Prototype模式 Constructor注入 | Singleton模式 Setter注入 | 成功 |
Singleton模式 Setter注入 | Prototype模式 Constructor注入 | 同上(成功) |
Singleton模式 Constructor注入 | Prototype模式 Setter注入 | 失败 |
Prototype模式 Setter注入 | Singleton模式 Constructor注入 | 同上(失败) |
以上列举了所有情况,根据表格我们可以看出创建成功的,一定至少有一个Bean为Singleton模式且Setter注入。
这个结论也与我们刚才分析的一部分Spring源码是一致的,首先构造一个未完全初始化的Bean,这个Bean要求为单例的,而后通过实例化另一个循环依赖Bean,成功后通过Setter完成原来单例Bean的初始化。
以上就是Spring循环依赖的全部内容。