Spring 解决循环依赖

[toc]

循环依赖

循环依赖就是N个类中循环嵌套引用,如果日常开发中我们用new对象的方式发生这种循环依赖的程序运行一直循环直到内存溢出报错,下面说一下Spring是如何解决的:

首先循环依赖处理有三种情况:

  • 构造器的循环依赖,这种依赖Spring处理不了,直接抛出BeanCurrentlyInCreationException异常
  • 单例模式下的setteer循环依赖:通过三级缓存处理循环依赖
  • 非单例循环依赖:无法处理,抛出BeanCurrentlyInCreateionException异常

Spring单例对象的初始化大略分为三步:

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  • populateBean:填充属性,这一步主要是多bean的雨来属性进行填充
  • initializeBean:调用配置的init方法

从上面讲述的单例Bean初始化步骤,可以知道,循环依赖主要发生在第一步、第二步,也就是构造器循环依赖和field循环依赖。

Spring处理三种循环依赖

构造器循环依赖

this.singletonsCurrentlyCreation.add(beanName)将当前要创建的Bean记录在缓存中,Spring容器将每一个正在创建的Bean标识符放在当前创建的Bean池中,bean标志:在创建过程中,将保持在这个池中,因此创建Bean过程中发现自己已经在创建当前Bean池里,将抛出BeanCurrentlyIncreationException异常表示循环依赖,而对创建完毕的Bean将从当前Bean池中清除掉。

单例模式下,setter循环依赖

Spring为了解决单例的循环依赖问题,使用了三级缓存:

//Cache of singleton objects: bean name ---> bean instance
private final Map<String,Object> singletonObjects=new ConcurrentHashMap(256);
//Cache of early singleton objects: bean name --->bean instance
private final Map<String,Object> earlySingletonObjects=new HashMap(16);
//Cache of singleton factories: bean name--->ObjectsFactory
private final Map<String,ObjectFactory<?>> singletonFacrories=new HashMap(16);

从字面的意思来说:

  • singletonObjects:指单例对象的Cache,完成初始化单例对象的Cache(一级缓存)
  • earlySingletonObjects:只提前曝光的单例对象的CCache,完成实例化但是尚未初始化的,提前曝光的单例对象的Cache(二级缓存)
  • singletonFactories:指单例对象工厂的Cache,进入实例化阶段的单例对象工厂的Cache(三级缓存)

以上三个Cache构成了三级缓存,Spring就用这三级缓存解决了循环依赖的问题。

创建Bean的时候,会首先从cache中获取这个Bean,这个缓存就是sigletonObjects。
主要调用方法是:DefaultSingletonBeanRegistry类下面的getSingleton()方法:

/**
* 方法作用 返回返回以给定名称注册的(原始)单例对象。
* 描述:检查已经实例化的单例,还允许对当前创建的单例的早期引用(解析循环引用)
* beanName:要创建的Bean的名称
* allowEarlyReference:是否应创建早期参考
* return 注册的单例对象;如果找不到,则为null
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation,判断当前单例的Bean是否正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            //allowEarlyReference,是否允许从singletonFactories中通过getObject拿到对象
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //从singletonFactories中移除,并放入earlySingletonObjects中
                        //其实就是从三级缓存移动到二级缓存
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                     }
             }
          }
    }
    return singletonObject;
}

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

public interface ObjectFactory<T> {

    /**
     * 返回一个实例可能是共享的或独立的,由该工厂管理的对象
     * @return 结果的实例
     * @throws BeansException 创建错误抛出异常
     */
    T getObject() throws BeansException;
}

这个接口在AbstractAutowireCapableBeanFactory里的实现,并在核心方法doCreateBean()引用了下面方法:

/**
     *如果有必要添加用于生成指定单例的给定单例工厂
     * 要求对单例登记,例如能够解决循环引用
     * @param beanName bean的名字
     * @param singletonFactory 单例对象的工厂
 */
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);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
}

这段代码发生在createBeanInstance之后,populatBean()之前,也就是单例对象此时已经被创建处理(调用了构造器),这个对象已经被生产出来了,此时将对象提前曝光出来,让大家使用。

这样的好处A的某个field或者setter依赖了B的实例对象,同时B的某个filed或者seteer依赖了A的实例对象,这种循环依赖的情况,A首先先完成初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化第二步,发现自己依赖对象B,此时将尝试get(B),发现B没有被创建,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没用,因为A还没有初始化完全),尝试调用二级缓存earltSingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectsFactory将自己提前曝光了,所以B拿到A对象后顺利完成了初始化阶段的1,2,3初始化之后将自己放到已经缓存中singletonObjects中,此时返回A中,A此时拿到了B的对象顺利完成自己的初始化节点2,3最终A也完成了初始化,放进了以及缓存singletonObjects中,而且更加幸运的是,由于B拿到了A对象引用,所以B现在持有A对象完成了初始化。

非单例循环依赖

对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存prototype作用域的Bean,因此无法提前暴露一个创建中的Bean

总结

只有单例的Bean才能解决循环依赖问题。

首先完成创建Bean过程的第一步时候createBeanInstance,将已经调用了构造器但尚未进行填充属性的bean放入三级缓存中即singletonFactories,提前曝光出来,当创建Bean进行第二步时populateBean,填充属性,当把属性填充成功时候,将从三级缓存中移除,放入二级缓存中,即earlySingletonObjects,当进行第三步initializeBean,Bean创建成功,将完全创建成功的Bean放入一级缓存中singletonObjects,Bean完成创建成功。

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