zoukankan      html  css  js  c++  java
  • Spring 解决循环依赖源码分析

    什么是循环依赖
    循环依赖就是N个类之间循环嵌套引用,如A依赖B,B又依赖A,你中有我,我中有你。实例化A时发现需要B属性,于是去实例化B,发现需要A属性。。。如果Spring不对这种循环依赖进行处理程序就会无限执行,导致内存溢出、系统崩溃。

    循环依赖又分为构造器循环依赖和属性循环依赖,由于Spring不支持构造器循环依赖,会直接报错,所以接下来只讨论属性循环依赖。

    Bean实例化步骤
    Bean实例化可以大致分为三步

                         

                         

    其中循环依赖发生在实例化和注入属性这两个步骤里。

    解决方案
    实例化A时,将A这个并不完整的对象缓存起来,这样当B实例化后,注入A的时候,能够从容器中获取到A对象,完成初始化。最后将B对象注入到A中,A完成初始化。

    Spring引入了三级缓存来解决循环依赖的问题

    /** 一级缓存,缓存初始化完成的bean */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    /** 二级缓存,缓存原始bean(还未填充属性),用于解决循环依赖 */
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
    /** 三级缓存,缓存bean工厂对象,用于解决循环依赖 */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);复制代码
    1
    2
    3
    4
    5
    6
    1
    2
    3
    4
    5
    当获取单例bean时,会依次访问一级缓存、二级缓存、三级缓存,缓存命中则返回。

    源码解析
    时序图


    getBean是从容器中获取bean的入口方法,它里面又调用了doGetBean方法,来看一下这个方法。

    doGetBean
    protected T doGetBean(final String name, @Nullable final Class requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    1
    2
    3
    // 尝试通过bean名称获取目标bean对象,比如这里的A对象
    Object sharedInstance = getSingleton(beanName);
    // 我们这里的目标对象都是单例的
    if (mbd.isSingleton()) {
    // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
    // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
    // 目标对象
    sharedInstance = getSingleton(beanName, () -&gt; {
    try {
    // 尝试创建目标对象
    <span class="hljs-built_in">return</span> createBean(beanName, mbd, args);
    } catch (BeansException ex) {
    throw ex;
    }
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    }
    return (T) bean;
    }复制代码

    这个方法里面有两个名称为getSingleton的方法,第一个getSingleton是从缓存中查找bean,如果缓存未命中,则走第二个getSingleton,尝试创建目标对象并注入依赖。

    当第一次调用doGetBean获取A对象,第一个getSingleton返回空,进入第二个getSingleton创建A对象,注入B对象。调用doGetBean获取B,第一个getSingleton返回空,进入第二个getSingleton创建B对象,获取并注入原始A对象,此时B对象初始化完成。最后将B对象注入A中,A完成初始化。

    先看第一个getSingleton的源码

    getSingleton(String beanName, boolean allowEarlyReference)
    @Nullable

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    1
    2
    3
    // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回
    Object singletonObject = this.singletonObjects.get(beanName);

    // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象
    // 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    <span class="hljs-keyword">if</span> (singletonObject == null &amp;&amp; allowEarlyReference) {

    // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的
    // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品
    ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
    <span class="hljs-keyword">if</span> (singletonFactory != null) {

    // 获取目标对象的实例
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    }
    return singletonObject;
    }复制代码

    首先从一级缓存singletonObjects获取目标对象,若不存在且目标对象被标记为创建中,从二级缓存earlySingletonObjects中获取bean。如果不存在,继续访问三级缓存singletonFactories,得到bean工厂对象,通过工厂对象获取目标对象。将目标对象放入二级缓存,删除三级缓存。

    第二个getSingleton方法

    getSingleton(String beanName, ObjectFactory<?> singletonFactory)

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {

    synchronized (this.singletonObjects) {
    // ......

    // 调用 getObject 方法创建 bean 实例
    singletonObject = singletonFactory.getObject();
    newSingleton = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">if</span> (newSingleton) {
    // 添加 bean 到 singletonObjects 缓存中,并从其他集合中将 bean 相关记录移除
    addSingleton(beanName, singletonObject);
    }

    // ......

    // 返回 singletonObject
    <span class="hljs-built_in">return</span> (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    1
    2
    3
    }复制代码

    该方法的主要逻辑是调用singletonFactory的getObject()方法创建目标对象,然后将bean放入缓存中。

    接下来看一下getObject方法的实现,由于在doGetBean中调用getSingleton时第二个参数是用匿名内部类的方式传参,实际上调用的是createBean方法,而createBean又调用了doCreateBean。

    doCreateBean
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)

    throws BeanCreationException {
    BeanWrapper instanceWrapper = null;

    // ......

    // ☆ 创建 bean 对象,并将 bean 对象包裹在 BeanWrapper 对象中返回
    instanceWrapper = createBeanInstance(beanName, mbd, args);

    // 从 BeanWrapper 对象中获取 bean 对象,这里的 bean 指向的是一个原始的对象
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

    /*
    * earlySingletonExposure 用于表示是否”提前暴露“原始对象的引用,用于解决循环依赖。
    * 对于单例 bean,该变量一般为 <span class="hljs-literal">true</span>。更详细的解释可以参考我之前的文章
    */
    boolean earlySingletonExposure = (mbd.isSingleton() &amp;&amp; this.allowCircularReferences &amp;&amp;
    isSingletonCurrentlyInCreation(beanName));
    <span class="hljs-keyword">if</span> (earlySingletonExposure) {
    // ☆ 添加 bean 工厂对象到 singletonFactories 缓存中
    addSingletonFactory(beanName, new ObjectFactory&lt;Object&gt;() {
    @Override
    public Object getObject() throws BeansException {
    /*
    * 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP
    * 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回
    * bean,所以大家可以把
    * <span class="hljs-built_in">return</span> getEarlyBeanReference(beanName, mbd, bean)
    * 等价于:
    * <span class="hljs-built_in">return</span> bean;
    */
    <span class="hljs-built_in">return</span> getEarlyBeanReference(beanName, mbd, bean);
    }
    });
    }

    Object exposedObject = bean;

    // ......

    // ☆ 填充属性,解析依赖
    populateBean(beanName, mbd, instanceWrapper);

    // ......

    // 返回 bean 实例
    <span class="hljs-built_in">return</span> exposedObject;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45

    1
    2
    3
    }

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
    if (!this.singletonObjects.containsKey(beanName)) {
    // 将 singletonFactory 添加到 singletonFactories 缓存中
    this.singletonFactories.put(beanName, singletonFactory);

    // 从其他缓存中移除相关记录,即使没有
    this.earlySingletonObjects.remove(beanName);
    this.registeredSingletons.add(beanName);
    }
    }
    1
    2
    3
    4
    5
    }复制代码

    方法的主要逻辑如下

    用createBeanInstance创建目标对象
    将对象添加到singletonFactories缓存中
    populateBean注入依赖
    总结
    Spring在实例化bean时,会先创建当前bean对象,放入缓存中,然后以递归的方式获取所依赖的属性。当注入属性时,如果出现了循环依赖则会从缓存中获取依赖对象。


    ————————————————
    版权声明:本文为CSDN博主「more than a coder」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/th1996/article/details/106092650

  • 相关阅读:
    vue里面的v-for列表循环
    浅谈Vue.use
    js 限制输入框只能输入数字的问题
    vue computed的执行问题
    前端 html 篇
    函数声明 及 名称问题
    文件读写操作
    异常以及异常处理框架探析
    使用JDBC插入数据到ORACLE,使用标识列自增列
    session超时跃出iframe并跳到登陆页面(转载)
  • 原文地址:https://www.cnblogs.com/hanease/p/14939299.html
Copyright © 2011-2022 走看看