前置知识

  • Spring的依赖注入方式,我们分为setter注入和构造器注入。那么Spring可以解决掉setter类型的依赖注入,构造器形式的是不可以的。
  • Spring的单例和多例模式,多例模式下的依赖注入也是解决不掉的。所以我们关注的范围为单例模式下setter注入形式的循环依赖的解决方案。
  • SpringBean的生命周期我们可以概括为实例化、属性赋值、初始化、销毁四个大的阶段。

循环依赖的场景

@Component
public class A {
    @Autowired B b;
}

@Component
public class B {
    @Autowired A a;
}

类似上面这样,A中有一个属性引用了B,B中有一个属性引用了A。那么这就是经典的循环依赖,形成一个圈,拓展到多个Bean,也可以是A依赖B,B依赖C,C依赖A。这样也是循环依赖的场景。
再来好好理解一下上面说的生命周期,实例化A的时候,我们去属性赋值B。于是去找到B,B又进行实例化,B属性赋值A。于是A又找B,于是产生了死循环。

Spring是怎么解决的

引入三级缓存

其实是三个Map


/** 一级缓存:用于存放完全初始化好的bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** 二级缓存:用于原始的bean对象(尚未填充属性),用于解决循环依赖 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<String, Object>(16);
/** 三级缓存:用于存bean工厂对象,用于解决循环依赖 */
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<String, ObjectFactory<?>>(16);

一级缓存

假设我们没有三级缓存,只有一级缓存,那我们会怎么样进行处理呢?

首先A对象进行实例化,A要进行属性填充B。但是B还没有被创建,于是开始B进行实例化,
同样B也要进行属性填充,发现他需要A。然而我们的一级缓存的Map里还没有A,所以他要创建A,
于是就产生了死循环。循环往复,最后栈溢出。

那么有人会说,我的A不进行属性填充,直接丢一级缓存里面,那不就可以了吗?
这样就会造成map里面存的A是一个缺胳膊少腿的A,当真正用它的时候,会报空指针异常。而且我们的一级缓存规定存放的是完全初始化好的Bean。所以大家应该理解了,一级缓存是行不通的,于是Spring的大神想到了二级缓存。

二级缓存

上面说到一级缓存的问题在于没法存储半成品的对象,也就是未完全初始化好的对象,那么搞一个二级缓存专门存储这个。
下面这段话,我把一级缓存叫做map1,二级缓存叫做map2。

从实例化A开始,实例化之后还没有进行属性填充的时候,就把A`对象的引用`放入到map2中备用。
然后进行属性填充,A去填充B,发现B没有实例化,于是B同样实例化之后,把自己的半成品放入到map2。
B开始进行填充,发现map1中没有,又去map2中进行寻找,发现有。于是B直接拿到map2中的A使自己变得完整。
这个时候B就把自己放入map1中。并把map2中的半成品删除了。回到刚才A的阶段,A发现map1中已经有了B。
那么A也可以完成自己的创建。这就是二级缓存解决的问题。

三级缓存

按照上面的分析,二级缓存已经就可以解决循环依赖的问题了,那么为什么还搞出来一个三级缓存呢?

这主要是因为Spring的Aop机制所产生的代理对象问题。
首先要了解到一个前置是Spring的代理对象产生的阶段是在填充属性之后才产生的,原理通过后置处理器BeanPostProcessor来实现的。如果说我们用二级缓存来解决,那么就要在属性填充的时候,将代理对象生成好,放入二级缓存。那么就与我们Spring的对象生命周期相悖。所以这个方式不好,于是有了三级缓存。

三级缓存主要针对的是动态代理类型的对象,通过工厂在真正需要动态代理对象的时候才来获取,这样可以提升一些性能,因为创建动态代理对象是一个消耗比较大的过程。

首先,还是进行实例化A对象,这个时候要将A的ObjectFactory对象放入map3中。
同样继续进行属性填充,需要先去创建B。
那么去实例化B的时候,同样先要将B的ObjectFactory对象放入map3中。将B对象放入map2中。
继续执行到B的属性填充,去获取A对象,而此时map1中还没有A,会发现map3中有A的ObjectFactory对象,
那么我们通过ObjectFactory对象可以获取A的早期对象,然后将这个早期对象放入map2中,
同时删除map3中的A,然后我们将A的引用给B,此时B的对象已经完整了,就可以把B放入map中,map3中的B删除掉。
既然B已经创建完成,A继续执行b的属性填充就可以拿到B对象,
这样A也完成,最后把A也放入map中,删除掉map2中的A。

循环依赖问题就此解决。

Last modification:June 12, 2021
如果觉得我的文章对你有用,请随意赞赏