Spring是如何解决循环依赖的?

在Spring 的使用的时候 可能会碰到一下的场景

<bean id="a" class="com.zhh.a">
  <property name="b" ref="b"></property>
</bean>

<bean id="b" class="com.zhh.b">
    <property name="a" ref="a"></property>
</bean>

a-->b 即a依赖于b , b--> a即 b依赖于a

Spring提供构造器注入和Setter注入,那么也就可能造成构造器循环依赖和setter循环依赖。

构造器注入
<bean id ="a" class="com.zhh.a">
    <constructor-arg value="a" ref="b"></constructor-arg>
</bean>
<bean id ="b" class="com.zhh.b">
    <constructor-arg value="b" ref="c"></constructor-arg>
</bean>
<bean id ="c" class="com.zhh.c">
    <constructor-arg value="a" ref="a"></constructor-arg>
</bean>

此时构造器 形成了循环依赖
1.Spring对于构造器的循环依赖并没有解决 而是通过抛出异常
在创建A的时候发现,首先去 当前 "当前创建的Bean池",查找当前bean是否在创建,如果没有发现,则继续准备需要的构造参数B,并将A 放入到当前正在创建的Bean池。
2.创建B的时候发现,首先去 当前 "当前创建的Bean池",查找当前bean是否在创建,如果没有发现,则继续准备需要的构造参数C,并将B 放入到当前正在创建的Bean池。
3.创建C的时候发现,首先去 当前 "当前创建的Bean池",查找当前bean是否在创建,如果没有发现,则继续准备需要的构造参数A,并将A 放入到当前正在创建的Bean池
4.再次创建A的时候,发现A已近在当前创建的Bean池,表示循环依赖,则抛出beanCurrentLyInCreationException

Setter注入

1.Spring容器在创建单例“A”的时候,根据无参构造器,创建bean,并且暴露出ObjectFactory,用于返回一个提前暴露一个创建中的Bean,并将“A” 放入到当前创建的Bean池中,
2.Spring容器在创建单例“B”的时候,根据无参构造器,创建bean,并且暴露出ObjectFactory,用于返回一个提前暴露一个创建中的Bean,并将“B” 放入到当前创建的Bean池中,
3.Spring容器在创建单例“C”的时候,根据无参构造器,创建bean,并且暴露出ObjectFactory,用于返回一个提前暴露一个创建中的Bean,并将“C” 放入到当前创建的Bean池中,然后通过setter注入A,由于创建A的时候提前暴露了ObjectFactory,从而可以返回提前暴露的一个Bean.
4.最后通过依赖注入,A和B ,完成setter注入

源码分析

    //beanName --> bean instance
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    //beanName -->ObjectFactory  用于解决循环依赖 临时变量
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    //beanName --> bean instance 用于解决循环依赖 临时变量
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);

singletonFactories : 用于保存当前BeanName和 对象工厂的引用 一旦当前对象被创建singletonFactories 则BeanName 信息被删除
earlySingletonObjects :用于保存早期BeanName和bean实例的引用,也是当前Bean 一旦被创建 earlySingletonObjects 信息将被删除

下面介绍方法一

 protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) { 
                this.singletonFactories.put(beanName, singletonFactory);//将当前Objectory 添加到singletonFactories 
                this.earlySingletonObjects.remove(beanName);//同时保证当前earlySingletonObjects 没有
                this.registeredSingletons.add(beanName);
            }
        }
    }

方法二

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //通过beanName获取单例bean
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            Map var4 = this.singletonObjects;
            synchronized(this.singletonObjects) {
                //通过beanName获取BeanFacotory
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject != NULL_OBJECT ? singletonObject : null;
    }

方法三

  protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }

在方法一中:对象信息对beanFactory的形式被放入singletonFactories中,这时earlySingletonObjects中肯定没有此对象(因为remove)。

在方法二中:经过过一些条件 allowEarlyReference 为true的时候,bean的信息 就从singletonFactory工厂中拿出来,被放入到earlySingletonObjects 同时 singletonFactories 调用remove 掉 也就是说earlySingletonObjects 和 singletonFactories是一个互斥的变量

在方法三中:在方法3中,对象被加入到singletonObjects中,同时singletonFactories和earlySingletonObjects中都remove掉持有的对象(不管持有与否),这就表示在之前的处理中,这只相当于一个临时容器,处理完毕之后都会remove掉。

在正常的情况下,调用顺序如下:以下有无,表示是否持有对指定Bean的引用

singletonFactories earlySingletonObjects singletonObjects
getSingleton(beanName, true)
doCreateBean(beanName,mdb,args)
getSingleton(beanName, true);
addSingleton(beanName, singletonObject)

如果出现循环依赖 解决调用顺序如下

singletonFactories earlySingletonObjects singletonObjects
getSingleton(A, true) A无B无 A无B无 A无B无
doCreateBean(A,mdb,args) A有B无 A无B无 A无B无
populateBean(A, mbd, instanceWrapper) 填充属性A ,同时由A解析B……
getSingleton(B, true) A有B无 A无B无 A无B无
doCreateBean(B,mdb,args) A有B有 A无B无 A无B无
populateBean(B, mbd, instanceWrapper) 填充属性B,由B准备解析A……
getSingleton(A, true) A无B有 A有B无 A无B无
完成populateBean(B, mbd, instanceWrapper)解析……
addSingleton(B, singletonObject) A无B无 A有B无 A无B有
完成populateBean(A, mbd, instanceWrapper)
A- = initializeBean(beanName, exposedObject, mbd)在initializeBean之后A变为A-
getSingleton(A, false);验证 addSingleton(A, singletonObject) ……

在上面这个过程中,在对A进行验证时,就会从earlySingletonObjects中取得一个A,但是这个A和后面的A-可能不是同一个对象,这是因为有了beanPostProcessor存在,它可以改变bean的最终值,比如对原始bean进行封装,代理等。在这个过程中,出现了3个对象A,A-,B,而B中所持有的A对象为原始的A。如果这里的A和A-不是同一个对象,即产生了beanA有了beanB的引用,但beanB并没有beanA的引用,而是另一个beanA的引用。这肯定不满足条件。

那么我们看Spring 对这种情况的处理:

Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {//判断点1
                if (exposedObject == bean) {//判断点2
                    exposedObject = earlySingletonReference;
                }
                else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {判断点3
                    String[] dependentBeans = getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
                    for (String dependentBean : dependentBeans) {
                        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            actualDependentBeans.add(dependentBean);
                        }
                    }
                    if (!actualDependentBeans.isEmpty()) {判断点4
抛出对象不致异常。
}

判断点1,首先确定这个对象能从earlySingletonObjects中取出对象来,经过上面的分析,我们知道,在正常情况下,此对象为null,即不存在循环检测。而在循环引用中,此对象能够被取出来。

判断点2,再判断这个对象和当前通过beanPostProcessor处理过的对象是否相同,如果相同,表示对象没有经过修改,即A=A-,那么循环引用成立。无需处理

判断点3,判断当前对象A是否被其他对象所依赖,在循环引用中,已经处理了A和B,那么在依赖表中,即在属性dependentBeanMap和dependenciesForBeanMap中。其中A->B表示A依赖于B,B->A表示B依赖于A。那么在dependentBeanMap中就会出现两个entry,分别为A->B和B->A。这里A依赖于A,那么表示A已经被依赖,则进入进一步检测中。在检测中,将取得一个A的被依赖列表中的bean已经被创建的对象列表值。

判断点4,如果被依赖对象列表不为空,则表示出现循环引用。因为按照创建规则,如果A->B,则必须先创建B,而B->A,则必须先创建A。在这里,A被B依赖,就要求A必须在B之前被创建,而B又被A依赖,又要求A必须在B之前被创建。这创建的两个对象必须满足一致才可以。即在A->B中的两个对象,必须和B->A的两个对象,互相一致才可以,否则就不是循环引用。

至此,整个流程梳理清楚。那么,如何处理这种循环引用呢?答案其实也很简单,在xml中将两方的循环切掉。然后使用一个beanPostProcessor即可以,此beanPostProcessor必须要在放到所有beanPostPrcessor的最后面。然后此beanPostProcessor,这样写即可:

  判断当前bean为beanA
BeanB beanB=beanFactory.getBean(“beanB”);
beanA.setBeanB(beanB);
beanB.setBeanA(beanA);

想具体了解的 可以看看一下博客
Spring 循环依赖详细介绍

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容