1. 什么是循环依赖?
通俗来讲,循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
举个例子
public class AService {
private BService bService;
}
public class BService {
private AService aService;
}
复制代码
上述例子中 AService
依赖了 BService
,BService
也依赖了 AService
,这就是两个对象之间的相互依赖。当然循环依赖还包括 自身依赖、多个实例之间相互依赖。
正常运行上面的代码调用 AService
对象并不会出现问题,也就是说普通对象就算出现循环依赖也不会存在问题,因为对象之间存在依赖关系是很常见的,那么为什么被 Spring 容器管理后的对象会出现循环依赖问题呢?
2. Spring Bean 的循环依赖问题
被 Spring 容器管理的对象叫做 Bean,为什么 Bean 会存在循环依赖问题呢?
想要了解 Bean 的循环依赖问题,首先需要了解 Bean 是如何创建的。
2.1 Bean 的创建步骤
为了能更好的展示出现循环依赖问题的环节,所以这里的 Bean 创建步骤做了简化:
- 在创建 Bean 之前,Spring 会通过扫描获取 BeanDefinition。
- BeanDefinition就绪后会读取 BeanDefinition 中所对应的 class 来加载类。
- 实例化阶段:根据构造函数来完成实例化 (未属性注入以及初始化的对象 这里简称为 原始对象)
- 属性注入阶段:对 Bean 的属性进行依赖注入 (这里就是发生循环依赖问题的环节)
- 如果 Bean 的某个方法有AOP操作,则需要根据原始对象生成代理对象。
- 最后把代理对象放入单例池(一级缓存
singletonObjects
)中。
上面的步骤主要是为了突出循环依赖问题,如果想了解 Bean 的完整生命周期可以看这一篇文章:浅谈 Spring Bean 的生命周期 - 掘金 (juejin.cn)
两点说明:
- 上面的 Bean 创建步骤是对于 单例(singleton) 作用域的 Bean。
- Spring 的 AOP 代理就是作为
BeanPostProcessor
实现的,而BeanPostProcessor
是发生在属性注入阶段后的,所以 AOP 是在 属性注入 后执行的。
2.2 为什么 Spring Bean 会产生循环依赖问题?
通过上面的 Bean 创建步骤可知:实例化 Bean 后会进行 属性注入(依赖注入)
如上面的 AService
和 BService
的依赖关系,当 AService
创建时,会先对 AService
进行实例化生成一个原始对象,然后在进行属性注入时发现了需要 BService
对应的 Bean,此时就会去为 BService
进行创建,在 BService
实例化后生成一个原始对象后进行属性注入,此时会发现也需要 AService
对应的 Bean。
这样就会造成 AService
和 BService
的 Bean 都无法创建,就会产生 循环依赖 问题。
2.3 三大循环依赖问题场景
Spring 并不能解决所有 Bean 的循环依赖问题,接下来通过例子来看看哪些场景下的循环依赖问题是不能被解决的。
AService
类
/**
* @author 单程车票
*/
public class AService {
private BService bService;
public AService() {
}
public AService(BService bService) {
this.bService = bService;
}
public BService getbService() {
return bService;
}
public void setbService(BService bService) {
this.bService = bService;
}
}
复制代码
BService
类
/**
* @author 单程车票
*/
public class BService {
private AService aService;
public BService() {
}
public BService(AService aService) {
this.aService = aService;
}
public AService getaService() {
return aService;
}
public void setaService(AService aService) {
this.aService = aService;
}
}
复制代码
测试类
/**
* 测试类
* @author 单程车票
*/
public class Application {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml");
AService aService = (AService) applicationContext.getBean("aService"