Springの循環依存関係をソースコードレベルから徹底的に分析

以下举例皆针对单例模式讨论

ダイアグラム リファレンススプリング | ProcessOn の無料オンライン描画、オンライン フローチャート、オンライン マインド マップ|

1. Spring はどのようにして Bean を作成しますか?

シングルトン Bean の場合、Spring コンテナのライフサイクル全体でオブジェクトは 1 つだけです。

Bean の作成プロセスで、Spring は DefaultSingletonBeanRegistry.java で定義されている 3 レベルのキャッシュを使用します。

    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
​
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
​
    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

com.gyh.general パッケージの OneBean を例として取り上げ、springboot 起動プロセスをデバッグし、Spring が Bean を作成する方法を分析します。

春の豆づくりの様子を図でご紹介します最も重要な手順は次のとおりです。

  1. getSingleton(beanName, true)第 1 レベル、第 2 レベル、および第 3 レベルのキャッシュから順番に Bean オブジェクトを検索し、キャッシュ内にオブジェクトがある場合は直接 (早期に) 返します。
  1. createBeanInstance(beanName, mbd, args)適切なコンストラクター、新しいインスタンス オブジェクト (インスタンス) を選択します。この時点では、インスタンス内の依存属性はまだ null であり、半完成品です。
  1. singletonFactories.put(beanName, oneSingletonFactory)前の手順のインスタンスを使用して、singletonFactory を構築し、それを 3 番目のレベルのキャッシュに置きます。
  1. populateBean(beanName, mbd, instanceWrapper)Bean を設定します。オブジェクトを作成するか、Bean によって定義されたプロパティに値を割り当てます。
  1. initializeBean("one",oneInstance, mbd)Bean の初期化: プロキシ オブジェクト (プロキシ) の生成など、Bean を初期化または処理します。
  1. getSingleton(beanName, false)一次キャッシュと二次キャッシュを順番に検索して、循環依存関係により事前に生成されたオブジェクトがあるかどうか、また、ある場合は初期化されたオブジェクトと一貫性があるかどうかを確認します。

2. Spring は循環依存関係をどのように解決しますか?

com.gyh.circular.threeCache パッケージ内の OneBean と TwoBean を例にとると、2 つの Bean は相互に依存します (つまり、閉ループを形成します)。

図の循環依存関係を解決する Spring のプロセスを参照すると、Spring が 3 レベル キャッシュの objectFactory を使用して初期オブジェクトを生成して返し、他のオブジェクトの依存関係注入のために事前に初期アドレスを公開していることがわかります。循環依存関係の問題。

3. Spring が解決できない循環依存関係にはどのようなものがありますか?

3.1 ループ内で @Async アノテーションが使用されている

3.1.1 @Async をループ内で使用するとエラーが発生するのはなぜですか?

例として、com.gyh.circular.async パッケージの OneBean と TwoBean を取り上げます。2 つの Bean は相互に依存しており、oneBean のメソッドは @Async アノテーションを使用しています。このとき、Spring の起動は失敗し、エラーが発生します。メッセージは次のとおりです:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

また、デバッグ コードを通じて、エラー報告場所が AbstractAutowireCapableBeanFactory#doCreateBean メソッド内にあることがわかります。これは、earlySingletonReference != null および ExposureObject != Bean であるため、エラーが発生します。





循環依存関係を解決するフローチャートの Springと上記の図を組み合わせると、次のことがわかります。

  1. 1 行目の Bean は、createBeanInstance (address1) によって作成されたインスタンスです。
  1. 2行目のexistsObjectはinitializeBean後に生成されたプロキシオブジェクト(address2)です。
  1. 3行目のearlySingletonReferenceで作成されるオブジェクトはgetEarlyBeanReference [ここでのアドレスはbean(address1)と同じ]

根本的な理由は次のとおりです。以前、TwoBean は、populateBean のときに、アドレスが address1 だった EarlySingletonReference オブジェクトにすでに依存していましたが、このとき、OneBean は、initializeBean の後に、アドレスが address2 だった新しいオブジェクトを返したため、Spring はどれが最終バージョンであるかを認識できませんでした。 Bean であるため、エラーが報告されました。

EarlySingletonReference の生成方法については、getSingleton("one", true) の処理を​​参照してください。

3.1.2 ループ内で @Async を使用するとエラーが発生しますか?

例として、com.gyh.circular.async パッケージの OneBean と TwoBean を取り上げます。2 つの Bean は相互に依存しているため、TwoBean (OneBean ではない) のメソッドは @Async アノテーションを使用します。正常に開始され、エラーは報告されません。

デバッグ コードは、TwoBean が @Async アノテーションを使用しているにもかかわらず、earlySingletonReference = null; ため、エラーが発生しないことを示しています。





根本的な理由は、OneBean が最初に作成され、TwoBean が後で作成されるため、リンク全体で TwoBean の objectFactory が 3 次キャッシュで検索されていないためです。(OneBean は作成プロセス中に 2 回見つかりました (1 -> 2 -> 1)。TwoBean の作成中に見つかったのは 1 回だけ (2 -> 1) です。)

これは次から取得できます: @Async により循環依存関係エラーのレポート条件が発生します。

  1. 循環依存関係内の Bean は @Async アノテーションを使用します
  1. そして、この Bean はループ内の他の Bean よりも前に作成されます。
  1. 注: Bean は複数のサイクルに同時に存在できますが、それがサイクル内で作成された最初の Bean である限り、エラーが報告されます。

3.1.3 @Transactional がループ内で使用されているのにエラーが報告されないのはなぜですか?

Spring は @Transactional アノテーションが付けられた Bean のプロキシ オブジェクトも生成することが知られていますが、なぜこの種の Bean はループ内にあるときにエラーを生成しないのでしょうか?

com.gyh.circular.transactional パッケージの OneBean と TwoBean を例にとります。2 つの Bean は相互に依存しており、OneBean のメソッドは @Transactional アノテーションを使用しています。Spring は正常に起動し、エラーは報告されません。

デバッグ コードは、OneBean の生成プロセスで、earlySingletonReference != null であっても、initializeBean の後の ExposureObject が元のインスタンスと同じアドレスを持つ (つまり、initializeBean ステップでインスタンスに対してプロキシが生成されない) ため、エラーが発生しないことを示しています。報告されます。







3.1.4 同じエージェントが 2 つの異なる現象を引き起こすのはなぜですか?

プロキシ オブジェクトも生成され、循環依存関係にも参加します。異なる現象が発生する理由は、循環依存関係にある場合、プロキシを生成するノードが異なるためです。

  1. @Transactional は getEarlyBeanReference 時にプロキシを生成し、プロキシ以降のアドレス (つまり最終アドレス) を事前に公開します。
  1. @AsyncはinitializeBean時にプロキシを生成するため、事前に公開されているアドレスが最終的なアドレスではなくなり、エラーが発生します。

getEarlyBeanReference のときに @Async がプロキシを生成できないのはなぜですか? 2 つによって実行されるコード プロセスを比較すると、次のことがわかります。

以下の図に示すように、両方とも AbstractAutoProxyCreator#getEarlyBeanReference のメソッドで元のインスタンス オブジェクトをラップします。





@Transactionalを使用するBeanがプロキシ作成時にアドバイスを受け取ると、即座にプロキシオブジェクトproxyが生成されます。





ただし、@Async を使用する Bean はプロキシの作成時にアドバイスを取得しないため、プロキシ化できません。





3.1.5 getEarlyBeanReference のときに @Async がアドバイスを返せないのはなぜですか?

AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean メソッドで行う主な作業は次のとおりです。

  1. 現在の Spring コンテナ内のすべての Advisor を検索する
  1. 現在の Bean に一致するすべての Advisor を返します

最初のステップで返されるアドバイザには、BeanFactoryCacheOperationSourceAdvisor と BeanFactoryTransactionAttributeSourceAdvisor が含まれますが、Async を処理するアドバイザはありません。

原因を突き止めて、非同期関連の Advisor を処理するために最初のステップが戻らない理由を調べてください。

@Async @Transactional @Cacheable の使用を事前に有効にする必要があることが知られています。つまり、@EnableAsync、@EnableTransactionManagement、@EnableCaching を事前にマークする必要があります。

@EnableTransactionManagement と @EnableCaching を例として、アノテーション定義に Selector クラスを導入し、Selector に Configuration クラスを導入します。Configuration クラスでは、対応する Advisor が作成され、Spring コンテナに配置されます。この 2 つのアドバイザーを入手できます。

@EnableAsync の定義で導入された Configuration クラスは Advisor の代わりに AsyncAnnotationBeanPostProcessor を作成するため、最初のステップでは取得されないため、このステップでは @Async の Bean はプロキシされません。

3.2 コンストラクターによって引き起こされる循環依存関係

例として、com.gyh.circular.constructor パッケージの下の OneBean と TwoBean を取り上げます。2 つのクラスのコンストラクターは相互に依存しています。Spring が開始されると、エラーが報告されます。org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?

デバッグ コードは、2 つの Bean がコンストラクターの新しいインスタンスに基づいているときに無限ループに陥り、使用可能なアドレスを事前に公開できないため、エラーを報告することしかできないことを示しています。



4. 上記の循環依存関係エラーを解決するにはどうすればよいですか?

  1. @Async の代わりに、非同期操作を必要とするメソッドを実行のためにスレッド プールに配置します。(お勧め)
  2. @Async アノテーションを付けたメソッドを提案します。(お勧め)
  3. @Async を使用したメソッドを別のクラスとして提案すると、非同期処理のみを実行し、他のビジネス依存関係を作成しません。つまり、循環依存関係の形成を回避し、エラー報告の問題を解決します。com.gyh.circular.async.extract パッケージを参照してください。
  4. コンストラクターに依存するオブジェクトは使用しないようにしてください。(お勧め)
  5. サイクルを破っても (推奨されません)、閉じたループは形成されません。開発前に、オブジェクトの依存関係とメソッド呼び出しチェーンを計画し、循環依存関係を使用しないようにしてください。(難しい、反復開発は変更を続けるため、サイクルが発生する可能性が高い)
  6. ブレーク作成順序 (非推奨)
  7. @Async のアノテーションが付けられたクラスは、循環依存関係内の他のクラスより前に作成されるとエラーを報告するため、このクラスが他のクラスより前に作成されないようにする方法を見つけてください。この問題も次のように解決できます。依存、@Lazy

おすすめ

転載: blog.csdn.net/changlina_1989/article/details/125918409