Spring中的循环依赖
- 循环依赖条件
- 解决方案
- Spring的一些容器
- 不允许循环依赖出现的情况
- 1、构造器注入
- 2、多例对象的setter注入无法循环依赖
- 数据结构支撑
- 流程简单解析
- 处理流程
- 为什么需要二级缓存
- 不支持循环依赖的原因
- 提前暴露对象的条件
循环依赖条件
对象之间彼此的相互引用
。
例如A持有B对象,同时B也持有A对象
也存在多个对象相互引用;例如A持有B,B持有C,C持有A的情况。
解决方案
Spring中解决循环依赖使用了三个缓存(即一二三级缓存)
,并且通过提前暴露对象
的手段使未进行依赖注入的对象
可用。
Spring的一些容器
不允许循环依赖出现的情况
当然了,并不是说任何时候都能允许循环依赖的出现。下面就简单说两种
1、构造器注入
A,B两个对象作为构造函数的参数传入,并在构造函数进行依赖注入;会导致不支持循环依赖。
代码示例
A 对象
@Component
public class A {
private B b;
// 在构造器标注自动装配
@Autowired
public A(B b) {
this.b = b;
}
}
B 对象
@Component
public class B {
private A a;
// 在构造器标注自动装配
@Autowired
public B(A a) {
this.a = a;
}
}
报出的错误
经过简单的筛选,以下错误就是问题的核心。
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [C:\DemoAndPirace\Spring\target\classes\com\flj\whilerefer\B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
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?
核心问题
Spring报错直接指出是否存在了循环依赖导致无法正常进行Bean的实例化
简单原理
为什么通过构造器注入无法进行循环依赖呢?
因为Spring在创建对象的时候还未实例化对象就要注入另一个对象了;注意,是实例化。
当需要注入另一个对象的时候就需要去创建另一个对象;但是另一个对象也持有自己,所以形成了套娃。
2、多例对象的setter注入无法循环依赖
第二种情况就是多例的Bean对象无法进行循环依赖
代码示例
A 对象
@Component
@Scope("prototype")
public class A {
@Autowired
private B b;
}
B对象
@Component
@Scope("prototype")
public class B {
@Autowired
private A a;
}
main方法,需要注意一定要去手动获取对象
public class SpringCon {
public static void main(String[] args)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfig.class);
// 一定要去获取Bean对象
// 因为在Spring容器中,多例对象是用到的时候才去获取的
// 如果不用该对象,那么不会创建;也就无法测试循环依赖
Object a = context.getBean("a");
}
}
异常错误
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
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?
无法循环依赖的原因
因为在多例中,不知道要对哪个对象进行循环依赖。
所以Spring在循环依赖的条件中
就把多例排除出去了。
后面源码会稍微讲一下。
===================================================
以下的模块需要对Spring的生命周期有所简单的了解。
大概从源码角度简单分析,涉及到的类还未贴出
===================================================
数据结构支撑
• 循环依赖使用了三个缓存结构来解决,并不是说一定要三个,其实一三就能解决,多个二级缓存只是为了提高效率
;并且三级缓存是一个工厂对象
• 其次,还涉及到了一个容器,这个容器是表示正在实例化的BeanDenfinition以及一个变量,就是允许提前暴露Bean实例。
三个Map图示(从上到下分别是一二三级缓存)
流程简单解析
首先,要清楚:如果一个类中包含成员变量拥有引用类型,并且加上了@Autowire的注解,那么在进行依赖注入的时候一定会实例化对应的引用对象,即beanFactory.getBean(被引用的BeanName);
循环依赖要涉及到Spring的IOC,DI,依赖注入的问题。
简单说明一下Bean对象的创建:解析XML->通过无参构造器实例化对象->收集Bean对象的成员变量的注解->提前暴露Bean对象->依赖注入->放入一级缓存->后期销毁
创建对象图例:
处理流程
假设下面是A对象走的流程
此时,依赖注入就会触发到另一个对象B的创建,也是跟上面一样的。
当B对象也进行依赖注入的时候,发现需要注入A,然后我们走到前面
为什么需要二级缓存
这里要说明一下为什么会有二级缓存这种东西
我们可以看到,三级缓存返回的是一个工厂,ObjectFactory
他的getObject方法比较复杂,所以直接将对象获取出来提取到一个中间缓存进行存放。
这样,如果A注入了很多循环依赖对象,那么其他的Bean需要拿到A的实例只需要从二级缓存中拿取实例对象即可,不需要去三级缓存拿工厂在生成,避免了一些重复的繁琐操作。
不支持循环依赖的原因
• 首先来解析构造函数
1) 单例能够实现循环依赖是因为他已经实例化好了对象,只是没有给对象中的属性赋值,那么这个地址是永远都可以使用的
2)而通过构造函数注入,就需要传入该对象才能成功的实例化对象,否则无法进行对象的实例化
3) 小结:setter方法注入是已经实例化好了对象;构造器注入则是缺少实例化对象的条件,导致无法实例化,产生了死锁才报错
• 多例不支持的原因
1) 因为多例不能提前暴露对象,所以当第二次进来的时候,会发现我这个对象已经正在实例化了,所以报错了
2)并且多例对象天生就具有懒加载
提前暴露对象的条件
单例的,允许循环依赖,该单例对象正在实例化