Spring 循环依赖

什么是循环依赖?

A 对象依赖了 B 对象,B 对象依赖了 A 对象

循环依赖分为两种:构造器的循环依赖、属性的循环依赖

比如:

属性的循环依赖:

//A依赖了B
class A{
    public B b;
}

//B依赖了A
class B{
    public A a;
}

构造器的循环依赖:

@Service
public class Student {
    @Autowired
    private Teacher teacher;

    public Student (Teacher teacher) {
        System.out.println("Student init1:" + teacher);
    }
}


@Service
public class Teacher {
    @Autowired
    private Student student;

    public Teacher (Student student) {
        System.out.println("Teacher init1:" + student);

    }
}

Spring 里面并不能解决构造器的循环依赖,因为JVM虚拟机在对类进行实例化的时候,需先实例化构造器的参数,而由于循环引用这个参数无法提前实例化,故只能抛出错误。
Tip:Spring 里面也不能解决原型(多例)模式下的构造依赖

所以我们接下来谈的 Spring 的循环依赖都是指属性的循环依赖,且是在单例模式下

那么循环依赖是个问题吗?

如果不考虑 Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的问题

比如:

        A a =new A();
        B b=new B();
        
        a.b=b;
        b.a=a;

那么 Spring 的循环依赖会导致什么问题的?如果你知道 Bean 的生命周期,那么你可能就知道这个问题

Bean 的生命周期

这里不会对 Bean 的生命周期进行详细的描述,只描述一下大概的过程

Bean 的生命周期指的是:在Spring 中,Bean 是如何生成的?

被 Spring 管理的对象叫做 Bean。Bean 的生成步骤如下:
1、Spring 扫描 class 得到 BeanDefinition
2、根据得到的 BeanDefinition 去生成 Bean
3、首先根据 class 推断构造方法
4、根据推断出的构造方法,反射,得到一个对象(暂时叫做原始对象
5、填充原始对象中的属性(依赖注入)
6、如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象
7、把最终生成的代理对象放入单例池(源码中叫 singletonObjects)中,下次 getBean 的时候就直接从单例池拿即可

可以看到,对于 Spring 中的 Bean 的生成过程,步骤还是很多的,并且不仅仅只有上面7步,还有很多很多,比如 Aware 回调、初始化等等

上面第5个步骤,就是循环依赖产生问题的地方。
拿上面的例子:A类依赖B类,B类依赖A类。

那么当在创建A类的Bean的时候,在第五步,需要进行属性填充,也就是要在单例池中找到一个类B的Bean来给A类中的 b 赋值。但这个时候单例池里面还没有B类的Bean。所以就会在单例池中创建B类的Bean,在创建B的时候,执行到第五步,就会去找 A 的Bean ,但是要在上面的步骤完全执行完的时候,A才会在单例池中,而现在A卡在第5步属性注入,单例池中没有 A
。所以 A 和 B 就都创建不出来,都卡在了属性注入那步,这就是循环依赖产生的问题

那么Spring中是怎么去解决循环依赖的?
答案是 三级缓存,它可以解决 Spring 中的 部分 循环依赖问题

以下将介绍什么是三级缓存,三级缓存是哪三级,以及他们怎么解决循环依赖的问题

先循序渐进,不讲三级缓存,只有二级缓存会出现什么情况,可不可以只用二级缓存解决循环依赖

二级缓存

先来分析为什么缓存能解决循环依赖?

上文分析得到,之所以产生循环依赖的问题,主要是:

A创建时 --> 需要B --> B创建时 --> 需要A ,从而产生了循环依赖
在这里插入图片描述
那么如何打破这个循环,加个中间人(二级缓存

A的Bean在创建过程中,在进行依赖注入之前,先把A的原始 Bean 放入缓存,再进行依赖注入,此时A的Bean依赖了 B的Bean,如果B的Bean不存在,则需要创建 B 的Bean,而创建 B 的 Bean 的过程和 A 一样,也是先创建一个 B 的原始对象,然后把 B 的原始对象提早暴露出来放在缓存中,然后在对 B 的原始对象进行依赖注入 A,此时能从缓存中拿到 A 的原始对象(虽然是A的原始对象,还不是最终的 Bean),B 的原始对象依赖注入完了之后,B 的生命周期结束,那么 A 的生命周期也能结束,整个流程如下图

在这里插入图片描述

Tip: 原始对象就是属性暂时没有值的对象,后面经过一系列操作,生成的Bean是完整对象

通过加入这个缓存,A 和 B 的 Bean 就都可以成功创建了,但是有的人可能会疑惑一个问题:从上图可以看出,B从缓存中获取到的是A的原始对象,也就是并不是一个完整的 A啊?

但是栈里面存放的是对象的引用啊,所以只要是同一个对象就好了,B 是持有的 A 对象的引用,不是A的内容,不管后续 A 怎么变,B都能正确通过地址获取到,所以是没有影响的(这里有个前提,就是都是单例对象)

通过以上的讲解,感觉二个缓存(二级缓存)似乎也解决了循环依赖,那么为什么还要有三级缓存(三个缓存)呢?

  • 单例池是一级缓存,再多加一个就是二级缓存(两个缓存),再多加一个就是三级缓存(三个缓存)

Tip:我之前混淆过二级缓存,三级缓存,应该这样理解,二级缓存就是有两个缓存,三级缓存就是有三个缓存,但是下面说的三级缓存也指第三级缓存

上面又说到,因为B是持有A的引用,所以不论怎么改变A的内容,B最终都能得到完整的A对象,那么有没有可能得不到呢?也就是赋值给B的原始对象和最后形成的A的Bean对象,这两个对象不一样

答案是有可能的

有两种情况:

还记得上面讲的 Bean 的生命周期吗?

  • 第6步可能会产生一个动态代理对象,那么最后在常量池里面存放的就不是原始对象的那个引用了,而是动态代理对象的引用,而 B 拿到的却是原始对象的引用
  • 还有一种情况,上面生命周期没提,就是Bean的后置处理器(BeanPostProcessor),会对前面生成的对象加工,在这步,比如重新new一个对象返回,那么也会导致赋值给B的A对象前后不一致

第一种情况,AOP 产生动态代理对象:

在这里插入图片描述

第二种情况,自己 new 一个对象:
在这里插入图片描述

这两种情况 二级(两个)缓存 都不能解决,第二种情况 不管有几级(几个)缓存,Spring 都不能解决 。但是第一种AOP那种情况,Spring里面是处理了的,就是通过三级缓存(三个缓存)处理的

所以为了解决 AOP 导致的前后赋值对象不一样,但出现循环依赖仍然能使用AOP ,就有了三级缓存

什么是三级缓存

在AbstractBeanFactory中有详细的注释:
在这里插入图片描述
(看不懂可以先跳过这里,看完了下面再来看,只是要知道三级缓存是什么,对应的返回类型,还有记住这几个粗黑的关键字)

1、singletonObjects(一级缓存):缓存某个 beanName 对应的经过了完整生命周期的 bean,其实就是一个单例池

2、earlySingletonObjects(二级缓存):缓存提前拿原始对象进行了AOP之后得到的代理对象,原始对象还没有进行属性注入和后续的 BeanPostProcessor 等生命周期。如果不需要AOP,二级缓存就还是存的原始对象

3、singletonFactories(三级缓存):缓存的是一个 ObjectFactory ,主要是用来去生成原始对象进行了 AOP 之后得到的代理对象,在每个 Bean 的生成过程中,都会提起暴露一个工厂,这个工厂可能用得到,也可能用不到, 如果没有出现循环依赖 依赖本 Bean,那么这个工厂无用,本 Bean 按照自己的生命周期执行,执行完之后直接把本 Bean 放入 singletonObjects 中即可。如果出现了循环依赖 依赖本 Bean,则另外那个 Bean 执行 ObjectFactory 提交得到一个 AOP 之后的代理对象(如果有 AOP 的话,如果无需 AOP,则直接得到一个原始对象)

4、其实还要一个缓存,就是 earlyProxyReferences,它用来记录某个原始对象是否进行过 AOP了

三级缓存

  • 在构造 A 类的Bean的时候,首先会生成一个原始对象,生成原始对象之后会构造一个 lambda 表达式 存放到三级缓存(singletonFactories,前面要求记住的三个关键字之一)中去。也即“提前暴露”。注意,存进去之后,这个 lambda 表达式是还没有被执行的

    所以三级缓存其实就是一个Map:key是beanName,value 是一个 lambda 表达式,lambda 表达式的返回值是 ObjectFactory
    在这里插入图片描述

    lambda 表达式:() -> getEarlyBeanReference(beanName, mbd, bean)

  • 存完 lambda 表达式之后,开始进行属性填充的时候,需要构造B类, 构造B类的时候属性注入发现需要A,就会执行一个 getSingleton 方法,这个方法可以通过A类的beanName找一个A的实例对象,这个方法源码介绍如下。

    @Nullable
    //传入 beanName 获取一个 bean 
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //首先B类去单例池中找A的实例对象(单例池就是一级缓存)
        Object singletonObject = this.singletonObjects.get(beanName);
    //如果单例池没找到A的实例对象,并且Bean处于正在创建中
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
   //就会从earlySingletonObjects缓存中找,这个是前面就说过的二级缓存
                singletonObject = this.earlySingletonObjects.get(beanName);

                if (singletonObject == null && allowEarlyReference) {
          //从二级缓存中也没找到,就会去singletonFactories找(这是前面就说过的三级缓存),找出来的就是我们前面提到过的 lambda 表达式
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
               
                    if (singletonFactory != null) {
                    //这句话就是在执行上面三级缓存中找到的 lambda 表达式,这个getObject是个函数式接口。执行完这句话,singletonObject 值就是一个AOP代理对象(不需要AOP的时候是原始对象)
                        singletonObject = singletonFactory.getObject();
                 // 把 lambda 表达式产生的代理对象(或者原始对象)放入二级缓存,但是这个代理对象还不是一个完整的Bean
                        this.earlySingletonObjects.put(beanName, singletonObject);
                 //从三级缓存移除已经执行完的二级缓存
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }

        return singletonObject;
    }

执行 lambda 表达式:


上面一直在提 lambda 表达式,其实执行这个表达式就是为了得到 A 的一个代理对象,如果A没有AOP那步,得到的就是A的原始对象

Tip:lambda 表达式的执行

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        //如果对象A不需要AOP,这段代码就不会执行,也就是会直接返回原始对象
        if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
            Iterator var5 = this.getBeanPostProcessors().iterator();

            while(var5.hasNext()) {
                BeanPostProcessor bp = (BeanPostProcessor)var5.next();
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor)bp;
              //获得一个A的代理对象
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }

        return exposedObject;
    }


	//这个 bean 就是我们A的原始对象
    public Object getEarlyBeanReference(Object bean, String beanName) {
    // cacheKey 就是 beanName
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    //把原始对象放进 earlyProxyReferences 这个 map 中去。这个earlyProxyReferences 也是前面要求记住的关键字之一,用来记录某个原始对象是否进行过 AOP。
        this.earlyProxyReferences.put(cacheKey, bean);
        //这个方法会产生一个代理对象
        return this.wrapIfNecessary(bean, beanName, cacheKey);
    }
  • 反正最后是在三级缓存中找到了A,也就是会找到上面那个 lambda 表达式,在这里开始执行那条lambda表达式,然后把lambda表达式产生的A 的 AOP代理对象或者原始对象放到了二级缓存,顺便从三级缓存把执行过的 lambda 表达式移除了,然后再把这个 lambda 表达式产生的 A 的代理对象或者原始对象给B注入,B就可以顺利执行后续的操作了

  • B执行完,A也能顺利执行了。这时候回到A,如果A的生命周期有AOP那步,A不需要再进行 AOP,直接从二级缓存中获取自己的代理对象。如果 A 本来就不用进行 AOP ,那么就还是自己的原始对象,判断A是否进行过AOP的逻辑如下

    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    //这个 bean 是传进来的原始对象
        if (bean != null) {
            //cacheKey 就是 beanName
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            //earlyProxyReferences这个是个 map,前面要求记住的关键字之一,和在讲 lambda 表达式的时候出现过的,如果 A 进行过 AOP,那么这个 map 就会存放 A 原始对象
            // 如果earlyProxyReferences没有这个beanName对应的对象,就说明这个对象没有提前进行过AOP,就进行 AOP
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return this.wrapIfNecessary(bean, beanName, cacheKey); //这个方法就是进行AOP,返回值就是 AOP 代理对象
            }
        }
//返回原始对象
        return bean;
    }

总结:

  • 三级缓存中存的是 lambda 表达式,只有循环依赖的时候才会去执行这个 lambda 表达式

  • lambda 表达式的作用:lambda 表达式的返回类型是 ObjectFactory,算是一个对象工厂。如果对象有AOP那步,返回的就是对象的AOP代理对象,否则就返回原始对象

  • 二级缓存中存放的是对象的AOP代理对象或者原始对象,也即 lambda 表达式产生的对象

  • 一级缓存就是单例池,存放的是经过所有步骤后的完整的 Bean,在解决循环依赖上没什么作用

  • 二级缓存和三级缓存在循环依赖的时候才会起作用,对正常流程没影响

  • 如果没有三级缓存,AOP就会导致赋值给B的原始对象和A最后生成的Bean对象不一样,就会报错

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring循环依赖指的是在Spring中,多个Bean之间存在相互依赖的情况。具体来说,当一个Bean A依赖于另一个Bean B,同时Bean B也依赖于Bean A时,就形成了循环依赖。这种情况下,Spring需要解决Bean的创建和依赖注入的顺序问题。 在Spring中,循环依赖问题是由于Bean的生命周期所引起的。Spring的Bean生命周期包括了Bean的实例化、属性注入、初始化以及销毁等过程。当出现循环依赖时,Spring会通过使用“提前暴露”的方式来解决这个问题。 具体来说,当Spring创建Bean A时,发现它依赖于Bean B,于是会创建一个A的半成品对象,并将其暂时放入一个缓存中。然后,Spring会继续创建Bean B,并将其注入到A的属性中。接着,Spring会继续完成B的创建,并将其放入缓存中。最后,Spring会将A的半成品对象交给B进行依赖注入,完成A的创建,并将其从缓存中移除。 需要注意的是,Spring循环依赖有一定的限制条件。例如,如果Bean A和Bean B都是单例模式,那么它们之间的循环依赖是无法解决的。因为单例模式下,Bean的创建和依赖注入是同时进行的,无法通过缓存来解决循环依赖。在这种情况下,程序员需要手动调整Bean的依赖关系或使用其他解决方案来避免循环依赖的问题。 综上所述,Spring循环依赖是指在Spring中多个Bean之间存在相互依赖的情况。Spring通过使用缓存和提前暴露的方式来解决循环依赖问题,但在某些情况下有一定的限制条件需要注意。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值