循环依赖之三级缓存

2022-07-28,,

循环依赖之三级缓存

文章目录

  • 循环依赖之三级缓存
    • 循环依赖概念
    • 三大Map和四大方法
    • A/B对象的迁移
    • 断点调试
    • 调试总结
      • 创建bean过程
      • 为什么构造器无法解决循环依赖
      • 解决循环依赖流程

文档参考周老师的资料

循环依赖概念

官方概念:

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).

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

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

三大Map和四大方法

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

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

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

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

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

同样可以理解为:

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

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

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

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

四大方法

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自己放到一级缓存里面。

断点调试

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

调试流程

调试总结

创建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从二级缓存移动到一级缓存中

本文地址:https://blog.csdn.net/a303015136/article/details/109632794

《循环依赖之三级缓存.doc》

下载本文的Word格式文档,以方便收藏与打印。