Spring循环依赖决策

循环依赖是什么样

  1. 我们自己手动创建多例对象,像这样
public class A{
    private B beanB;
    public A(){
        beanB = new B();
    }
}
public class B{
    private A beanA;
    public B(){
        beanA = new A();
    }
}

这种情况,每次创建对象都是构造函数相互依赖,他们是实例化不同的对象,这种情况一直持续下去,在内存不断构造新的对象出来,直到内存耗尽。

  1. 手动创建单例对象,像这样
public class Test {
    public static void main(String[] args) {
        A a = A.getInstance();
        System.out.println("Test中实例化A:" + a);
    }
}
class A {
    private static final A a = new A();
    private B b;

    private A() {
        b = B.getInstance();
        System.out.println("A中实例化B:" + b);
    }
    public static A getInstance() {
        return a;
    }
}
class B {
    private static final B b = new B();
    private A a;

    private B() {
        a = A.getInstance();
        System.out.println("B中实例化A:" + a);
    }
    public static B getInstance() {
        return b;
    }
}

输出结果:

B中实例化A:null
A中实例化B:com.plg.jietiao.B@6b2fad11
Test中实例化A:com.plg.jietiao.A@79698539

这种情况,实例化貌似顺利完成了,但是如果在实例化过程中,使用到对象a,那就会有空指针的问题,毕竟我们实例化对象之后,就是为了要使用它。

  1. 使用常用的spring框架创建单例对象,把对象的实例化过程交给spring框架来完成,像这样
public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}
public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}
<?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-4.0.xsd">
    
    <bean id="beanA" class="com.test.bean.A" scope="singleton">
        <constructor-arg ref="beanB"/>
    </bean>
    <bean id="beanB" class="com.test.bean.B" scope="singleton">
        <constructor-arg ref="beanA"/>
    </bean>
</beans>

在两个对象的构造函数中直接去实例化另外一个单例对象,造成循环构造的情况,这种情况spring直接抛出BeanCurrentlyInCreationException异常,程序执行中止。

  1. 使用spring框架创建单例对象,通过属性设置的方式循环依赖,像这样
public class A{
    private B beanB;
    public void setBeanB(B beanB) {
        this.beanB = beanB;
    }
}
public class B{
    private A beanA;
    public void setBeanA(A beanA) {
        this.beanA = beanA;
    }
}
<?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-4.0.xsd">
    
    <bean id="beanA" class="com.baidu.shopping.A">
        <property name="beanB" ref="beanB"/>
    </bean>
    
    <bean id="beanB" class="com.baidu.shopping.B">
        <property name="beanA" ref="beanA"/>
    </bean>
</beans>

在实例化一个对象的过程中,对属性赋值,依赖于另外一个对象的实例化,而非构造函数去依赖另外一个对象,即使这些对象都是单例。这种情况是可以顺序完成对象的实例化过程。

通过以上列举4种情况来看,只有第4种解决循环依赖的方式,可以顺利完成。那么spring在对象实例化的过程中,是如何解决对象之间的循环依赖问题的?

对象实例化关键步骤

首先要分析一下,spring在创建对象的整个过程中,有哪些关键的步骤
在这里插入图片描述
最主要步骤2、4、7、8这几个步骤

  • 步骤2:调用类的构造方法,完成实例化对象
  • 步骤4:通过setter,设置对象属性
  • 步骤7:调用对象的afterPropertiesSet()方法
  • 步骤8:调用spring xml中的init方法。

循环依赖主要发生在第一、第二步。也就是构造器循环依赖和属性循环依赖。接下来我们具体看看spring是如何处理循环依赖。

构造器循环依赖

构造器循环依赖,这种方式构造对象,是没办法完成的,spring会直接抛出对象创建异常BeanCurrentlyInCreationException,spring是如何检测这种构造器循环依赖的呢?

spring在DefaultSingletonBeanRegistry类中,定义了一个私有集合:

/** Names of beans that are currently in creation */
private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));

每次在实例化对象之前,都会先调用这个集合的add方法,看如下代码:

	/**
	 * Callback before singleton creation.
	 * <p>The default implementation register the singleton as currently in creation.
	 * @param beanName the name of the singleton about to be created
	 * @see #isSingletonCurrentlyInCreation
	 */
	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

将正在实例化对象的类完成路径,存放在这个集合中,这个集合相当于一个创建bean的池子,所有正在被创建的beanName都会缓存在这个集合中,注意是正在创建。

如果add方法中检测到当前实例化对象的类完成路径,已经存在了,就会返回false,程序抛出异常,启动中止。

如果正在创建的beanName不存在于集合中,就会add成功,才会执行实例化对象之后的逻辑,这个beforeSingletonCreation方法被调用是在上图中的步骤1,方法postProcessBeforeInstantiation()调用之前。

beforeSingletonCreation方法正常检测通过之后,就执行上图中对象的创建过程,一直到步骤9:postProcessAfterInitialization()方法执行完成,

单例对象创建完成之后,开始执行afterSingletonCreation方法:

	/**
	 * Callback after singleton creation.
	 * <p>The default implementation marks the singleton as not in creation anymore.
	 * @param beanName the name of the singleton that has been created
	 * @see #isSingletonCurrentlyInCreation
	 */
	protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

此时就会从singletonsCurrentlyInCreation集合中删除当前正在实例化的对象beanName,删除成功之后,就执行spring其他的步骤,其他的步骤不是本文关注的重点,忽略。

设想实例化A的时候构造函数依赖B,A->B,此时A和B都会在singletonsCurrentlyInCreation集合中,在实例化B的时候构造函数依赖A,B->A,此时检测到A已经在集合中存在,抛出异常,结束程序。所以无论是创建单例还是多例对象,只要是构造器依赖,spring都会抛出异常,区别在於单例是在启动的过程中抛出异常,多例对象,程序启动的时候不会抛出异常,等到真正从spring容器中获取实例对象的时候,才会抛出异常,因为多例对象的实例化是延迟到程序使用对象的时候。

属性循环依赖

也就是通过类的setter方法循环依赖。那为什么spring在构造器循环依赖的情况,实例化对象会失败,通过属性赋值的方式循环依赖,就可以完成实例化对象呢?

这种情况,spring引入了3个Map集合来解决这个问题,DefaultSingletonBeanRegistry类中,定义了3个私有集合,分别是:

	/** Cache of singleton objects: bean name --> bean instance */
	/** 一级缓存:用于存放完全实例化完成的 bean **/
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

	/** Cache of early singleton objects: bean name --> bean instance */
	/** 二级缓存:循环对象依赖列表,对象在创建之后,进行注入过程中,发现产生了循环依赖,那么会将对象放入到这个队列,并且从三级缓存singletonFactories中移除掉。 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
	
	/** Cache of singleton factories: bean name --> ObjectFactory */
	/** 三级级缓存:存放 bean 工厂对象,用于解决循环依赖 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

首先看看涉及到这几个集合变量的关键代码:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized(this.singletonObjects) {
        	//代码编号1
        	//一级缓存singletonObjects不包含当前beanName,说明当前bean还没有实例化完成
            if (!this.singletonObjects.containsKey(beanName)) {
            	//代码编号2
            	//把当前正在实例化的bean,缓存在三级缓存singletonFactories中
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

首先的问题是这个方法addSingletonFactory,在对象实例化的什么阶段被调用的?看看上面对象被创建的几个关键步骤图,这是发生在步骤3:调用bean的构造函数之后,步骤4:调用populateBean方法之前,populateBean方法就是进行属性值的赋值操作,这个方法最主要就是把当前已经调用构造函数的bean,缓存在三级缓存singletonFactories中,表示当前这个bean已经被构造出来了,只是还没有进行bean属性赋值和初始化的操作。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//代码编码1
		//从一级缓存singletonObjects中检查当前正在创建的bean所依赖的bean,是否已经被实例化完成,如果已经实例化完成了,直接退出当前方法
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
            	//代码编码2
            	//从二级缓存earlySingletonObjects中检查当前正在创建的bean所依赖的bean
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                	//代码编码3
                	//从三级缓存singletonFactories中检查当前正在创建的bean所依赖的bean,是否已经构造完成
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //代码编码4
                        //当前正在创建的bean所依赖的bean,也正在实例化,刚刚构造出来,还没有进行属性赋值阶段,将依赖的bean缓存在二级缓存earlySingletonObjects中,并将依赖的bean从三级缓存中移除
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject != NULL_OBJECT ? singletonObject : null;
    }

看看上面这段代码

  • 第一个问题是要搞明白这个方法getSingleton,在对象创建的什么阶段调用的?getSingleton方法被调用是发生在populateBean()阶段,也就是在给对象属性赋值的阶段。
  • 第二个问题这个getSingleton方法的参数beanName,表示的是当前正在实例化的bean,还是当前正在实例化的bean所依赖的bean:不是表示当前正在创建的bean,而是当前正在创建的bean所依赖的bean,比如以下代码中,当前正在实例化beanB,如果beanB属性值依赖了beanA,则此时这个beanName应该是beanA,
	<bean id="beanB" class="com.test.bean.B" scope="singleton">
        <property name="a" ref="beanA"/>
    </bean>
  • 第三个问题就是这段代码在干嘛,已经在代码的注释中注明了。beanName的检测是从一级缓存singletonObjects开始,如果一级缓存不存在,就检测二级缓存earlySingletonObjects,如果二级缓存也不存在,则检测三级缓存singletonFactory,如果三级缓存中存在,就将对象直接从三级缓存移除,放到二级缓存中。那就会有下一个问题,什么时候从二级缓存移动到一级缓存
  • 第四个问题bean对象什么时候从二级缓存移动到一级缓存?看看以下代码先,addSingleton的调用是发生在什么阶段呢?发生在步骤9之后,也就是调用postProcessAfterInitialization()方法之后,这个时候,当前正在实例化的bean接近尾声,已经完成了构造、属性赋值、初始化等操作。addSingleton这个方法要做的事情就是:把当前正在实例化的bean存放到一级缓存singletonObjects中,并从二级缓存和三级缓存中移除,这就表示当前这个bean已经完成实例化的过程。
protected void addSingleton(String beanName, Object singletonObject) {
        synchronized(this.singletonObjects) {
        	//代码编码1
        	//存放到一级缓存singletonObjects中
            this.singletonObjects.put(beanName, singletonObject != null ? singletonObject : NULL_OBJECT);
            //从三级缓存singletonFactories中移除
            this.singletonFactories.remove(beanName);
            //从二级缓存earlySingletonObjects中移除
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }

总结属性循环依赖
检测循环依赖的过程如下:

  • A创建过程中,A将自己放到三级缓里面,赋值阶段发现需要B,于是去实例化B
  • B实例化的时候,B先将自己放到三级缓里面 ,赋值阶段发现需要A,接着B先查一级缓存有没有A,如果没有,再查二级缓存,还是没有,再查三级缓存找A,找到了。
    • 然后把三级缓存里面的A放到二级缓存里面,并删除三级缓存里面的A
    • B顺利初始化完毕,B将自己从三级缓存中移除,并放到一级缓存里面(此时B里面的A依然是创建中状态)
  • 然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B ,A完成创建,
  • 接着A将自己从二级缓存中移除,并将自己放到一级缓存里面,这样便解决了属性赋值循环依赖的问题。

第二级缓存能不能取消

回顾上面的这个过程,发现二级缓存只是缓存了被循环依赖的bean,比如A依赖B,B依赖C,C依赖A,先构造对象A,再构造B,再构造C,整个过程中二级缓存中只缓存了A,那么有个问题,二级缓存能不能取消掉,只用到一级缓存和三级缓存来解决循环依赖,我想了一下,貌似可以取消。

此处说到可以取消二级缓存,是因为本文举例都是普通的对象,不涉及到代理对象,如果有代理对象加入,看看会是什么情况。回头看看二级缓存和三级缓存存储的对象的区别

	/** 二级缓存:循环对象依赖列表,对象在创建之后,进行注入过程中,发现产生了循环依赖,那么会将对象放入到这个队列,并且从三级缓存singletonFactories中移除掉。 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
	
	/** 三级级缓存:存放 bean 工厂对象,用于解决循环依赖 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
  • 二级缓存earlySingletonObjects Map
    • key存储的是beanName
    • value类型是Object,存储的是实例化出来的对象,包括普通对象或者代理对象
  • 三级缓存singletonFactories Map
    • key存储的是beanName
    • value类型ObjectFactory,存储的是ObjectFactory接口的匿名类,他是一个对象工厂,只有一个方法getObject(),可以改变类的实例化结果,比如一个普通对象,通过这个工厂之后,返回一个代理对象

再一次看看查找对象的代码

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                    	//代码编码1
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject != NULL_OBJECT ? singletonObject : null;
    }

具体看看代码编码1处,如果涉及到代理对象,singletonFactory.getObject()此处就会创建出来依赖对象的一个代理类出来(至于为什么不是本文分析的重点,忽略),依赖对象的代理类被存放到了二级缓存earlySingletonObjects中,key依然是beanName,value是依赖对象的代理类。

试想我们把二级缓存和三级缓存合成一个缓存,遇到代理类的情况,Map集合的key都是beanName,原始对象和代理对象形成相互覆盖,不能区分出我是想要代理对象还是原始对象,三级缓存中存储的对象是一个半成品,这个半成品转移到二级缓存之后,就有两种形态存在,一种是本来的对象,这种就不涉及代理,第二种就是代理对象,这种就涉及到代理,最后被移动到一级缓存中,也是代理对象。

参考

一文说透 Spring 循环依赖问题
spring是如何解决循环依赖的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章