携手创造,一起成长!这是我参加「日新计划 8 月更文应战」的第33天,点击检查活动概况
基于 Spring Framework v5.2.6.RELEASE
接上篇:Spring 源码阅读 20:获取 Bean 的标准称号
前情提要
上一篇介绍了获取 Bean 实例目标的第一步,依据给定的name
获取 Bean 的标准称号。给定的name
或许是 Bean 的别号或者 FactoryBean 的逆向引证称号,获取到的标准称号是 Bean 在容器中的唯一标识符。
本文接着介绍 AbstractBeanFactory 中doGetBean
方法的下一行要害代码。
Object sharedInstance = getSingleton(beanName);
从缓存中获取 Bean 实例目标
直接进入getSingleton
方法的源码。
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
@Override
@Nullable
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
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;
}
这个方法的作用是依据beanName
从容器缓存中获取对应的 Bean 实例目标。此处有两个需求留意的当地:
- 调用方法传入的参数,是经过
transformedBeanName
方法获取的标准的 Bean 称号。 - 经过调用重载方法,给了
allowEarlyReference
参数一个默认值true
。
然后检查方法体中的代码,首要,大概浏览一下方法体的整个流程,能够发现,在容器中有三个调集,Spring 会运用beanName顺次从这三个调集中查找 Bean 的实例目标,这三个调集,咱们能够将之看作缓存(实践上便是缓存),在 DefaultSingletonBeanRegistry 中能够找到创立它们的代码:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
这三个 Map 调集的 Key 都是 Bean 的称号,Value 分别是单例 Bean 的实例、前期单例 Bean 的实例、单例 Bean 工厂。
在getSingleton
方法的最初,会测验从singletonObjects
调集获取 Bean 的实例,获取到就得到了想要的成果,直接返回。
当没有获取到时,假如isSingletonCurrentlyInCreation(beanName)
的成果为true
,那么测验从下一个缓存 Map 中获取。isSingletonCurrentlyInCreation
方法的源码如下:
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation
/**
* Return whether the specified singleton bean is currently in creation
* (within the entire factory).
* @param beanName the name of the bean
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
这儿还是一个调集,依据注释能够知道,这个方法的作用是判别一个单例 Bean 是不是正在被创立,也便是说这个调集中保存了一切正在被创立的 Bean 的称号。
返回到getSingleton
方法中,假如从singletonObjects
中获取不到,而且这个 Bean 正在被创立,那么就测验从earlySingletonObjects
调集中获取。这儿寄存的是前期单例 Bean 的实例目标,这些 Bean 实例被创立好,可是还没有进行完初始化,就会被放在这个调集傍边,能够提早被获取到了,假如这儿获取到了目标 Bean 实例,那么也会作为方法的成果返回。
假如还没有获取到,而且allowEarlyReference
为true
,那么会接着测验从singletonFactories
中获取,这儿缓存的是创立前期单例的 Bean 工厂,假如从singletonFactories
调集中获取到了这个工厂目标,那么就调用它的getObject
方法,将前期 Bean 创立出来,并作为成果返回。
别的,假如前期 Bean 是在这儿创立的,那么还需求把创立好的前期 Bean 添加到earlySingletonObjects
中,并把工厂从singletonFactories
移除掉,由于再次之后,它已经不会被用到了。
你或许会问了:何必呢?直接把 Bean 完整地初始化好放在一个缓存中获取,获取不到就创立新的,不就行了吗,为什么还需求前期实例和工厂实例这种中间状态呢?
这就要讲到循环依靠的问题。
循环依靠
假设接下来咱们说到的类型,都是用来创立单例 Bean 的类型,思考这样三种状况:
- ClassA 中有一个特点的类型是 ClassA
- ClassA 中有一个特点的类型是 ClassB,一起,ClassB 中有一个特点的类型是 ClassA
- ClassA 中有一个特点的类型是 ClassB,ClassB 中有一个特点的类型是 ClassC,一起,ClassC 中有一个特点的类型是 ClassA
在之前的 Spring 源码阅读 19:如何 get 到一个 Bean? 一文中,从前介绍过,完结一个 Bean 的初始化之前,必需求先初始化其各个特点值对应的 Bean。当呈现以上三种状况或者类似的形成循环依靠联系的状况,就会呈现问题。
以第 3 种状况为例,要完结 ClassA 的初始化,就要先初始化 ClassB,那么就要先初始化 ClassC,初始化 ClassC 又需求完结 ClassA 的初始化。这样就变成了「鸡生蛋蛋生鸡」的问题。
这种状况在实践的开发中,难以彻底避免,因而,这是 Spring 必需求处理的问题。
三级缓存
Spring 处理这个问题的方法便是:在一个 Bean 还没有完结初始化的时候,就暴露它的引证。这儿就需求用到前面介绍过的三个缓存 Map,也叫三级缓存。
以刚刚描绘的状况为例,当 ClassA 需求需求一个 ClassB 的单例 Bean 作为特点填充的时候,先创立一个前期的 ClassB 的 Bean,此时,ClassB 刚刚被创立出来,还没有进行特点填充等初始化的流程,就将它放在earlySingletonObjects
中,这样,ClassA 的 Bean 实例就能够运用这个 ClassB 的前期实例进行特点填充,ClassB 的前期实例,能够再次之后再进行初始化,这样就不会由于循环依靠联系,导致无法初始化这三个 Bean。
暂时无法在飞书文档外展现此内容
上图是 Bean 的初始化流程和从各缓存中获取 Bean 实例的流程,具体的过程,会在之后剖析到第一次获取一个没有被创立的 Bean 的流程时,再经过代码做深入剖析。
限制
不过,Spring 对这个问题的处理还存在限制性。
在 Spring 中,能够经过 setter 方法和结构器将一个 Bean 实例作为特点值注入给另一个 Bean 实例。依据 Spring 经过三级缓存处理循环依靠问题的原理,在一个 Bean 被放入缓存之前,至少需求先经过反射把目标创立出来。可是,假如一个一个特点只能经过结构器注入,那么就无法在注入之前,完结 Bean 目标的创立,也就无法处理循环依靠的问题。
因而,Spring 只能处理经过 setter 注入的循环依靠问题。
总结
本文剖析了 Spring 容器从缓存中获取单例 Bean 的原理,主要涉及到了 Spring 为处理循环依靠问题,而设计的三级缓存机制。经过getSingleton
方法从缓存中获取 Bean 实例目标,或许获取到,也或许由于 Bean 还彻底没有开端创立而获取不到。下一篇,咱们接着剖析,假如这儿获取到了单例 Bean,接下来还需求做哪些处理。