欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring源码深度解析— IOC 之 循环依赖处理

程序员文章站 2023-10-16 22:45:12
什么是循环依赖 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图所示: 注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。 Spring中循环依赖场景有: (1)构造器 ......

什么是循环依赖

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如a依赖于b,b依赖于c,c又依赖于a。如下图所示:

spring源码深度解析— IOC 之 循环依赖处理

注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。
spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖。 

对于构造器的循环依赖,spring 是无法解决的,只能抛出 beancurrentlyincreationexception 异常表示循环依赖,所以下面我们分析的都是基于 field 属性的循环依赖。

spring 只解决 scope 为 singleton 的循环依赖,对于scope 为 prototype 的 bean spring 无法解决,直接抛出 beancurrentlyincreationexception 异常。

如何检测循环依赖

检测循环依赖相对比较容易,bean在创建的时候可以给该bean打标,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。

解决循环依赖

我们先从加载 bean 最初始的方法 dogetbean() 开始。

在 dogetbean() 中,首先会根据 beanname 从单例 bean 缓存中获取,如果不为空则直接返回。

protected object getsingleton(string beanname, boolean allowearlyreference) {
    object singletonobject = this.singletonobjects.get(beanname);
    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) {
                    singletonobject = singletonfactory.getobject();
                    this.earlysingletonobjects.put(beanname, singletonobject);
                    this.singletonfactories.remove(beanname);
                }
            }
        }
    }
    return singletonobject;
}

这个方法主要是从三个缓存中获取,分别是:singletonobjects、earlysingletonobjects、singletonfactories,三者定义如下:

private final map<string, object> singletonobjects = new concurrenthashmap<>(256);

private final map<string, objectfactory<?>> singletonfactories = new hashmap<>(16);

private final map<string, object> earlysingletonobjects = new hashmap<>(16);

这三级缓存分别指:
(1)singletonfactories : 单例对象工厂的cache
(2)earlysingletonobjects :提前暴光的单例对象的cache
(3)singletonobjects:单例对象的cache

他们就是 spring 解决 singleton bean 的关键因素所在,我称他们为三级缓存,第一级为 singletonobjects,第二级为 earlysingletonobjects,第三级为 singletonfactories。这里我们可以通过 getsingleton() 看到他们是如何配合的,这分析该方法之前,提下其中的 issingletoncurrentlyincreation() 和 allowearlyreference

  • issingletoncurrentlyincreation():判断当前 singleton bean 是否处于创建中。bean 处于创建中也就是说 bean 在初始化但是没有完成初始化,有一个这样的过程其实和 spring 解决 bean 循环依赖的理念相辅相成,因为 spring 解决 singleton bean 的核心就在于提前曝光 bean。
  • allowearlyreference:从字面意思上面理解就是允许提前拿到引用。其实真正的意思是是否允许从 singletonfactories 缓存中通过 getobject() 拿到对象,为什么会有这样一个字段呢?原因就在于 singletonfactories 才是 spring 解决 singleton bean 的诀窍所在,这个我们后续分析。

getsingleton() 整个过程如下:首先从一级缓存 singletonobjects 获取,如果没有且当前指定的 beanname 正在创建,就再从二级缓存中 earlysingletonobjects 获取,如果还是没有获取到且运行 singletonfactories 通过 getobject() 获取,则从三级缓存 singletonfactories 获取,如果获取到则,通过其 getobject() 获取对象,并将其加入到二级缓存 earlysingletonobjects 中 从三级缓存 singletonfactories 删除,如下:

singletonobject = singletonfactory.getobject();
this.earlysingletonobjects.put(beanname, singletonobject);
this.singletonfactories.remove(beanname);

这样就从三级缓存升级到二级缓存了。

上面是从缓存中获取,但是缓存中的数据从哪里添加进来的呢?一直往下跟会发现在 docreatebean() ( abstractautowirecapablebeanfactory ) 中,有这么一段代码:

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, () -> getearlybeanreference(beanname, mbd, bean));
}

也就是我们上一篇文章中讲的最后一部分,提前将创建好但还未进行属性赋值的的bean放入缓存中。

如果 earlysingletonexposure == true 的话,则调用 addsingletonfactory() 将他们添加到缓存中,但是一个 bean 要具备如下条件才会添加至缓存中:

  • 单例
  • 运行提前暴露 bean
  • 当前 bean 正在创建中

addsingletonfactory() 代码如下:

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);
        }
    }
}

从这段代码我们可以看出 singletonfactories 这个三级缓存才是解决 spring bean 循环依赖的诀窍所在。同时这段代码发生在 createbeaninstance() 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了,所以 spring 在这个时候选择将该对象提前曝光出来让大家认识认识。

介绍到这里我们发现三级缓存 singletonfactories 和 二级缓存 earlysingletonobjects 中的值都有出处了,那一级缓存在哪里设置的呢?在类 defaultsingletonbeanregistry 中可以发现这个 addsingleton() 方法,源码如下:

protected void addsingleton(string beanname, object singletonobject) {
    synchronized (this.singletonobjects) {
        this.singletonobjects.put(beanname, singletonobject);
        this.singletonfactories.remove(beanname);
        this.earlysingletonobjects.remove(beanname);
        this.registeredsingletons.add(beanname);
    }
}

添加至一级缓存,同时从二级、三级缓存中删除。这个方法在我们创建 bean 的链路中有哪个地方引用呢?其实在前面博客 lz 已经提到过了,在 dogetbean() 处理不同 scope 时,如果是 singleton,则调用 getsingleton(),如下:

if (mbd.issingleton()) {
    sharedinstance = getsingleton(beanname, () -> {
        try {
            return createbean(beanname, mbd, args);
        }
        catch (beansexception ex) {
            // explicitly remove instance from singleton cache: it might have been put there
            // eagerly by the creation process, to allow for circular reference resolution.
            // also remove any beans that received a temporary reference to the bean.
            destroysingleton(beanname);
            throw ex;
        }
    });
    bean = getobjectforbeaninstance(sharedinstance, name, beanname, mbd);
}
public object getsingleton(string beanname, objectfactory<?> singletonfactory) {
    assert.notnull(beanname, "bean name must not be null");
    synchronized (this.singletonobjects) {
        object singletonobject = this.singletonobjects.get(beanname);
        if (singletonobject == null) {
            //....
            try {
                singletonobject = singletonfactory.getobject();
                newsingleton = true;
            }
            //.....
            if (newsingleton) {
                addsingleton(beanname, singletonobject);
            }
        }
        return singletonobject;
    }
}

至此,spring 关于 singleton bean 循环依赖已经分析完毕了。所以我们基本上可以确定 spring 解决循环依赖的方案了:spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 objectfactory 提前曝光(即加入到 singletonfactories 缓存中),这样一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 objectfactory 的 getobject() 获取了,也就是 getsingleton()中的代码片段了。

到这里,关于 spring 解决 bean 循环依赖就已经分析完毕了。最后来描述下就上面那个循环依赖 spring 解决的过程:首先 a 完成初始化第一步并将自己提前曝光出来(通过 objectfactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 b,此时就会去尝试 get(b),这个时候发现 b 还没有被创建出来,然后 b 就走创建流程,在 b 初始化的时候,同样发现自己依赖 c,c 也没有被创建出来,这个时候 c 又开始初始化进程,但是在初始化的过程中发现自己依赖 a,于是尝试 get(a),这个时候由于 a 已经添加至缓存中(一般都是添加至三级缓存 singletonfactories ),通过 objectfactory 提前曝光,所以可以通过 objectfactory.getobject() 拿到 a 对象,c 拿到 a 对象后顺利完成初始化,然后将自己添加到一级缓存中,回到 b ,b 也可以拿到 c 对象,完成初始化,a 可以顺利拿到 b 完成初始化。到这里整个链路就已经完成了初始化过程了。