序文
Spring が循環依存関係をどのように解決するかは、Javaのインタビューの質問であり、過去2年間で一般的になりました。
実際、作者自身は、この種のフレームワークのソースコードの質問にまだ懐疑的です。
もし私が、インタビュアーが「注入プロパティがnullの場合、調査からいくつかの方向に進むことができます」などと尋ねるかもしれません これらのシーンの問題。
この記事を書いたところで、話をやめ、Springが循環依存関係を解決する方法を見て、循環依存関係の本質を説明しましょう 。
テキスト
一般的に言って、Springが循環依存関係を内部的に解決する方法を尋ねる場合 、プロパティは単一のデフォルトシングルトン Beanで相互に参照するシナリオである必要があります。
たとえば、いくつかのBean間の相互参照:
「サイクル」して自分に依存することさえ:
最初に前提を説明します:プロトタイプ (プロトタイプ)シナリオは循環依存関係をサポートしていません通常、AbstractBeanFactory
クラスの下の判断に行き、例外をスローします。
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
その理由は簡単ですが、新しいAを作成する と、プロトタイプフィールドBが 注入されることがわかり、新しいBがプロトタイプフィールドAに注入されることがわかります...
これは人形です。最初にStackOverflow か OutOfMemoryかを推測しますか ?
Springはあなたが推測できないことを恐れているので、最初にBeanCurrentlyInCreationExceptionをスローし ます
公式のドキュメントは言うまでもなく、コンストラクターに基づく循環依存関係はすべて対決です。循環依存関係をサポートするためにコンストラクターを注入する場合、コンストラクターは存在しないため、コードを変更することをお勧めします。
では、デフォルトのシングルトン属性注入シナリオで は、Springは循環依存関係をどのようにサポートしますか?
Springは 循環依存関係を解決します
まず、Springは3つのマップを内部で保持してい ます。これは、通常3レベルのキャッシュと呼ばれています。
著者は、Springのドキュメントを調べましたが、3レベルのキャッシュの概念は見つかりませんでした。これは、理解を容易にするためのローカル語彙である場合もあります。
Spring DefaultSingletonBeanRegistry
クラスでは、これらの3つのマップがクラスの上にぶら下がっています。
-
singletonObjectsは、「シングルトンプール」および「コンテナ」として一般的に知られている、最も身近な友人であり、シングルトンBeanが作成される場所をキャッシュします。
-
singletonFactoriesは、Beanを作成した元のファクトリをマップします
-
earlySingletonObjects は、Beanの初期参照をマップします。つまり、このMapのBeanは完全ではなく、「Bean」と呼ばれることさえできず、単なる インスタンスです。
最後の2つのマップは、実際には「飛び石」レベルにあります。これらは、Beanを作成するときに役立つだけで、作成後にクリアされます。
そのため、おそらくすべてのコメントがCache ofで始まるため、前の記事の「3レベルキャッシュ」という用語について少し混乱していました。
なぜ後の2つのマップが飛び石になるのでしょうか?最終的にsingletonObjectsに配置される Beanが、必要な「クールで白い」カップであると仮定し ます。
次に、Springは2つのカップ、つまりsingletonFactoriesとEarlySingletonObjectsを準備して、前後に数回「注ぎ込み」、お湯を「涼しい白く開いた」状態に乾燥させてsingletonObjectsに入れ ます。
ゴシップはない、それはすべて絵に集中している。
上記はGIFです。表示されない場合は、まだ読み込まれていない可能性があります。コンピュータカードではなく、3秒間1フレーム。
著者は、Springの主な手順を簡略化するために 17枚の絵を描きました。GIFの上には、前述の3レベルのキャッシュがあり、主な方法を以下に示します。
もちろん、この時点では、Springのソースコードと併せて確認する必要があります。そうしないと、理解できない場合があります。
概要またはインタビューを取得したいだけの場合は、最初に、上記で説明した「3レベルキャッシュ」と、以下で説明する本質を思い出すことができます。
循環依存の性質
上記の循環依存関係をSpringがどのように処理するかを理解したら、「ソースコードの読み取り」の考え方から飛び出しましょう。次の特性を持つ関数を実装するとしたら、どうしますか?
-
指定されたいくつかのクラスインスタンスをシングルトンとして設定します
-
クラスのフィールドもシングルトンのインスタンスです
-
循環依存関係をサポートする
たとえば、クラスAがあるとします。
public class A {
private B b;
}
クラスB:
public class B {
private A a;
}
単刀直入に言えば、あなたが聞かせて春を模倣:Aふり 及び Bは @Componentで装飾され、
そしてクラスのフィールドをふり @Autowiredによって装飾され、処理後に地図でそれらを置きます。
実際、それは非常に簡単です、作者は参考のために大まかなコードを書きました:
/**
* 放置创建好的bean Map
*/
private static Map<String, Object> cacheMap = new HashMap<>(2);
public static void main(String[] args) {
// 假装扫描出来的对象
Class[] classes = {A.class, B.class};
// 假装项目初始化实例化所有bean
for (Class aClass : classes) {
getBean(aClass);
}
// check
System.out.println(getBean(B.class).getA() == getBean(A.class));
System.out.println(getBean(A.class).getB() == getBean(B.class));
}
@SneakyThrows
private static <T> T getBean(Class<T> beanClass) {
// 本文用类名小写 简单代替bean的命名规则
String beanName = beanClass.getSimpleName().toLowerCase();
// 如果已经是一个bean,则直接返回
if (cacheMap.containsKey(beanName)) {
return (T) cacheMap.get(beanName);
}
// 将对象本身实例化
Object object = beanClass.getDeclaredConstructor().newInstance();
// 放入缓存
cacheMap.put(beanName, object);
// 把所有字段当成需要注入的bean,创建并注入到当前bean中
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// 获取需要注入字段的class
Class<?> fieldClass = field.getType();
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
// 如果需要注入的bean,已经在缓存Map中,那么把缓存Map中的值注入到该field即可
// 如果缓存没有 继续创建
field.set(object, cacheMap.containsKey(fieldBeanName)
? cacheMap.get(fieldBeanName) : getBean(fieldClass));
}
// 属性填充完成,返回
return (T) object;
}
このコードの効果は、循環依存関係が実際に処理され、処理が完了すると、完全な「Bean」 がcacheMapに配置されることです。
これは「Springが循環依存関係を解決する方法」ではなく、「循環依存関係」の本質です。
この例の理由は、少数のポットフレンドが「ソースコードの読み取りの泥沼」に陥り、問題の性質を忘れていることを見つけるためです。
ソースコードを見るためにソースコードを見ましたが、理解できませんでしたが何なのか忘れてしまいました。
本当に理解できない場合は、最初に基本バージョンを作成し、Springがこのように実装されている理由を逆にすることをお勧めします。
何?問題の本質は、実際には2つの合計です。
たった今コードを読んだ後、何か親しみがありますか?そうです 、問題解決は2つの合計の問題解決に似ています。
2つの合計の意味がわからないので 、紹介します。
2つの合計 は、Webサイトのリートコードのシリアル番号1の質問です。これは、ほとんどの人のアルゴリズムの紹介の最初の質問です。
アルゴリズムに馬鹿にされがちな企業は、インタビュアーから任命されて集まります。次に 、シーンを2サム歩きます。
質問の内容は、配列が与えられ、数値が与えられます。返された配列を追加して、指定した数の 2つのインデックスを取得できます。
例えば:、与えられたnums = [2, 7, 11, 15], target = 9
後、返す [0, 1]
ので、2 + 7 = 9
この問題の最善の解決策は、+ HashMapを1回トラバースすることです。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
//作者:LeetCode
//链接:https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-2/
//来源:力扣(LeetCode)
まず、マップに移動して必要な数を見つけます。そうでない場合は、現在の数をマップに保存し、必要な数を見つけたら、一緒に戻ります。
上記のコードと同じですか?
まずキャッシュに移動してBeanを見つけます 。そうでない場合は、現在のBeanをインスタンス化して マップに配置します。現在のBeanに依存関係がある場合は、マップから取得できます。