什么是循环依赖
很简单, 就是A依赖B, B依赖A. 比如
class A {
private B b;
}
class B {
private A a;
}
这是问题么? 当然不是, 这个完全可以在构造完对象后设置对应的属性, 像这样:
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
那么下面这个呢.
class A {
private B b;
public A (B b) {
this.b = b;
}
}
class B {
private A a;
public B (A a) {
this.a = a;
}
}
这是问题么? 当然是, 构造A对象必须使用B对象, 但是构造B对象必须使用A对象, 所以导致两个对象无法创建成功.
分析下两种情况, 第一种情况对象的构造和属性设置分开进行, 而第二种对象的构造和属性设置耦合在一起. 所以本文将第一种情况称为弱依赖, 第二种称为强依赖.
从上述代码可以知道循环依赖如果是强依赖无法解决, 弱依赖才可以处理.
那么spring可以帮助我们解决强依赖么? spring这么强大, 那当然是不行的... spring只是工具不是魔法, 它只是简化工作量而已.
所以spring只能帮我们自动解决弱依赖问题.
spring 如何解决循环依赖
先让大家看下spring解决循环依赖的秘籍~
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
......
不管你信不信, 这就是spring处理循环依赖的思路.
- 先创建对象. 放到一边备用.
- 拿出对象并设置属性.
spring会将构造完毕的对象放入一个缓存中等待用户使用, 这个缓存成为一级缓存. 一级缓存存储的都是可以直接使用的对象. 那么哪里存储没有构造完毕的对象(就叫原始对象吧)呢? 那就再加一个缓存, 称为二级缓存, 二级缓存存储原始对象, 还没有构造完毕, 不能提供给用户使用.
整理流程如下:
三级缓存
如果看spring源码会发现情况要比上述复杂的多, 把握整体思想: 一级缓存存储可使用的对象, 二级缓存存储未完成对象.
三级缓存用于存储对象的工厂方法, 当对象被AOP增强后, 会通过三级获取工厂方法生成增强后的代理对象, 然后将代理对象存储到二级缓存, 并删除三级缓存对应的缓存.
BeanPostProcessor
通过实现BeanPostProcessor可以修改创建的对象, 那对循环依赖会有影响么? 是的, 会影响. BeanPostProcessor可以在对象实例化之后更改创建的对象, 比如在BeanPostProcessor中修改B对象, 那么会在执行后将新的对象注入到A对象中, 如果至此结束则没有问题, 但是如果还要在BeanPostProcessor中修改A对象, 那spring会报错, 因为新提供的A对象不是之前注入到B对象中的对象, 如果注入成功会造成依赖的对象不一致的情况.
原型模式
上面提到的各种缓存都是直接使用缓存中的对象, 如果是原型模式, 每次都需要新建对象的话怎么处理循环依赖? 答案是不能处理...spring会直接报错.
总结
- spring只能处理循环依赖为弱依赖的情况.
- 而且需要是单例模式下.
- 处理思路就是将依赖双方中间设置一个缓存.
- 尽量不要出现循环依赖, 2.6以上版本的spring boot默认不支持循环依赖, 出现循环依赖, 就算是弱依赖也会报错, 需要设置spring.main.allow-circular-references配置为true.
本文主旨是理解思想, 如有不对, 欢迎指正 ~