循环依赖之三级缓存

循环依赖之三级缓存

文档参考周老师的资料

循环依赖概念

官方概念:

Circular dependencies

If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).

截屏2020-11-11 下午9.31.19

多个bean之间(singleton)相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A。可用setter方式解决(也可以使用懒加载的方式)。但是构造器方式不能解决。

原型(properties)不支持循环依赖,会报错。

三大Map和四大方法

首先现弄清楚实例化和初始化的概念:

实例化:堆内存中申请一块内存空间

初始化:完成属性的各种赋值

DefaultSingletonBeanRegistry三大Map:(这里一定是单例Bean,非单例不能解决)

第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xFQywJtj-1605104413435)(/Users/duhaiyang/Desktop/截屏2020-11-11 下午9.43.31.png)]

同样可以理解为:

第一层singletonObjects存放的是已经初始化好了的Bean

第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean

第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YnWccKcG-1605105885107)(/Users/duhaiyang/Desktop/啥子情况的图片.png)]截屏2020-11-11 下午9.59.16

四大方法

1.getSingleton:希望从容器里面获得单例的bean,没有的话
2.doCreateBean: 没有就创建bean
3.populateBean: 创建完了以后,要填充属性
4.addSingleton: 填充完了以后,再添加到容器进行使用

public class A {
    private B b;

    public B getB(){
        return b;
    }

    public void setB(B b){
        this.b = b;
    }

    public A(){
        System.out.println("---A created success");
           }
}
 
 
public class B {
    private A a;

    public A getA(){
        return a;
    }

    public void setA(A a){
        this.a = a;
    }


    public B(){
        System.out.println("---B created success");
      
    }
}
 
 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--
        1.spring容器默认的单例模式可以解决循环引用,单例默认支持
        2.spring容器原型依赖模式scope="prototype"多例模式下不能解决循环引用
    -->

    <!--depends-on 的意思就是当前这个bean如果要完成,先看depends-on指定的bean是否已经完成了初始化-->
    <!--scope="prototype"代表每次都要新建一次对象-->


    <bean id="a" class="com.hhf.study.spring.circulardepend.A" >
        <property name="b" ref="b"/>
    </bean>

    <bean id="b" class="com.hhf.study.spring.circulardepend.B">
        <property name="a" ref="a"/>
    </bean>
</beans>
 

A/B对象的迁移

1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B

2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A

3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

截屏2020-11-11 下午9.59.16

断点调试

这里由于笔记太麻烦,所以已抓图的方式记录

截屏2020-11-11 下午10.07.31

调试流程

image-20201111221108395

调试总结

创建bean过程

Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程,不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成

为什么构造器无法解决循环依赖

Spring解决循环依赖依靠的是Bean的“中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态……>半成品。实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
Spring为了解决单例的循环依赖问题,使用了三级缓存其中一级缓存为单例池〈 singletonObjects)二级缓存为提前曝光对象( earlySingletonObjects)三级缓存为提前曝光对象工厂( singletonFactories)。
假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。

解决循环依赖流程

1 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA

2 在getSingleton()方法中,从一级缓存中查找,没有,返回null

3 doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)

4 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法

5 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中

6 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB

7 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性

8 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA

9 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中

10 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

Spring循环依赖三级缓存是指在实例化Bean的过程中,Spring框架内部维护了三个缓存Map,分别是singletonObjects、earlySingletonObjects和singletonFactories。 1. singletonObjects缓存:存放已经完全初始化的Bean实例,包括在初始化过程中有循环依赖的Bean 2. earlySingletonObjects缓存:存放已经进行了实例化但是还未完成初始化的Bean实例,也包括在初始化过程中有循环依赖的Bean 3. singletonFactories缓存:存放Bean实例的Factory对象,用于解决循环依赖的问题,当需要解决循环依赖时,由Factory对象提供未完成的Bean实例。 通过这三个缓存,Spring框架可以在Bean实例化的过程中解决循环依赖的问题。当Bean A依赖Bean B,同时Bean B也依赖Bean A时,Spring框架会通过缓存机制来避免无限递归的循环依赖。具体流程如下: 1. 当需要实例化Bean A时,Spring框架会先从singletonObjects缓存中查找是否已经存在该Bean实例,如果存在,则直接返回该实例。 2. 如果singletonObjects缓存中不存在Bean A实例,则会从earlySingletonObjects缓存中查找是否存在该实例。如果存在,则将其提供给Factory对象创建Bean A实例并返回。 3. 如果earlySingletonObjects缓存中也不存在Bean A实例,则会从singletonFactories缓存中查找是否存在该实例的Factory对象,如果存在,则使用该Factory对象创建Bean A实例并返回。 4. 如果singletonFactories缓存中也不存在Bean A实例的Factory对象,则需要创建一个新的Factory对象,同时将其存放到singletonFactories缓存中。这个新的Factory对象会提供一个未进行初始化的Bean A实例,同时缓存到earlySingletonObjects中。 5. 当Bean A实例被完全初始化后,会将其从earlySingletonObjects缓存中移动到singletonObjects缓存中,以供其他Bean依赖使用。 通过三级缓存的机制,Spring框架可以解决循环依赖的问题,并在Bean实例化过程中避免无限递归的循环依赖
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值