Spring如何通过三级缓存解决循环依赖

时间:2022-07-23
本文章向大家介绍Spring如何通过三级缓存解决循环依赖,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

什么是循环依赖

什么是循环依赖?比如如下一个例子

public class A{
	private B b;
}
public class B{
	private A a;
}

这个例子存在的问题:理论上spring创建A的时候依赖了B,然后spring就会去加载B,但是这个时候B又依赖了A,spring又去加载A,就会陷入一个死循环,但我们在实际使用spring的时候并没有出现这样的循环,这是因为spring设计之初就考虑了这个问题,那么spring是如何解决的呢?我们先要明确的是spring对循环依赖的处理有三种情况,分别为

  1. 构造器的循环依赖:这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常。
  2. 单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
  3. 非单例循环依赖:无法处理。Spring如何通过三级缓存解决循环依赖Spring中有三级缓存,分别如下
  4. singletonObjects:完成初始化的单例对象的cache(一级缓存)
  5. earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)
  6. singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)

Spring获取一个Bean的流程就说从一级到三级依次去寻找这个Bean

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            //allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    //从singletonFactories中移除,并放入earlySingletonObjects中。
                    //其实也就是从三级缓存移动到了二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

如果一二三级均没有这个Bean,就会走新建方法,Spring创建一个Bean的时候也会有3个步骤,如下

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml中的init 方法。

我们再来说上面的循环依赖的例子:A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行第二步填充属性,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象,B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。

为什么构造器循环依赖和多例循环依赖Spring无法解决

构造器循环依赖

this .singletonsCurrentlylnCreation.add(beanName)将当前正要创建的bean 记录在缓存中 Spring 容器将每一个正在创建的bean 标识符放在一个“当前创建bean 池”中, bean 标识 柏:在创建过程中将一直保持在这个池中,因此如果在创建bean 过程中发现自己已经在“当前 创建bean 池” 里时,将抛出BeanCurrentlylnCreationException 异常表示循环依赖;而对于创建 完毕的bean 将从“ 当前创建bean 池”中清除掉。

多例循环依赖

对于“prototype”作用域bean, Spring 容器无法完成依赖注入,因为Spring 容器不进行缓 存“prototype”作用域的bean ,因此无法提前暴露一个创建中的bean 。

为什么不能只用一二级缓存来解决循环依赖?

这里面涉及到的问题很复杂,可以看这篇文章,我怕文章被删了,所以留了下图。

总结起来就是几点:一级缓存是放完全初始化好的对象的,如果只需要IOC功能其实一级缓存就能解决。但涉及到循环依赖,我们就需要暴露出一个没有初始化好的对象,那么我们不能把初始化好的和没好的都放到一级缓存里面吧?那不是乱套了,所以这个时候就需要引入二级缓存,把初始化好的放到一级缓存里面去,没好的放到二级缓存里面去。看起来可以完美解决问题了,但如果有代理对象的话,实际流程就会变成下面这样。我们假设上诉例子中A为代理对象

  1. A初始化的时候把能够生成A代理对象的一个lambda表达式放到三级缓存中
  2. A发现自己依赖B对象就去生成B对象,B对象发现自己需要A对象,就会去三级缓存中把这个lambda表达式取出来并且生成了A对象的代理对象,然后把代理对象放入到二级缓存,同时赋值B对象中的A,到此B对象构建完毕并放入一级缓存中。
  3. A对象使用构建好的B对象构建自己,A对象构建完毕。

你可能会有点疑问,为什么要把一个构建A代理对象的工厂放入到三级缓存中?为什么不能直接把已经生成好的代理对象放到二级缓存呢?这其实是出于性能的考虑,因为如果没有用到循环依赖的话,如果我们一开始就把生成好的代理对象放到二级缓存里面去,是不是有点影响性能呢?

详细的看下面

在这里插入图片描述