深入理解SpringIOC(三) ---- Spring如何解决循环依赖

引言

1. 什么是循环依赖?

所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。它们之间的依赖关系如下:
在这里插入图片描述

当我们的平时写代码出现一下逻辑的时候,

public class HelloWorld {

	 public static void main(String[] args) {
	  // write your code here
	  A a = new A();
	  System.out.println(a);
	 } 
}
	
class A { 
 private B b = new B();
}

class B {
 private A a = new A();
}

会报出(超出栈的深度错误)在这里插入图片描述但是当我们在spring中出现如下代码时

public class BeanB {
   @Autowired
    private BeanA beanA; 
}

public class BeanA {
   @Autowired
    private BeanB beanB;
}

IOC 容器在读到上面的配置时,会按照顺序,先去实例化 beanA。然后发现 beanA 依赖于 beanB,接在又去实例化 beanB。实例化 beanB 时,发现 beanB 又依赖于 beanA。如果容器不处理循环依赖的话,容器会无限执行上面的流程,直到内存溢出,程序崩溃。当然,Spring 是不会让这种情况发生的。在容器再次发现 beanB 依赖于 beanA 时,容器会获取 beanA 对象的一个早期的引用(early reference),并把这个早期引用注入到 beanB 中,让 beanB 先完成实例化。beanB 完成实例化,beanA 就可以获取到 beanB 的引用,beanA 随之完成实例化。

补充:早期引用:所谓的”早期引用“是指向原始对象的引用。所谓的原始对象是指刚创建好的对象,但还未填充属性。里面的属性全是Null

注意: 在Spring中一共有三种循环依赖,构造器循环依赖,Setter循环依赖,和Prototype作用域的循环依赖,对于这三种循环依赖,Spring并不是都解决的.

通过一个表格来描述:

名称 是否可解决循环依赖
构造器循环依赖
Setter循环依赖
Prototype作用域的循环依赖

那么下来让我们来具体说明spring是如何解决循环依赖的,为什么有的又不能解决

简单叙述(Spring如何解决单例对象的循环依赖)

1. 在解释我们的疑问之前,我们先来回顾一下IOC创建Bean的流程

在这里插入图片描述
首先我来按照上面的图,分析一下整个流程的执行顺序。这个流程从 getBean 方法开始,getBean 是个空壳方法,所有逻辑都在 doGetBean 方法中。doGetBean 首先会调用 getSingleton(beanName) 方法获取 sharedInstance,sharedInstance 可能是完全实例化好的 bean,也可能是一个原始的 bean,当然也有可能是 null。

  1. 如果不为 null,则走绿色的那条路径。再经 getObjectForBeanInstance 这一步处理后,绿色的这条执行路径就结束了。

  2. 如果 sharedInstance = null 的情况,则走红色的那条路径。在第一次获取某个 bean 的时候,缓存中是没有记录的,所以这个时候要走创建逻辑。上图中的 getSingleton(beanName,new ObjectFactory() {…}) 方法会创建一个 bean 实例,上图虚线路径指的是 getSingleton 方法内部调用的两个方法,其逻辑如下:getSingleton 会在内部先调用 getObject 方法创建 singletonObject,然后再调用 addSingleton 将 singletonObject 放入缓存中。getObject 在内部代用了 createBean 方法,createBean 方法基本上也属于空壳方法,更多的逻辑是写在 doCreateBean 方法中的。doCreateBean 方法中的逻辑很多,其首先调用了 createBeanInstance 方法创建了一个原始的 bean 对象,随后调用 addSingletonFactory 方法向缓存中添加单例 bean 工厂,从该工厂可以获取原始对象的引用,也就是所谓的“早期引用”。再之后,继续调用 populateBean 方法向原始 bean 对象中填充属性,并解析依赖。getObject 执行完成后,会返回完全实例化好的 bean。紧接着再调用 addSingleton 把完全实例化好的 bean 对象放入缓存中。

2. 简单说明spring如何解决循环依赖

我们先实例化A,实例化好后,调用addSingletonFactory放入三级缓冲池中,然后此时我们设置属性的时候会发现我们还依赖B,于是我们就先去实例化B后并为进行属性赋值,而当我们进行赋值的时发现B也依赖A,但是之前已经把A存放在singletonFactories,那么B会将其取出并装配自己,实例化以后把自己放入单例池中,然后A也就可以继续进行属性赋值了,最后A 实例好以后,将自己也放入单例池中,这样Spring就解决了循环依赖。

图解:
在这里插入图片描述

代码分析

protected <T> T doGetBean(
        final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
        throws BeansException {

    // ...... 

    // 从缓存中获取 bean 实例
----》    Object sharedInstance = getSingleton(beanName);

    // ......
}

//被上面调用
public Object getSingleton(String beanName) {
----》    return getSingleton(beanName, true);
}


//被上面调用
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从 singletonObjects 获取实例,singletonObjects 中的实例都是准备好的 bean 实例,可以直接使用
    Object singletonObject = this.singletonObjects.get(beanName);
    // 判断 beanName 对应的 bean 是否正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 从 earlySingletonObjects 中获取提前曝光的 bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 获取相应的 bean 工厂
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 提前曝光 bean 实例(raw bean),用于解决循环依赖
                    singletonObject = singletonFactory.getObject();

                    // 将 singletonObject 放入缓存中,并将 singletonFactory 从缓存中移除
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

上面的源码中,doGetBean 所调用的方法 getSingleton(String) 是一个空壳方法,其主要逻辑在 getSingleton(String, boolean) 中。该方法逻辑比较简单,首先从 singletonObjects 缓存中获取 bean 实例。若未命中,再去 earlySingletonObjects 缓存中获取原始 bean 实例。如果仍未命中,则从 singletonFactory 缓存中获取 ObjectFactory 对象,然后再调用 getObject 方法获取原始 bean 实例的应用,也就是早期引用。获取成功后,将该实例放入 earlySingletonObjects 缓存中,并将 ObjectFactory 对象从 singletonFactories 移除。

看完这个方法,我们再来看看 getSingleton(String, ObjectFactory) 方法,这个方法也是在 doGetBean 中被调用的。这次我会把 doGetBean 的代码多贴一点出来,如下:

protected <T> T doGetBean(
        final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
        throws BeansException {

    // ...... 
    Object bean;

    // 从缓存中获取 bean 实例
    Object sharedInstance = getSingleton(beanName);

    // 如果找到了
    if (sharedInstance != null && args == null) {
        // 进行后续的处理
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
    //否则
        // ......
        // mbd.isSingleton() 用于判断 bean 是否是单例模式
        if (mbd.isSingleton()) {
            // 再次获取 bean 实例
----》            sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
		                @Override
		                public Object getObject() throws BeansException {
		                    try {
		                        // 创建 bean 实例,createBean 返回的 bean 是完全实例化好的
		                        return createBean(beanName, mbd, args);
		                    } catch (BeansException ex) {
		                        destroySingleton(beanName);
		                        throw ex;
		                    }
		                }
		            });
	    // 进行后续的处理
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }

        // ......
    }

    // ......

    // 返回 bean
    return (T) bean;
}


//被上面调用
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {

        // ......
        // 调用 getObject 方法创建 bean 实例
        singletonObject = singletonFactory.getObject();
        newSingleton = true;

        if (newSingleton) {
            // 添加 bean 到 singletonObjects 缓存中,并从其他集合中将 bean 相关记录移除
------》            addSingleton(beanName, singletonObject);
        }

        // ......

        // 返回 singletonObject
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}


//被上面调用
protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 将 <beanName, singletonObject> 映射存入 singletonObjects 中
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

        // 从其他缓存中移除 beanName 相关映射
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

上面的代码中包含两步操作,第一步操作是调用 getObject 创建 bean 实例,第二步是调用 addSingleton 方法将创建好的 bean 放入缓存中。代码逻辑并不复杂,相信大家都能看懂。

那么接下来我们继续往下看,这次分析的是 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,该变量一般为 true。
 */
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        //  添加 bean 工厂对象到 singletonFactories 缓存中
-----》        addSingletonFactory(beanName, new ObjectFactory<Object>() {
			            @Override
			            public Object getObject() throws BeansException {
			            /* 
			             * 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP 
			             * 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 
			             * bean      
			             */
			                return getEarlyBeanReference(beanName, mbd, bean);
			            }
			        });
     }
			
    Object exposedObject = bean;

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

    // ......

    // 返回 bean 实例
    return exposedObject;
}

//被上面调用
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. 创建原始 bean 实例 createBeanInstance(beanName, mbd, args)
  2. 添加原始对象工厂对象到 singletonFactories 缓存中
    addSingletonFactory(beanName, new ObjectFactory{…})
  3. 填充属性,解析依赖 populateBean(beanName, mbd, instanceWrapper)

举例说明

这里以 BeanA 和 BeanB 两个类相互依赖为例。在上面的方法调用中,有几个关键的地方,下面一一列举出来:

在容器初始化Bean的过程中,假设IOC先初始化A

1. 先创建BeanA的原生对象

instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

假设 beanA 先被创建,创建后的原始对象为 BeanA@1234,上面代码中的 bean 变量指向就是这个对象。

2. 暴露早期引用

addSingletonFactory(beanName, new ObjectFactory<Object>() {
    @Override
    public Object getObject() throws BeansException {
        return getEarlyBeanReference(beanName, mbd, bean);
    }
});

beanA 指向的原始对象创建好后,就开始把指向原始对象的引用通过 ObjectFactory 暴露出去。getEarlyBeanReference 方法的第三个参数 bean 指向的正是 createBeanInstance 方法创建出原始 bean 对象 BeanA@1234。

3. 解析依赖

populateBean(beanName, mbd, instanceWrapper);

populateBean 用于向 beanA 这个原始对象中填充属性,当它检测到 beanA 依赖于 beanB 时,会首先去实例化 beanB。beanB 在此方法处也会解析自己的依赖,当它检测到 beanA 这个依赖,于是调用 BeanFactry.getBean(“beanA”) 这个方法,从容器中获取 beanA。

4. 获取早期引用

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //  从二级缓存中获取早期引用
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //  从 SingletonFactory 中获取早期引用
                    singletonObject = singletonFactory.getObject();

                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

接着上面的步骤讲,populateBean 调用 BeanFactry.getBean(“beanA”) 以获取 beanB 的依赖。getBean(“beanA”) 会先调用 getSingleton(“beanA”),尝试从缓存中获取 beanA。此时由于 beanA 还没完全实例化好,于是 this.singletonObjects.get(“beanA”) 返回 null。接着 this.earlySingletonObjects.get(“beanA”) 也返回空,因为 beanA 早期引用还没放入到这个缓存中。最后调用 singletonFactory.getObject() 返回 singletonObject,此时 singletonObject != null。singletonObject 指向 BeanA@1234,也就是 createBeanInstance 创建的原始对象。此时 beanB 获取到了这个原始对象的引用,beanB 就能顺利完成实例化。beanB 完成实例化后,beanA 就能获取到 beanB 所指向的实例,beanA 随之也完成了实例化工作。由于 beanB.beanA 和 beanA 指向的是同一个对象 BeanA@1234,所以 beanB 中的 beanA 此时也处于可用状态了。

补充:populateBean 调用 BeanFactry.getBean(“beanA”) 的过程

---populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw)
    --- this.applyPropertyValues(beanName, mbd, bw, (PropertyValues)pvs);
        ---valueResolver.resolveValueIfNecessary(pv, originalValue);
             ---this.resolveReference(argName, ref);
                ---this.beanFactory.getBean(refName);

5. 最后我们再看一次流程图加深一下理解

在这里插入图片描述

补充

1. 为什么Spring无法解决构造器注入的循环依赖

我们在上面已经分析过了,Spring解决循环依赖就是通过先缓存一个原生对象,然后发现存在依赖时,先去创建依赖的对象,当依赖的对象发现循环依赖时,就去二级、三级缓冲池去找,而这个时候发现存在,就用原生对象完成属性填充,当被依赖的对象完成最终的实例化后,再完成自己的最终实例化(属性填充)。但是如果我们使用构造器注入的话,在初始化的时候就需要填充依赖,如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestA类,则又去创建TestB,从而形成一个环,没办法创建。

注意:Spring容器会将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持
在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出
BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

2. 为什么Spring无法解决原型(prototype)对象的循环依赖

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

发布了45 篇原创文章 · 获赞 3 · 访问量 2308

猜你喜欢

转载自blog.csdn.net/weixin_44046437/article/details/100046585