您的位置:首页 > 编程语言 > Java开发

从spring源码角度分析循环依赖bean的组装

2016-08-25 00:00 771 查看

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方式注入属性的,而且大部分都是单例,因此可以提前暴露出来
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring 循环依赖 源码