Springソースコード読解21:循環依存とL3キャッシュ

「ナゲッツデイリー新プラン・8月アップデートチャレンジ」参加33日目、イベント詳細はこちら

Spring Framework v5.2.6.RELEASE に基づく

前回記事の続き:Springのソースコード読解その20:beanの正規名を取得する

要約

name前回の記事では、指定された Bean の正規名に従って、Bean インスタンス オブジェクトを取得する最初の手順を紹介しました。nameBean のエイリアス名または 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 インスタンス オブジェクトを取得することです。ここで注意すべき点が 2 つあります。

  1. メソッドを呼び出すことによって渡されるパラメーターは、メソッドtransformedBeanNameを介して取得された正規の Bean 名です。
  2. オーバーロードされたメソッドを呼び出すことにより、allowEarlyReferenceパラメーターにデフォルト値が与えられますtrue

次にメソッド本体のコードを見てみましょう.まずメソッド本体の全体の流れをざっと見てみると.コンテナ内に3つのコレクションがあることがわかります.SpringはbeanNameを使ってこの3つからbeanのインスタンスオブジェクトを探します.これら 3 つのコレクションはキャッシュ (実際にはキャッシュ) と考えることができ、それらを作成するコードは 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);
复制代码

これら 3 つのマップ セットのキーは Bean の名前であり、値はそれぞれシングルトン 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 实例,那么也会作为方法的结果返回。

如果还没有获取到,并且allowEarlyReferencetrue,那么会接着尝试从singletonFactories中获取,这里缓存的是创建早期单例的 Bean 工厂,如果从singletonFactories集合中获取到了这个工厂对象,那么就调用它的getObject方法,将早期 Bean 创建出来,并作为结果返回。

另外,如果早期 Bean 是在这里创建的,那么还需要把创建好的早期 Bean 添加到earlySingletonObjects中,并把工厂从singletonFactories移除掉,因为再次之后,它已经不会被用到了。

你可能会问了:何必呢?直接把 Bean 完整地初始化好放在一个缓存中获取,获取不到就创建新的,不就行了吗,为什么还需要早期实例和工厂实例这种中间状态呢?

这就要讲到循环依赖的问题。

循环依赖

假设接下来我们提到的类型,都是用来创建单例 Bean 的类型,思考这样三种情况:

  1. ClassA 中有一个属性的类型是 ClassA
  2. ClassA 中有一个属性的类型是 ClassB,同时,ClassB 中有一个属性的类型是 ClassA
  3. 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 が第 3 レベルのキャッシュによって循環依存の問題を解決するという原則に従って、Bean がキャッシュに入れられる前に、少なくともオブジェクトがリフレクションによって作成される必要があります。ただし、コンストラクターを介して 1 つの属性のみを注入できる場合、注入前に Bean オブジェクトの作成を完了することができず、循環依存の問題を解決できません。

したがって、Spring は、setter インジェクションを介してのみ循環依存を解決できます。

要約する

このホワイト ペーパーでは、Spring コンテナーがキャッシュからシングルトン Bean を取得する原理を分析します。これには、主に、循環依存関係の問題を解決するために Spring によって設計された 3 レベルのキャッシュ メカニズムが含まれます。メソッドを介してキャッシュから Bean インスタンス オブジェクトを取得しgetSingletonます。取得される場合と、Bean の作成が開始されていないために取得されない場合があります。次の記事では、ここで singleton Bean を取得した場合、次にどのような処理を行う必要があるかを引き続き分析します。

おすすめ

転載: juejin.im/post/7136589270583607309