3 レベルのキャッシュを通じて Spring の循環依存関係を解決する方法

以下の内容はSpring6.0.4をベースにしています。

実はこれは面接でよく聞かれる質問で、このテーマについてずっと詳しくお話したいと思っていたのですが、インターネット上にはこのテーマに関する記事がたくさんありますが、まだ説明するのが少し難しいといつも感じています。今日は、友達と一緒にこの問題を解決できるかどうか試してみましょう。

1. 循環依存関係

1.1 循環依存関係とは

まず、循環依存関係とは何でしょうか? これは実際には理解するのが簡単です。つまり、次のように 2 つの Bean が相互に依存しています。

@Service
public class AService {
    @Autowired
    BService bService;
}
@Service
public class BService {
    @Autowired
    AService aService;
}

AService と BService は相互に依存しています。

このことはよく理解しておかなければなりません。

1.2 循環依存関係の種類

一般に、循環依存関係には 3 つの異なる形式があり、上記のセクション 1.1 はそのうちの 1 つです。

次の図に示すように、他の 2 つは 3 つの依存関係です。

この種の循環依存関係は一般に深く隠されており、検出するのは簡単ではありません。

以下に示すように、自立性もあります。

一般に、コードに循環依存関係がある場合は、コードの設計プロセスに問題がある可能性があることを意味するため、循環依存関係の発生を回避するように努める必要があります。ただし、循環依存関係が発生すると、デフォルトで Spring がそれを処理しますが、これは循環依存関係のコードが問題ないという意味ではありません。実はSpringの最新版では循環依存が追加で有効になっており、追加の設定が無い場合は循環依存が発生した場合に直接エラーが報告されるようになっています。
さらに、Spring はすべての循環依存関係を処理できるわけではありません。Song Ge が後で分析します。

2. 循環依存関係ソリューションのアイデア

2.1 解決策

では、循環依存関係を解決するにはどうすればよいでしょうか? 実際、これは非常に簡単です。China Plus にキャッシュを追加するだけです。次の図を見てみましょう。

ここではキャッシュプールを紹介しました。

AService のインスタンスを作成する必要がある場合、最初に Java リフレクションを通じて元の AService を作成します。この元の AService は、単に新しい (実際にはリフレクションによって作成されたばかり) 属性が設定されていない AService として理解できます。では、まずこの AService をバッファー プールに保存します。

次に、AService のプロパティに値を設定すると同時に、AService の依存関係を処理する必要がありますが、この時点で AService が BService に依存していることがわかったので、BService オブジェクトを作成しました。 BService を作成すると、BService が AService に依存していることがわかりました。このとき、バッファ プールから AService を取り出して使用し、BService が作成されるまで後続の BService 作成のプロセスを続行し、それを AService に割り当てます。 、そして、AService と BService の両方が作成されます。

BService によってバッファー プールから取得された AService は、実際の最終的な AService ではなく、半完成品であると言う友人もいるかもしれませんが、Java は参照によって渡されることを知っておく必要があります (値によって渡されると考えることもできます)。 , ただし、この値はメモリアドレスです)、そのときBServiceが取得したのはAServiceの参照、はっきり言ってただのメモリアドレスであり、このアドレスに従ってAServiceが見つかったので、AServiceが作成された場合その後、BService によって取得された AService が完成します。

次に、上記のキャッシュ プールは Spring コンテナ内で、earlySingletonObjects と呼ばれる特別な名前を持ちます。完全なライフ サイクルを経ないと、Bean のプロパティは設定されておらず、Bean に必要な依存関係もまだ設定されていない可能性があります。注射された。他の 2 つのキャッシュ レベルは次のとおりです。

  • singletonObjects: これは第 1 レベルのキャッシュです。第 1 レベルのキャッシュには、完全なライフサイクルを経たすべての Bean が格納されます。つまり、Bean が作成、属性の割り当て、さまざまなプロセッサの実行に至るすべてを経験したことになります。 , Bean を取得する必要がある場合、まず 1 次キャッシュに行って Bean を見つけます。1 次キャッシュに何もない場合は、2 次キャッシュに行くことを検討します。
  • singletonFactories: これは 3 番目のレベルのキャッシュです。1次キャッシュ、2次キャッシュではキャッシュのキーはbeanName、キャッシュの値はBeanオブジェクトですが、3次キャッシュではキャッシュの値はLambda式となり、これを通じてターゲットを作成できます。 オブジェクトのプロキシ オブジェクト。

上記の紹介によると、循環依存関係を解決するには 1 レベル キャッシュと 2 レベル キャッシュで十分であるのに、なぜ 3 レベル キャッシュがあるのか​​と奇妙に思う友人もいるかもしれません。次に、AOP の状況を考慮する必要があります。

2.2 AOP がある場合の対処方法

上記で紹介したのは普通のBean作成ですので特に問題はありません。しかし、Spring にはもう 1 つ非常に重要な機能、それが AOP です。

そうは言っても、Spring には AOP の作成プロセスについて友人と話さなければなりません。

通常、最初にリフレクションを通じて Bean インスタンスを取得し、次に Bean の属性を入力します。属性が入力された後、次のステップはさまざまな BeanPostProcessor を実行します。Bean 内にプロキシする必要があるメソッドがある場合、システムは、対応するポストプロセッサが自動的に設定されます。Songe が簡単な例を示します。次のサービスがあるとします。

@Service
public class UserService {

    @Async
    public void hello() {
        System.out.println("hello>>>"+Thread.currentThread().getName());
    }
}

次に、システムは
AsyncAnnotationBeanPostProcessor という名前のプロセッサを自動的に提供し、このプロセッサ内でプロキシ UserService オブジェクトを生成し、このオブジェクトを使用して元の UserService を置き換えます。

皆さん、理解する必要があるのは、元の UserService と新しく生成されたプロキシ UserService は 2 つの異なるオブジェクトであり、2 つの異なるメモリ アドレスを占有しているということです。

以下の写真を振り返ってみましょう。

AService が最終的にプロキシ オブジェクトを生成する場合、AOP を処理するステップにまだ達していないため、元の AService は実際にはキャッシュ プールに格納されます (最初に各属性に値を割り当て、この結果、BService がキャッシュプールから取得した AService が元の AService になります。BService の作成後、AService の属性の割り当てが完了します。その後の AService の作成処理で、AService はプロキシ オブジェクトになる、いいえ キャッシュ プール内の AService が壊れ、最終的に BService が依存する AService は、最終的に作成された AService と同じではなくなります。

この問題を解決するために、Spring では 3 レベルのキャッシュ singletonFactories を導入しています。

singletonFactories の動作メカニズムは次のとおりです (AService が最終的にプロキシ オブジェクトであると仮定します)。

AService を作成するときは、元の AService がリフレクションを通じて作成された後、まず現在の Bean が現在のレベル 1 キャッシュに存在するかどうかを判断し、存在しない場合は次のようにします。

  1. まず 3 次キャッシュにレコードを追加します。レコードのキーは現在の Bean の beanName、値は Lambda 式 ObjectFactory です。この Lambda を実行することで、現在の AService のプロキシ オブジェクトを生成できます。
  2. 次に、現在の AService Bean が 2 次キャッシュに存在する場合は、それを削除します。

次に、AService の属性に値を割り当て続けます。AService には BService が必要であることがわかり、BService が作成されます。BService を作成するときに、BService には AService が必要であることがわかります。そこで、まず 1 次キャッシュに移動して調べます。 AService があるかどうか。ある場合はそれを使用し、ない場合は 2 次キャッシュに移動して AService があるかどうかを確認し、ある場合はそれを使用し、ない場合は 3 次キャッシュに移動して AService を見つけます。 ObjectFactory を呼び出し、ここで getObject メソッドを実行します。このメソッドは実行中です。プロキシ オブジェクトを生成する必要があるかどうかを判断し、必要に応じてプロキシ オブジェクトを生成し、生成する必要がない場合はそれを返します。プロキシ オブジェクトを取得し、元のオブジェクトを返します。最後に、取得したオブジェクトを次回使用するために 2 次キャッシュに保存し、3 次キャッシュ内の対応するデータを削除します。このようにして、AServiceが依存するBServiceが作成されます。

次に、AService の改善を続け、さまざまなポストプロセッサを実行します。この時点で、一部のポストプロセッサは AService のプロキシ オブジェクトを生成したいと考えていますが、AService はすでにプロキシ オブジェクトであるため、生成する必要がないことがわかり、直接既存のプロキシ オブジェクトを使用します。AService の代わりにプロキシ オブジェクトを使用できます。

これまでのところ、AService と BService の両方が完了しています。

本質的に、singletonFactories は AOP プロセスを進めます。

3. まとめ

一般に、Spring は 2 つの重要な点を把握することで循環依存関係を解決します。

  • 早期公開: 新しく作成されたオブジェクトに値が割り当てられていない場合、他の Bean による早期参照のために公開され、キャッシュに配置されます (二次キャッシュ)。
  • 事前の AOP : A が B に依存するときに、循環依存関係が発生しているかどうかを確認します (確認方法は、作成中の A にマークを付けることです。その後、B には A が必要で、B が A を作成すると、A が作成されていることがわかります)。 (循環依存関係が発生したことを意味します)、循環依存関係が発生した場合、事前に AOP 処理が実行され、処理後に使用されます (3 レベル キャッシュ)。
本来、AOPの処理は、属性に値を代入した後、各種ポストプロセッサ(AbstractAutoProxyCreator)でAOP処理を行うものでしたが、現在ではAOP処理は行われなくなりました。

ただし、3 レベル キャッシュではすべての循環依存関係を解決できるわけではないことに注意してください。そのため、これについては記事の後半で引き続き詳しく説明します。

おすすめ

転載: blog.csdn.net/mxt51220/article/details/131785210