Spring源码深度解析(二)---循环依赖问题

什么是循环依赖

关于Spring bean的创建,其本质上还是一个对象的创建,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化

在Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过一定的方式设置的。

可以看到,这里A和B中各自都以对方为自己的全局属性。这里首先需要说明的一点是,Spring实例化bean是通过ApplicationContext.getBean()方法来进行的。

首先Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于Spring容器中还没有A对象实例,因而其会创建一个A对象,然后发现其依赖了B对象,因而会尝试递归的通过ApplicationContext.getBean()方法获取B对象的实例,但是Spring容器中此时也没有B对象的实例,因而其还是会先创建一个B对象的实例。

此时A对象和B对象都已经创建了,并且保存在Spring容器中了,只不过A对象的属性b和B对象的属性a都还没有设置进去。

在前面Spring创建B对象之后,Spring发现B对象依赖了属性A,递归调用时发现 Bean 还在创建中即为循环依赖

此时,B对象的属性a就设置进去了,然后还是ApplicationContext.getBean()方法递归的返回,也就是将B对象的实例返回,此时就会将该实例设置到A对象的属性b中。

前面在为对象B设置属性a的时候,这个A类型属性还是个半成品。

而在上面这个递归过程的最后,Spring将获取到的B对象实例设置到了A对象的属性b中了,这里的A对象其实和前面设置到实例B中的半成品A对象是同一个对象,其引用地址是同一个,这里为A对象的b属性设置了值,其实也就是为那个半成品的a属性设置了值

1、setter方式 - 单例

配置文件为setter方式单例注入

	<!--scope="singleton"(默认就是单例方式)  -->
	<bean id="a" class="com.zfx.student.StudentA" scope="singleton">
		<property name="studentB" ref="b"></property>
	</bean>
	<bean id="b" class="com.zfx.student.StudentB" scope="singleton">
		<property name="studentC" ref="c"></property>
	</bean>
	<bean id="c" class="com.zfx.student.StudentC" scope="singleton">
		<property name="studentA" ref="a"></property>
	</bean>

对于单例对象来说,在Spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,Spring大量运用了Cache的手段,为了循环依赖问题的解决过程中Spring甚至使用了“三级缓存”。

三级缓存

/** 一级缓存:用于存放完全初始化好的 bean **/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** 二级缓存:存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** 三级级缓存:存放 bean 工厂对象,用于解决循环依赖 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/**
bean 的获取过程:先从一级获取,失败再从二级、三级里面获取

创建中状态:是指对象已经 new 出来了但是所有的属性均为 null 等待被 init
*/

三级缓存的用法

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Spring首先从singletonObjects(一级缓存)中尝试获取
   Object singletonObject = this.singletonObjects.get(beanName);
   // 如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取
   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) {
              //如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取
               singletonObject = singletonFactory.getObject();
               //如果获取到了则将singletonObject放入到earlySingletonObjects,也就是将三级缓存提升到二级缓存中
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return (singletonObject != NULL_OBJECT ? singletonObject : null);}

上面的代码需要解释两个参数:

  • isSingletonCurrentlyInCreation():判断当前单例bean是否正在创建中,也就是没有初始化完成(递归调用时发现 Bean 还在创建中即为循环依赖)
  • allowEarlyReference :是否允许从singletonFactories中通过getObject拿到对象

分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:

this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;}

在bean创建过程中,有两处比较重要的匿名内部类实现了该接口。一处是Spring利用其创建bean的时候,另一处就是:

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

检测循环依赖的过程如下:

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

spring解决bean的循环依赖问题为何需要三级缓存,而不是两级缓存?

其实按道理是可以二级缓存就可以初始化一些内容,但是在这里

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

在将三级缓存放入二级缓存的时候,会判断是否有SmartInstantiationAwareBeanPostProcessor这样的后置处理器,换句话说这里是给用户提供接口扩展的,所以采用了三级缓存。

回顾一下如何解决的?

一句话:先让最底层对象完成初始化,通过三级缓存与二级缓存提前曝光创建中的 Bean,让其他 Bean 率先完成初始化。

2、setter方式 - prototype

scope=“prototype” 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。

<bean id="a" class="com.zfx.student.StudentA" scope="prototype">
		<property name="studentB" ref="b"></property>
	</bean>
	<bean id="b" class="com.zfx.student.StudentB" scope="prototype">
		<property name="studentC" ref="c"></property>
	</bean>
	<bean id="c" class="com.zfx.student.StudentC" scope="prototype">
		<property name="studentA" ref="a"></property>
	</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容器无法完成依赖注入,因为Spring容器不进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean。可以通过“setAllowCircularReferences(false)”来禁用循环引用。

3、构造器循环依赖

 <bean id="a" class="com.zfx.student.StudentA">
		<constructor-arg index="0" ref="b"></constructor-arg>
	</bean>
	<bean id="b" class="com.zfx.student.StudentB">
		<constructor-arg index="0" ref="c"></constructor-arg>
	</bean>
	<bean id="c" class="com.zfx.student.StudentC">
		<constructor-arg index="0" ref="a"></constructor-arg>
	</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?

为什么构造器注入不能解决循环依赖问题

instantiate(实例化)其实就是理解成new一个对象的过程,而new的时候肯定要执行构造方法,所以猜想对于应该是A在instantiate(实例化)时,进行B的初始化。

因为A中构造器注入了B,那么A在关键的方法addSingletonFactory()之前就去初始化了B,导致三级缓存中根本没有A,所以会发生死循环,Spring发现之后就抛出异常了。

依赖检查

Spring容器将每一个正在创建bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中一种保存在池中,所以在创建bean时发现自己已经在“当前创建bean池”,说明存在循环依赖,抛异常;对于创建完毕的bean会从“当前创建bean池”移除。

总结

Spring对于构造器注入和使用prototype作用域的setter构成的循环依赖是无法解决的,解决循环依赖前提条件是Bean单例模式下非构造函数的循环依赖,而且要求该对象没有被代理过。Spring通过“提前曝光”机制,配合Java的对象引用原理,解决了某些情况下的循环依赖问题。

参考 http://www.jianshu.com/p/6c359768b1dc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值