什么是循环依赖?
当我们使用 IOC 进行依赖注入时,可能存在 A 依赖 B 且 B 依赖 A 的情况,如此将会形成 A->B->A 的循环依赖。
为了实例化 A,IOC 不得不先实例化 B,但是 B 的实例化又依赖 A,如此下去无穷无尽。那么循环依赖在 Spring 中的表现是怎样的呢?Spring 又是如何解决的?
案例
Spring 中依赖注入可以分为属性注入和构造器注入两类,注入的 bean 也有单例和多例的区分。我们可以对多种组合情况进行单独测试。
A单例&属性注入,B单例&属性注入
@Service
public class Test1 {
@Autowired
private Test2 test2;
}
@Service
public class Test2 {
@Autowired
private Test1 test1;
}
结果: 成功
A单例&属性注入,B单例&构造器注入
@Service
public class Test1 {
@Autowired
private Test2 test2;
}
@Service
public class Test2 {
private Test1 test1;
public Test2(Test1 test1) {
this.test1 = test1;
}
}
结果:成功
A单例&构造器注入,B单例&属性注入
@Service
public class Test1 {
private Test2 test2;
public Test1(Test2 test2) {
this.test2 = test2;
}
}
@Service
public class Test2 {
@Autowired
private Test1 test1;
}
结果:失败
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| test1 defined in file [Test1.class]
↑ ↓
| test2 (field private Test2.test1)
└─────┘
A单例&构造器注入,B单例&构造器注入
@Service
public class Test1 {
private Test2 test2;
public Test1(Test2 test2) {
this.test2 = test2;
}
}
@Service
public class Test2 {
private Test1 test1;
public Test2(Test1 test1) {
this.test1 = test1;
}
}
结果:失败,日志同上
A多例模式&属性注入,B单例模式&属性注入
@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Test1 {
@Autowired
private Test2 test2;
}
@Service
public class Test2 {
@Autowired
private Test1 test1;
}
结果:失败,日志同上
其他组合情况都会报相同错误,由于篇幅原因,本文就不再列举了。
结论
在循环依赖情况下,只有当优先加载的bean为 单例&属性注入 时,才能够成功注入。
为什么是这种现象?
分析getBean流程
图片稍小,建议放大了看
从这个简化的 getBean 流程中可以很清楚看到,Spring在获取单例时,会先从三个不同的缓存中查找bean,若找到便直接返回。若没有找到,通过createBean创建对象并立刻放入第三级缓存中。
若首先加载的bean为单例属性注入,则会先通过默认构造函数创建对象,并放入三级缓存中,后续再循环回来时,直接从缓存中获取对象,也就避免了死循环。
若首先加载的是构造函数注入,无法通过默认构造函数创建,只能使用指定的带参构造函数创建对象,故而在创建对象时需要提前获取参数依赖,这时bean并没创建成功,更没有在缓存中,出现循环时直接报错。
若首先加载的是多例,Spring不会保存对象的引用,更没有缓存。当再次循环回来时,发现相同的beanName已经在Set集合中,则报错。