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

什么叫循环依赖

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

spring如何处理正在创建的Bean

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

循环依赖的三种情况

构造器

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会从池中移除)

单例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。具体实现原理后面详细看

原型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”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容
器不进行缓存,因此无法提前暴露一个创建中的Bean。

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

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

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

三级缓存

/** 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都实例化成功了。

猜你喜欢

转载自blog.csdn.net/qq_28605513/article/details/85536632