阅读本文章之前推荐先阅读博主关于Bean生命周期的文章,传送地址:Spring中IOC容器和Bean的生命周期
循环依赖
文章目录
- 循环依赖
- 一、循环依赖的概念
- 二、什么情况下可以解决循环依赖的问题
- 三、Spring如何解决循环依赖
- 1. 解决简单的循环依赖(没有AOP)
- 2. 解决结合了AOP的循环依赖
一、循环依赖的概念
从字面上来理解就是A依赖B的同时B也依赖了A,如下图所示
对应的代码如下:
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
}
对应的流程图如下所示,可以看出发生了死循环:
二、什么情况下可以解决循环依赖的问题
Spring解决循环依赖是有前置条件的:
- 出现循环依赖的Bean必须是单例的
- 依赖注入不能全是构造器注入的方式
@Component
public class A {
//没有使用@Autowired注解
private B b;
//使用构造器注入
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public B(A a){
this.a = a;
}
}
上述代码中两个Bean的注入方式都是通过构造器注入的,这种情况下循环依赖是无法被解决的,启动时会报错:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
- 循环依赖的解决情况和注入方式的关系
从图中可以看出,均使用 setter
方法注入的情况循环依赖可以被解决;均采用构造器注入的情况无法被解决;使用了构造器注入的方式循环依赖也可能被解决
三、Spring如何解决循环依赖
Spring通过提前暴露对象(三级缓存)的方式解决循环依赖,大体过程如下图所示:
提前暴露的对象指的就是实例化完成但还没有初始化的对象,称为半成品对象
观察上图可以得出解决循环依赖最重要的两点是:
- 实例化和初始化的过程分开
- 设置缓存来预存半成品对象
1. 解决简单的循环依赖(没有AOP)
分析一个最简单的例子:
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
}
//这种情况下循环依赖一定可以被Spring自动解决
Spring内部有三级缓存:
singletonObjects
,一级缓存(也称为单例池),用于保存实例化、注入、初始化完成的bean实例earlySingletonObjects
,二级缓存,用于保存完成实例化,但是还未进行属性注入及初始化的对象(半成品对象)singletonFactories
,三级缓存,是一个单例工厂,二级缓存中存储的半成品对象就是从这个工厂中获取到的
Spring解决循环依赖的流程图:
Spring在创建多个Bean的时候默认是按照自然排序来进行创建的,所以第一步Spring会去创建testService1:
从上述的流程图可以得出如下结论:
- 初始化完成的Bean最终会被放到一级缓存,也就是单例池中
- 属性注入之前Spring将Bean包装成一个工厂(ObjectFactory)添加进了三级缓存中
- 但实际上这个三级缓存在没有使用AOP的循环依赖中并没有实际用处,可以直接将对象放在二级缓存中
- testService2中提前注入了一个没有经过初始化的TestService1类型的对象不会有问题吗?
- 不会的,因为在创建 testService1 的流程中一直使用的是注入到 testService2 中的 testService1 对象的引用,之后会根据这个引用对 testService1 进行初始化,所以这是没有问题的
2. 解决结合了AOP的循环依赖
依然按照上述的例子,如果对testService1使用了AOP代理,则testService2从三级缓存中获取到的是testService1的代理对象(再将其移动至二级缓存中),而不是testSevice1的半成品对象
几个问题:
- 在给testService2注入的时候为什么要注入一个代理对象?
- 对testService1进行了
AOP
代理时,说明希望从容器中获取到的就是testService1代理后的对象而不是testService1本身,因此把testService1当作依赖进行注入时也要注入它的代理对象
- 三级缓存真的提高了效率吗?
- 对于没有使用AOP情况,之前已经分析过,没有提高任何效率
- 对于使用AOP的情况
- 如果使用了三级缓存,为testService1创建代理对象的时机是在testService2中需要注入testService1时
- 如果没有使用三级缓存,在实例化testService1之后,就要为其创建代理对象
- 对于两种情况的整体而言,无论有没有三级缓存消耗的时间都是一样的,故三级缓存提高了效率这种说法都是错误的