「ナゲッツデイリー新プラン・8月アップデートチャレンジ」参加33日目、イベント詳細はこちら
Spring Framework v5.2.6.RELEASE に基づく
前の記事からの続き: Spring ソース コードの読み取り 21: 循環依存関係と L3 キャッシュ
要約
前回の記事ではdoGetBean
、AbstractBeanFactory のメソッドで、Bean 名の変換が完了したら、最初のステップとして、Spring コンテナーの第 3 レベルのキャッシュからシングルトン Bean のインスタンス オブジェクトを取得することを紹介しました。
コンテナーのキャッシュで Bean インスタンス オブジェクトが取得された場合、Spring はこのオブジェクトをさらに処理します。この部分のコードは次のとおりです。
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
复制代码
ログ処理部分に加えて、メソッドは主に、getObjectForBeanInstance
最終的に結果として返される Bean インスタンスを取得するために呼び出されます。
キャッシュから Bean インスタンス オブジェクトを取得した後、Spring はどのようなフォローアップ作業を行う必要があるのでしょうか? この記事では、この方法を出発点として詳細な分析を行います。
さまざまな種類の豆を扱う
まずgetObjectForBeanInstance
、メソッドを呼び出すときは、次のパラメーターに注意する必要があります。
- キャッシュから取得した Bean インスタンス オブジェクトは
sharedInstance
、前回の if 判定によると、現時点ではこのオブジェクトは空ではありません。 - 呼び出し
doGetBean
メソッドのパラメーターname
、つまり Bean 名変換前の値。この値は、メソッドをname
最初に呼び出すgetBean
ときに渡されるパラメータ値でもありgetBean
、メソッド呼び出し時の意図を表していると言えます。 - 変換された canonical
beanName
、つまりコンテナー内の Bean の一意の識別子。 - 最後のパラメータが に渡され
null
、後続の分析が最終的に到着し、詳細に調べます。
次にメソッドのコードを入力します。
// org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
}
if (mbd != null) {
mbd.isFactoryBean = true;
}
return beanInstance;
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
Object object = null;
if (mbd != null) {
mbd.isFactoryBean = true;
}
else {
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
复制代码
方法体中的代码,大概用空行分割成了三个部分,我们逐个来分析。
首先判断name
属性的值,是不是一个工厂引用,具体的判断方式如下:
// org.springframework.beans.factory.BeanFactoryUtils#isFactoryDereference
public static boolean isFactoryDereference(@Nullable String name) {
return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
}
复制代码
很简单,就是看它是不是以&
符号开头的。再次提示,这里判断的是原始的name
参数值。
此处需要再说明一下,前文中说,这个值代表了最开始调用getBean
方法的意图,什么意思呢?
因为&
代表这个name
是一个逆向引用,如果调用getBean
方法传入的name
是以&
开头的话,说明调用getBean
方法的意图,是为了获取用来创建 Bean 的 FactoryBean 的实例,如果不是以&
开头的话,说明调用getBean
方法的意图,是为了获取 Bean 的实例对象本身。
上面一段话读三遍,后面要考。
回到代码第一部分的逻辑,如果name
是以&
符号开头,表明我们要获取的是 FactoryBean 本身的实例。首先判断了,如果beanInstance
是一个 NullBean 就直接返回。之后,判断了如果它不是 FactoryBean 的实例,就会报错,没问题的话,则返回。
这里报错是因为,缓存中获取的实例beanInstance
不是一个 FactoryBean 的实例,name
中包含了&
符号代表了方法调用的意图是要获取 FactoryBean 本身的实例。
如果name
不是以&
符号开头,那么,则说明要获取的就是 Bean 的实例对象。
下面看方法体中的第二部分代码:
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
复制代码
这里很简单,如果beanInstance
不是 FactoryBean 的实例,那么,它就是要获取的对象实例本身,所以直接返回就可以了。
之后就剩下最后一种情况,beanInstance
是 FactoryBean 的实例,且name
不以&
开头。也就是说,当前已经获取到的beanInstance
是创建 Bean 的工厂实例,但是要获取的是工厂创建出来的 Bean 对象实例。
以下是处理这种情况的代码:
Object object = null;
if (mbd != null) {
mbd.isFactoryBean = true;
}
else {
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
复制代码
根据调用方法时的参数值,这里的mbd
一开始是null
。因此,首先会通过getCachedObjectForFactoryBean
方法获取。
// org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getCachedObjectForFactoryBean
@Nullable
protected Object getCachedObjectForFactoryBean(String beanName) {
return this.factoryBeanObjectCache.get(beanName);
}
复制代码
这里又出现了一个容器,我们看一下它的定义:
/** Cache of singleton objects created by FactoryBeans: FactoryBean name to object. */
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<>(16);
复制代码
根据注释可知,它用来缓存被 FactoryBean 创建好的单例对象。
如果从这个容器中获取到的对象不为空,则它就是最后的结果。如果为空,那么进入下一个if
语句块。在这个语句块中,首先将beanInstance
强制转换为 FactoryBean 的类型,然后判断容器中是否有beanName
对应的 BeanDefinition,有的话将其获取到。
这里我们假设 Spring 的配置及初始化过程都没问题,并且这里请求的是一个已经配置的 Bean,那么,这里的mbd
变量就是已经获取到的 BeanDinifition。之后的工作,就是开始使用这个factory
,来得到真正的 Bean 实例对象,这部分逻辑在getObjectFromFactoryBean
方法中。
在分析它之前,顺便说一下传入的参数!synthetic
。根据我们的假设,这里的mbd
不为空,且synthetic
的默认值是false
,因此这里传入的参数值是true
。
使用 FactoryBean 获取 Bean 的对象实例
进入getObjectFromFactoryBean
方法的代码:
// org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
// Only post-process and store if not put there already during getObject() call above
// (e.g. because of circular reference processing triggered by custom getBean calls)
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
if (shouldPostProcess) {
if (isSingletonCurrentlyInCreation(beanName)) {
// Temporarily return non-post-processed object, not storing it yet..
return object;
}
beforeSingletonCreation(beanName);
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,
"Post-processing of FactoryBean's singleton object failed", ex);
}
finally {
afterSingletonCreation(beanName);
}
}
if (containsSingleton(beanName)) {
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
return object;
}
}
else {
Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}
复制代码
方法体比较长,我们挑关键的代码来看。
首先判断了factory是不是单例的,且单例缓存中是不是存在beanName对应的单例对象。我们只考虑单例的情况,这里判断条件的结果是true
。
接下来,会调用doGetObjectFromFactoryBean
方法,来创建 Bean 实例对象。这个方法中主要的代码就是调用了factory
的getObject
方法,这是 FactoryBean 的实现类用来创建对象的方法。创建完之后,进入下面的流程。
判断条件if (shouldPostProcess)
中,shouldPostProcess
是通过参数传递的,前面已经分析过了它的值是true
,因此,进入if
语句块的流程。
在if
语句块中,通过isSingletonCurrentlyInCreation
方法判断了实例是不是正在创建中,如果是,就直接返回。这里的判断,其实也是判断一个集合中是否包含beanName
,这个集合是singletonsCurrentlyInCreation
。假设我们是第一次创建这个实例,则会进入之后的流程,关键的代码有三行:
beforeSingletonCreation(beanName);
object = postProcessObjectFromFactoryBean(object, beanName);
afterSingletonCreation(beanName);
复制代码
这里beforeSingletonCreation
和afterSingletonCreation
方法的源码如下:
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#afterSingletonCreation
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
复制代码
其实就是在执行postProcessObjectFromFactoryBean
方法期间,将beanName
保存在singletonsCurrentlyInCreation
集合中,与前面的判断语句对应。(另外,这里还有一个排除检查的列表inCreationCheckExclusions
)
最后再看一下postProcessObjectFromFactoryBean
方法究竟对object
做了什么处理。
protected Object postProcessObjectFromFactoryBean(Object object, String beanName) throws BeansException {
return object;
}
复制代码
其实什么也没做,因此这里应该是提供给子类的扩展点。
总结
以上でキャッシュからBeanインスタンスをgetObjectForBeanInstance
取得し、メソッドで最終的な結果オブジェクトを取得する処理は終了ですgetObjectForBeanInstance
が、メソッドの主な機能は、FactoryBeanインターフェースを実装した型のBeanを扱うことです。ここで、あまり知られていない知識ポイントがあります。つまり、Bean の型が FactoryBean の実装クラスである場合、その Bean インスタンスを作成した FactoryBean 自体のインスタンスを取得したい場合は、getBean("&beanName")
呼び出すことで取得できます。 .
プロセスのこの部分の後、ここで取得されたオブジェクトはdoGetBean
、最後にメソッドによって返されるオブジェクトです。ただし、これまでのところ、doGetBean
メソッドのごく一部しか分析していません。つまり、Bean のインスタンス オブジェクトをキャッシュで取得できます。メソッドの最初のステップで Bean インスタンスがキャッシュに取得されない場合は、doGetBean
Bean インスタンスを最初から初期化する必要があります. 次の記事から、この場合の Bean の初期化と取得の方法を分析します.