このインタビューの質問を解決するために Spring の循環依存関係が第 3 レベルのキャッシュを使用する理由を何度も尋ねられましたが、明確に説明するのは困難です。Spring のソース コードを読んだ後でも、循環依存関係を解決するための Spring の設計アイデアを記録し、テクノロジー愛好家と共有する必要があると思います。
初めて面接で質問されてから、たくさんの記事を読んでインスピレーションを得ましたが、実際の事例に基づいた記事が少なく忘れてしまいやすく、真実をわかりやすく説明しなければなりませんでした。面接中に面接官に「難しいですね。後で実例に基づいて循環依存関係を徹底的に理解しようと決心しました。今日は Spring がデバッグ中に循環依存関係をどのように処理するかを実例を使用して学びます。」
説明する
この記事を読む学生は、Spring の ioc プロセスと di プロセス、および Spring Bean の作成と Bean 属性の入力について理解している必要があります。
Spring コンテナから Bean を取得したことを思い出してください
まず、Bean を取得する Spring のメソッドを見つけます。これは 3 つのキャッシュから取得されます。
//一级缓存,存储可以直接使用的bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//二级缓存 存储不完整对象 不能直接用的 循环依赖时,某些依赖的属性没有设置,这个时候bean就是不完整对象
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
//三级缓冲 存储bean工厂 可以延迟调用实例化 使用的时候才调用工厂方法获取对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//从Spring容器获取bean对象方法
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;
}
复制代码
なぜここで 3 つのキャッシュが必要なのでしょうか? なぜ 2 ではないのでしょうか? 1つもありませんか?その理由を見てみましょう?
循環依存関係のシナリオをシミュレートする
クラス A とクラス B は、お互いのクラス属性を持ちます。
@Service
public class A {
private B b;
}
复制代码
@Service
public class B {
private A a;
}
复制代码
循環依存関係の相互への手動注入をシミュレートする
オブジェクト a はオブジェクト b に依存し、オブジェクト b はオブジェクト a に依存します。属性注入プロセスでは、最初に B をインスタンス化し、A を注入し、次に A を埋めます。
A a = new A();
B b = new B();
b.setA(a); //B里面注入不完整的A了
a.setB(b); //注入依赖的对象,A里面有B了,a注入完成变成完整的a
复制代码
Springの循環依存性注入処理フローのデバッグ
同じシナリオです。Spring がそれをどのように処理するかを見てください。
最初のステップは、クラス A のオブジェクト a を作成することです。
Spring では、クラス A がインスタンス化された後、インスタンス A の代わりにファクトリがキャッシュされます。なぜですか?
2 番目のステップでは、オブジェクト a の属性 b の注入を開始します。
a が b を注入すると、キャッシュは b インスタンスの代わりにファクトリ クラスを登録します。
3 番目のステップでは、b オブジェクトが属性 a オブジェクトの注入を再度開始します。
b オブジェクトは再び a オブジェクトの注入を開始しますが、この時点では、a には対応するファクトリのみが存在します。
a のプロキシ オブジェクトは A ファクトリを通じて返され、プロキシ オブジェクトは早期公開キャッシュ EarlySingletonObjects (2 次キャッシュ) に置かれます。この時点では、a は b の注入を完了しておらず、まだ半完成です。製品であるため、 a は依然として中間キャッシュに置かれます。
ステップ 4: b オブジェクトをオブジェクトに挿入する
この時点では、Bの注入は完了していますが、Aの注入はまだ完了していません。A の内側の B B の内側には値が割り当てられません
5 番目のステップは、完全な B オブジェクトを挿入することです (実際にはまだ完了していません)。
ステップ 6: a オブジェクトの b 属性を注入する
最後に、A オブジェクトの B 属性を注入します。この時点で、A オブジェクトが完成しているため、B オブジェクトも完成しています (前の手順を補うため)
オブジェクトグラフを完成させる
完全な b オブジェクト グラフ
要約すると、なぜレベル 3 キャッシュを使用するのでしょうか?
次回、面接で循環依存関係について尋ねられたら、面接官にどのように説明すればよいでしょうか?
1. Spring は、循環依存関係の問題を解決するために、singletonObjects、singletonFactories、earlySingletonObjects という 3 つのキャッシュ マップを使用します。
2. singletonObjects のキャッシュには完全なオブジェクトが格納されており、直接使用できます。
3. singletonFactories のキャッシュは遅延初期化用であり、循環依存関係を解決するための鍵です。循環依存関係の注入がある場合、注入されたオブジェクトをプロキシする必要がある場合があります。このファクトリ クラスは、プロキシ クラスのインスタンスを作成するために使用されます。
4. EarlySingletonObjects キャッシュは、b を a に注入するために使用され、b は a に注入されます。A は半完成品であり、a のような半完成 Bean のステータスを保存するために使用する必要があります。
a が b を挿入するまで待つ必要があります。その後、キャッシュが削除されます。
5. SingletonFactories とearlySingletonObjects はどちらも格納されたオブジェクトの中間状態であり、最終的に注入されたオブジェクトが完全であることを確認するために依存され、依存関係の注入が完了するとクリアされます。