从spring源码角度分析循环依赖bean的组装
1.循环依赖介绍
如下图循环依赖,图一,图二
2.spring对循环依赖的支持
2.1spring循环依赖的情况
- 构造器注入属性依赖(A B两个对象都使用构造方法,注入依赖的属性)
无论是单例,还是原型对象,只要是通过构造器注入的属性依赖,都会报错,循环依赖错误 org.springframework.beans.factory.BeanCurrentlyInCreationException:
原因:试想,构造器是创建对象的入口方法,构造的时候都循环依赖了,我这个对象压根就创建不了啊。那肯定是无法解决的,大罗神仙也无能为力。 - setter方法注入属性依赖
这个spring完美解决了,支持这种循环依赖
原理:创建对象A的时候,先通过无参构造方法创建一个实例,此时属性都是空的,但是对象引用已经创建出来,然后把A的引用提前暴露出来。然后setter B属性的时候,创建B对象,此时同样通过无参构造方法构造然后将对象引用暴露出来。接着B执行setter方法,去池中找A,能找到A(因为此时A已经暴露出来,有指向改对象的引用了),这么依赖B就构造完成,也初始化完成,然后A接着初始化完成。—循环依赖就这么解决了 - 原型对象的属性依赖(当然指的是通过setter方法注入依赖)
这个spring也无能为力,因为是原型对象,A创建的时候不会提前暴露出来,所以,每次都是要创建,创建的时候,发现有相同的对象正在创建,同样报错,循环依赖错误,同第一种情况类似。
2.2spring源码的解释
这里只展示关键代码:
创建bean的入口方法
1. AbstractBeanFactory–>doGetBean()
Object sharedInstance = getSingleton(beanName); //从缓存中查找,或者如果当前创建池中有并且已经暴露出来了,就返回这个对象
……
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
} 这里判断的标准就是,如果当前原型实例池中有正在创建的对象了,说明这个对象是个原型对象,并且当前线程中这个对象已经处于创建中了,还要再创建,肯定报错,抛出循环依赖异常。
……..
创建单例bean的方法:
DefaultSingletonBeanRegistry–>getSingleton(String beanName, ObjectFactory<?> singletonFactory)
……………
beforeSingletonCreation(beanName);创建单例bean之前的检查
进入 beforeSingletonCreation方法
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
} 如果当前正在创建的单例池中有这个对象,那也抛出循环依赖异常,说明了当前线程中,要创建同样的对象两次,这种情况出现的原因是,前面那个创建的时候,并没有暴露出来这个对象,这才导致了创建两次
如果beforeSingletonCreation通过验证,接下来就是真正的创建单例对象了
真正创建对象
AbstractAutowireCapableBeanFactory–>doCreateBean
……..
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
} 注意这一步很关键,是调用构造方法创建一个实例对象,如果这个构造方法有参数,而且就是循环依赖的参数,那么这个对象就无法创建了,因为到这里对象没有创建,也没有暴露当前对象,如果是无参的构造方法,那么就可以,先创建一个对象,尽管所有的属性都为空
………
这里说明了什么条件下,才会提前暴露当前创建的对象
三个条件:单例,允许循环依赖(默认就是true),在当前单例创建池中有(这个在bean入口创建的时候就会放进去的)
结合2.1中的三种情况分析:
1.首先如果是循环依赖的双方都是原型对象,那么肯定报错,因为对象无法提前暴露出来,就成了死循环。
2.如果是单例的,但是配置的确实有参数的构造器注入,那么也肯定报错,因为这里的代码就执行不到
3.如果是单例的,setter方法注入,那么肯定是可以的,因为先执行了无参构造方法创建一个实例对象,然后这里又暴露出来了,接下来就是真正的注入属性依赖了。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug(“Eagerly caching bean ‘” + beanName +
”’ to allow for resolving potential circular references”);
}
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
2.3 还要说明的几点
如果是组合呢。—尽管这种情况很少出现
1.A是单例(setter方法注入属性),B是原型—然后循环依赖呢?答案是可以注入
原因:A先实例化,A调用了无参构造方法构造出对象,然后暴露出来,接着注入B,构造B,此时A已经暴露出来了,所以无论B是setter注入,还是构造器注入,都可以注入进去,因为A相当于已经存在了
2.A是单例(构造器注入属性),B是原型—然后循环依赖?答案是不可以注入
A创建的时候,不会暴露出来,B就更不会暴露出来了,所以抛异常
2.A是单例(构造器注入),B是单例(setter方法注入)—然后循环依赖呢?答案是看实例化的顺序—
原因:
如果A先实例化:A构造器注入,构造的时候就找B,B没有实例化,然后实例化B,无参构造方法实例化B,然后将B暴露出来,接着注入A,此时就发现A已经在实例化了,又实例化,所以报错
如果B先实例化:B先构造然后暴露出来,接着A创建,依赖B,这时候能成功的构造出来A(因为B已经暴露出来了),接着A setter注入A–完成注入
3.循环依赖总结
如果循环依赖的都是单例对象(都是通过setter方式注入属性的),那么这个肯定没问题,放心使用即可
如果一个是单例,一个是原型,那么一定要保证单例对象能提前暴露出来,才可以正常注入属性。
不过也不用担心,正常情况下,我们都是用的无参数构造方法构造对象,通过setter方法注入属性的,因此,一般来说,spring项目中的bean都是通过setter方式注入属性的,而且大部分都是单例,因此可以提前暴露出来