《系列二》-- 5、单例bean缓存的获取

发布时间 2023-06-24 10:12:42作者: bokerr

回到 doGetBean 初始的位置:

img.png

img.png

1 判断bean是否完成整个加载流程

    /** Cache of singleton objects: bean name --> bean instance */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

如下是 singletonObjects 的定义,一个线程安全的HashMap,顾名思义只有是单例的bean被成功加载后才会被它缓存,所以非单例bean 是不可能从中获取到的。

如果我们请求的bean 是单例bean且已经被加载过,完成了整个加载流程,此时程序在方法第一行就已经可以退出了。

不是就接着往下:

2 判断当前bean是否被加载过,是否已作为提前暴露的bean

这里的逻辑其实很简单,无非就是双重锁机制。第一次获取到null后,判断当前bean是否已经进入创建流程 isSingletonCurrentlyInCreation 这个方法名很直观。

isSingletonCurrentlyInCreation 返回true时,说明该bean已经在别的线程 (或递归循环依赖) 中进入创建流程了。

【PS * 前边提到过,bean被加载时,都会递归的去加载它所依赖的 bean】

这里还有另一位主角:

    /** Cache of early singleton objects: bean name --> bean instance */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

它用于提前暴露bean, 你看这里先取了一次,没取到才正式进入创建流程,同样也是为了避免提前暴露动作重复。

关于循环依赖

还有另一位需要被重视的成员:

    /** Cache of singleton factories: bean name --> ObjectFactory */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

看到了么,它保存的对象实现了 ObjectFactory 接口,不陌生吧,前边讲循环依赖消解的文章介绍过它。

这里还有另一个很有意思的现象不知道你有没有注意到:earlySingletonObjects保存该bean之后,就立即从 singletonFactories 中移除了。

这里说明他们极有可能是一个传递关系:当一个bean 第一次被加载时,默认会保存到 singletonFactories,
这时候这个bean 还在创建流程中,可能出现的情况是这时有另一个 bean 也依赖于它 (参考循环依赖,如果 spring 加载bean的过程是单线程,这时铁定出现循环依赖了。)

根据前边讲循环依赖的文章:

this.earlySingletonObjects.put(beanName, singletonObject);

bean的提前暴露它不就来了么?别看这里只有几行代码,结合上下文之后才会知道它的重要性。

img.png

前边讲到 bean 从 singletonFactories 传递到 earlySingletonObjects

那么我们再去追究下, singletonFactories 中是什么时候注入的,然后发现了只有一个地方调用了其上的:singletonFactories.put() 方法

再往上追踪,我们发现了一个名叫: doCreateBean 的方法,这是我们后续流程中关注的方法;它处于从 0 开始创建一个bean 的流程中。

先记住它,后边我们会再见到它的。

结合上边分析的流程,这里还有另一个定义,那就是三级缓存;spring 在消解循环依赖时引入了三级缓存:

  • 一级缓存: Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256)

  • 二级缓存: Map<String, Object> earlySingletonObjects = new HashMap<>(16)

  • 三级缓存: Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)

首先时创建全新bean 时,将其注入了 三级缓存中。

所以创建bean 的流程时,先从一级缓存获取,如果成功直接返回;

否则从二级缓存中,成功获取到bean,那么直接返回;

如果从二级缓存也无法获取,那么尝试从三级缓存获取,若从三级缓存成功获取,那么从三级缓存中移除该bean,并转移到二级缓存中。

最终,若三级缓存也无法获取,说明是在获取一个从未被加载的 bean。

最终,第一次被加载的bean,最初会被缓存到,三级缓存中,bean 创建流程中,关注上述提到的 doCreateBean()方法即可闭环。