什么是循环依赖?
当一个bean A 依赖另外一个bean B,并且bean B也依赖于bean A时,就会发生这种情况:
bean A -> bean B -> bean A
当然,中间可能会有其他隐含的bean:
bean A -> bean B -> bean C -> bean E -> bean A
Spring中循环依赖会发生什么?
当Spring上下文加载所有bean时,它会尝试按照他们完成工作所需要的顺序来创建bean。例如,如果我们没有循环依赖,就像下面这样:
bean A -> bean B -> bean C
Spring会先创建bean C,然后创建bean B,并将bean C注入到bean B中,然后创建bean A,并将bean B注入到beanA中。
但是,当循环依赖时,Spring就无法确定首先创建哪个bean了,因为它们相互依赖。这种情况下,Spring将抛出
BeanCurrentlyCreationException。
在Spring中使用构造函数注入时,就可能会发生这种情况;如果你使用其他类型的注入,就不会发生这种情况,因为依赖项将在被需要的时候注入,而不是在上下文加载时注入。
解决方法
重新设计
当你有一个循环一来是,很可能是设计问题时,职责没有很好地分开。你可以尝试重新正确地设计组件。以便它们的层次结构良好,不产生循环依赖。
如果由于遗留代码、无法修改的代码或没有足够的时间重新设计时,你可以尝试下面的方法。
使用@Lazy
解决循环依赖一种简单的方法就是Spring懒惰初始化其中一个bean。也就是说:它没有完全初始化bean,而是创建一个代理,并将其注入到另外一个bean中。被注入的bean只有在第一次需要的时候才会完全创建。示例:
使用Setter/Field注入
最流行的解决方法之一,也是Spring官方提出的,就是使用setter注入。
简单地说,使用setter/field注入,而不是构造函数注入-这确实解决了问题。通过这种方式,Spring创建了bean,但在需要它们之前不会注入依赖项。
这里需要注意的是Field注入会使用反射,所以尽量不要使用Field注入。示例:
使用@PostConstruct
另一种解决方式就是在其中一个bean上使用@PostConstruct注释的方法来设置另外一个依赖。示例:
总结
在 Spring 中有很多方法可以处理循环依赖。首先要考虑的是重新设计你的 bean,这样就不需要循环依赖了:它们通常是可以改进的设计的症状。
但是如果你需要在你的项目中有循环依赖,你可以遵循这里建议的一些解决方法。
首选方法是使用 setter 注入。但是还有其他选择,通常基于阻止Spring管理Bean的初始化和注入,然后自己使用一种或多种策略来完成。