说明
看这篇文章的同学需要有对Spring ioc和di流程有了解,知道Spring bean创建和bean属性填充。
回忆从Spring容器获取bean
首先,我们可以找到Spring获取bean的方法,它会从三个缓存中获取。
//一级缓存,存储可以直接使用的bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//二级缓存 存储不完整对象 不能直接用的 循环依赖时,某些依赖的属性没有设置,这个时候bean就是不完整对象
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
//三级缓冲 存储bean工厂 可以延迟调用实例化 使用的时候才调用工厂方法获取对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//从Spring容器获取bean对象方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//三级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这里为什么需要3个缓存呢?为啥2个不行?1个也不行?我们下面开始来看到底为什么?
模拟循环依赖场景
A,B类分别有对方的类属性
@Service
public class A {
private B b;
}
@Service
public class B {
private A a;
}
模拟循环依赖相互手动注入
a对象依赖b对象,b对象又依赖a对象,属性注入流程就是先实例化B,注入A,然后再填充A
A a = new A();
B b = new B();
b.setA(a); //B里面注入不完整的A了
a.setB(b); //注入依赖的对象,A里面有B了,a注入完成变成完整的a
调试Spring循环依赖注入处理流程
相同的场景,看看Spring如何处理。
第一步,创建A类的对象a
在Spring中,A类实例化后,缓存了一个工厂,而不是实例A,why?
第二步 开始注入a对象的属性b
a注入b的的时候,缓存注册工厂类,而不是b实例
第三步 b对象又开始注入属性a对象
b对象又开始注入a对象了,这个时候a只有对应的工厂。
通过A工厂返回了a的代理对象,将代理对象放入到提早暴露的缓存earlySingletonObjects(二级缓存),这个时候的a并没有将b注入完成,还是一个半成品,所以a还是先放入到一个中间缓存。
第四步 b对象注入a对象
这个时候B注入完成了,A还没注入完成。B里面的A里面的B没有赋值
第五步 注入完整B对象,(实际还不是完整的)
第6步 a对象的b属性注入
最后将A对象的B属性注入,这个时候A对象完整了,所以B对象也完整了(弥补上一步骤)
完整a对象图
完整b对象图
总结一下,为什么使用三级缓存?
下次面试再被问到循环依赖的问题,我会怎么来给面试官说明白呢?
1、Spring通过3个缓存map来解决循环依赖的问题,分别是singletonObjects,singletonFactories,earlySingletonObjects
2、singletonObjects这个缓存是存储完整的对象,可以直接使用的。
3、singletonFactories这个缓存是为了延迟初始化,是解决循环依赖的关键,存在循环依赖注入时,可能注入的对象需要被代理,这个工厂类来实例化一个代理类。
4、earlySingletonObjects这个缓存是为了在a注入b,而b又注入a这个阶段,a是一个半成品,需要用来存储a这种半成品bean的状态。
需要等待a将b注入之后,这个缓存就要移除。
5、singletonFactories和earlySingletonObjects都是存储对象的中间状态,依赖它们保证最终注入的对象是完整的,依赖注入完成后会被清除。