ソースコードレベルからの Spring 循環依存関係の詳細な分析 | JD Cloud テクニカルチーム

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

グラフィックリファレンスhttps://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce

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 オブジェクトを検索し、キャッシュ内にオブジェクトがある場合は直接 (早期に) 返します。

2.createBeanInstance(beanName, mbd, args)適切なコンストラクター、新しいインスタンス オブジェクト (インスタンス) を選択します。この時点では、インスタンス内の依存属性はまだ null であり、半完成品です。

3.singletonFactories.put(beanName, oneSingletonFactory)前のステップのインスタンスを使用して、singletonFactory を構築し、それを 3 番目のレベルのキャッシュに置きます。

4. populateBean(beanName, mbd, instanceWrapper)Bean を設定します。オブジェクトを作成するか、Bean によって定義されたプロパティの値を割り当てます。

5. initializeBean("one",oneInstance, mbd)Bean の初期化: Bean を初期化するか、プロキシ オブジェクト (プロキシ) の生成などのその他の方法で処理します。

6.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) によって作成されたインスタンスです。

2. 2行目のexhibitedObjectはinitializeBean後に生成されたプロキシオブジェクト(address2)です。

3. 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 アノテーションを使用します

2. そして、この Bean はループ内の他の Bean よりも前に作成されます。

3. 注: 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 時にプロキシを生成し、プロキシ以降のアドレス (つまり、最終アドレス) を事前に公開します。

2. @Async は、InitializeBean 時にプロキシを生成するため、事前に公開されているアドレスが最終的なアドレスではなくなり、エラーが発生します。

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

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

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

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

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

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

1. 現在の Spring コンテナ内のすべての Advisor を検索します

2. 現在の 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 のアノテーションが付けられたクラスは、循環依存関係内の他のクラスより先に作成されるとエラーを報告するため、このクラスが他のクラスより先に作成されないようにする方法を見つけてください。この問題も解決できます。 : @DependsOn、@Lazy

著者: Guo Yanhong、Jingdong Technology

出典: JD Cloud 開発者コミュニティ

HuaweiがHarmonyOS 4 miniblinkバージョン108を正式リリース、コンパイルに成功 世界最小のChromiumカーネル Vimの父、Bram Moolenaar氏が病気で死去 . ChromeOSはブラウザとオペレーティングシステムを独立した Bilibili(ビリビリ)ステーションに分割し、再び崩壊 . HarmonyOS NEXT: フル使用 自社開発カーネル Nim v2.0 正式リリース、命令型プログラミング言語 Visual Studio Code 1.81 リリース Raspberry Pi 月産能力 100 万台達成 Babitang 初のレトロメカニカルキーボード発売
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10093714