Spring源码解析-六、spring单例如何解决循环依赖

什么叫循环依赖

循环依赖即两个及以上的bean对象互相持有对方的引用,最终造成一个闭环。web

spring如何处理正在建立的Bean

Spring容器会将每个正在建立的Bean 标识符放在一个“当前建立Bean池”中,Bean标识符在建立过程当中将一直保持
在这个池中,所以若是在建立Bean过程当中发现本身已经在“当前建立Bean池”里时将抛出
BeanCurrentlyInCreationException异常表示循环依赖;而对于建立完毕的Bean将从“当前建立Bean池”中清除掉。spring

循环依赖的三种状况

构造器

public class BeanA {
    private BeanB b;

    public BeanA(BeanB b) {
        this.b = b;
    }
}

public class BeanB {
    private BeanC c;

    public BeanB(BeanC c) {
        this.c = c;
    }
}

public class BeanC {
    private BeanA a;

    public BeanC(BeanA a) {
        this.a = a;
    }
}

----------------------
 <bean id="a" class="com.raycloud.dmj.data.utils.BeanA">
        <constructor-arg index="0" ref="b"/>
    </bean>
    <bean id="b" class="com.raycloud.dmj.data.utils.BeanB">
        <constructor-arg index="0" ref="c"/>
    </bean>
    <bean id="c" class="com.raycloud.dmj.data.utils.BeanC">
        <constructor-arg index="0" ref="a"/>
    </bean>
----------------------
   ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

报错:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?缓存

Spring容器先建立单例BeanA,BeanA依赖BeanB,而后将A放在“当前建立Bean池”中,此时建立BeanB,BeanB依赖BeanC ,而后将B放在“当前建立Bean池”中,此时建立BeanC,StudentC又依赖BeanA, 可是,此时BeanA已经在池中,因此会报错,,由于在池中的Bean都是未初始化完的,因此会依赖错误 ,(初始化完的Bean会从池中移除)svg

单例setter

public class BeanA {
    public BeanB b;

    public void setB(BeanB b) {
        this.b = b;
    }
}
public class BeanB {
    public BeanC c;

    public void setC(BeanC c) {
        this.c = c;
    }
}
public class BeanC {
    public BeanA a;

    public void setA(BeanA a) {
        this.a = a;
    }
}
----------------------
<bean id="a" class="com.raycloud.dmj.data.utils.BeanA">
        <property name="b" ref="b"/>
    </bean>
    <bean id="b" class="com.raycloud.dmj.data.utils.BeanB">
        <property name="c" ref="c"/>
    </bean>
    <bean id="c" class="com.raycloud.dmj.data.utils.BeanC">
        <property name="a" ref="a"/>
    </bean>
----------------------
 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        BeanA a = context.getBean("a", BeanA.class);
        BeanB b = context.getBean("b", BeanB.class);
        BeanC c = context.getBean("c", BeanC.class);
        System.out.println(String.format("a:%s,a.b:%s",a,a.b));
        System.out.println(String.format("b:%s,b.c:%s",b,b.c));
        System.out.println(String.format("c:%s,c.a:%s",c,c.a));

输出:
a:com.raycloud.dmj.data.utils.BeanA@46bfc63c,a.b:com.raycloud.dmj.data.utils.BeanB@586fb16d
b:com.raycloud.dmj.data.utils.BeanB@586fb16d,b.c:com.raycloud.dmj.data.utils.BeanC@ce99877
c:com.raycloud.dmj.data.utils.BeanC@ce99877,c.a:com.raycloud.dmj.data.utils.BeanA@46bfc63c
能够看到单例setter循环依赖没有报错,且循环的依赖都成功set。具体实现原理后面详细看this

原型setter

<bean id="a" class="com.raycloud.dmj.data.utils.BeanA"  scope="prototype">
        <property name="b" ref="b"/>
    </bean>
    <bean id="b" class="com.raycloud.dmj.data.utils.BeanB"  scope="prototype">
        <property name="c" ref="c"/>
    </bean>
    <bean id="c" class="com.raycloud.dmj.data.utils.BeanC"  scope="prototype">
        <property name="a" ref="a"/>
    </bean>

一样报错:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?prototype

所以能够知道多例模式下也不能解决循环依赖。
为何?
对于“prototype”做用域Bean,Spring容器没法完成依赖注入,由于“prototype”做用域的Bean,Spring容
器不进行缓存,所以没法提早暴露一个建立中的Bean。code

单例模式如何解决循环依赖

在这里插入图片描述
如图,set方法注入,spring先实例化Bean[经过无参构造器],在设置属性,这样就不会报错了。
具体如何解决循环依赖
以咱们的实例来讲,spring会先实例化a,b,c,并放入一个map,而后设置属性,a设置属性b,只须要从mao中取出b便可,以此类推。orm

而事实上spring可不止这一个map,而是经过三级缓存来解决单例Bean的循环依赖。xml

三级缓存

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
 
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

singletonObjects:存放单例对象实例的缓存
earlySingletonObjects:存在提早曝光的bean,也就是正在建立中的bean。
singletonFactories :建立单例对象的工厂对象

实现原理

实现原理的代码以下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先从单例缓存中取
		Object singletonObject = this.singletonObjects.get(beanName);
		//一级缓存中没有,而且判断正在建立中【好比A的构造器依赖B,或者已经实例化正在setB  ,因此先去建立B,那么A就是建立中】
		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) {
						singletonObject = singletonFactory.getObject();
						//放入二级缓存
						this.earlySingletonObjects.put(beanName, singletonObject);
						//移除三级缓存
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

简单来讲: 在建立单例Bean的时候是这样解决循环依赖的。 假设A B互相依赖。 先经过createBean建立A,会先走createInstance来实例化A,而后把A的单例工厂放到三级缓存,实例化后须要设置属性,发现须要B,可是B没有初始化,所以经过createBean建立,一样须要实例化,实例化之后发现依赖A,所以先去单例缓存中找,由于A还在建立中,因此找不到,而后去二级缓存找,依旧找不到,所以最后经过单例工厂建立获取了A,B就建立好了,B建立好了,就set给A,此时A.B都实例化成功了。