目次
序文
読書の準備
過去のレビュー:
- Spring の根底にある核原理の分析[学習難易度: ★★☆☆☆ ]
- 手書き簡単スプリングコンテナ工程解析【学習難易度:★★☆☆☆】
- Springの基盤となるアーキテクチャの中核概念の分析[学習難易度:★★★☆☆、重要度:★★★★★ ]
- Beanのライフサイクルフローチャート【学習難易度: ☆☆☆☆☆、重要度: ★★★★★】
- Spring の Bean ライフサイクルのソースコード解析 —— フェーズ 1 (スキャンして BeanDefinition を生成) [学習難易度: ★★☆☆☆、重要度: ★★★☆☆ ]
- Spring Bean ライフサイクルのソースコード解析 フェーズ 2 (1) (IOC のインスタンス化) [学習難易度: ★★★★★、重要度: ★★★☆☆ ]
- Spring Bean ライフサイクルのソースコード解析 フェーズ 2 (2) (IOC 属性充填・依存性注入) [学習難易度: ★★★★★、重要度: ★★★★★ ]
読書のアドバイス
- ソースコードを見て、細部に絡むことを忘れないでください。そうしないと、簡単に行き詰ってしまいます。通常はメインプロセスだけを見てください
- 理解できない場合は、クラスのアノテーションまたはメソッドのアノテーションを見てください。Spring は非常に優れたソース コードであり、注釈が適切に配置されています
- アイデアユーザーの方はF11のブックマーク機能をもっと活用してください。
- Ctrl + F11 ファイル/フォルダーを選択し、ニーモニックを使用してブックマークを設定/解除します (必須)
- Shift + F11 ポップアップブックマーク表示レイヤー (必須)
- Ctrl +1、2、3...9 対応する値のブックマーク位置に移動します (必須)
予備知識
Bean のライフサイクル
Bean のライフサイクルとは、Bean が Spring でどのように生成されるかということを指します。Bean の生成手順は次のとおりです。 (追記: ここでは Bean のライフサイクルについては詳しく説明しません。一般的なプロセスについてのみ説明します)
- beanDefinition に従って Bean をインスタンス化する
- 元のオブジェクトにプロパティを設定します (依存関係の注入)
- 初期化前
- 初期化
- 初期化後
- 生成された Bean をシングルトン プールに配置します。
最終的に生成されたプロキシ オブジェクトをシングルトン プール (ソース コードでは singletonObjects と呼ばれます) に配置し、次回 getBean を実行するときにシングルトン プールから直接取得します。
循環依存関係の生成
循環依存関係に関しては、誰もがよく知っていますが、循環依存関係のコードは次のとおりです。
@Component
public class CircularA {
@Autowired
CircularB b;
}
@Component
public class CircularB {
@Autowired
CircularA a;
}
しかし、循環依存関係がどのように生成され、それを解決する方法について考えたことがありますか? ここで、私たち Spring 作者と同じように、循環依存関係を解決する方法を考えている皆さんに推論を与えたいと思います。
春の 3 つの地図
ここで、シングルトン Bean を取得するときに、Spring のソース コードに表示される 3 つのマップとは何なのか、またそれらを格納するために何が使用されるのかについて、事前に一般的な概要を説明しておきます。それらは次のとおりです。
Map<String, Object> singletonObjects
: レベル 1 キャッシュ。これは私たちがよくシングルトン プールと呼ぶもので、ここに Bean が保存されます。Spring のライフサイクルを完全に通過しました。[Spring によって設計されたライフサイクルを完了しました](ここで完全なライフサイクルを体験するということは、インスタンス化の前後や初期化の前後を経験する必要があるという意味ではありません。簡単に言えば、Spring が承認した成熟した Bean です)Map<String, Object> earlySingletonObjects
: 二次キャッシュ。ここに格納されているのは直訳すると「Early Singleton Bean」です。早いとは何ですか?前回の「Mature Bean」との相対的なものですが、【まだライフサイクルを完了していないBean】。Map<String, ObjectFactory<?>> singletonFactories
:L3キャッシュ。直訳すると「シングルトンビーン工場」となります。実際、私は今でも、前に述べた固有名詞を使って説明するのが好きです。本番Beanのフックメソッドのキャッシュ。
授業内容
注: 以下の図の Bean ライフ サイクル フローチャートは実際のサイクルを表すものではなく、便宜上、いくつかの処理のみを取り上げています。
1. [レベル 3 キャッシュ] 進化的推論
1. 1次キャッシュの進化的推論のみ
まず図を見てみましょう 3次キャッシュが存在しない以前、1次キャッシュが1つしかない場合、AがBに依存し、BがAに依存すると、次のような現象が発生します。
明らかに、最初の作成プロセスでは、シングルトン プールにはオブジェクト B もオブジェクト A も存在しません。結局のところ、まだ 2 番目のステップ (属性の注入) に到達しただけで、生成されたオブジェクトをシングルトン プールに入れるのは最後のステップです。したがって、上の図の状況では、外部からの介入がない場合、2 つの豆の間に閉ループが形成され、これを解くことはできません。これは明らかに私たちが望んでいる結果ではありませんよね? では、この問題をどうやって解決すればいいのでしょうか?
1.1 インスタンス化後に生成されたオブジェクトをシングルトンプールに直接入れる
このとき、ごく普通に考えると、以下のように事前にシングルトンプールに入れておけばいいのではないか、
壊れているのではないか?ふふふ、
それは理にかなっているとしか言えませんが、大したことではありません。Spring は実際にシングルトン プールからオブジェクトを取得するため、これは[ライフサイクルを完了していない半完成オブジェクト]を事前に公開することと同じです。このように、マルチスレッド環境において、誰かがシングルトンプールにアクセスしに来て、このBeanAを直接取得し、その中のメソッドを呼び出した場合、[プロパティインジェクション]がなければGになるのではないでしょうか?はい、それは同時実行の安全性の問題です。ここでは、このスキームを直接渡すことしかできません。
PS: もちろん、一次キャッシュをロックすれば解決できるという人もいるとは思いますが、ああ、確かに解決できます。しかし、パフォーマンスはどうなのか気になったことはありませんか...
1.2 概要
- インスタンス化後、[ライフサイクルを完了していない半完成オブジェクト] がシングルトン プールに入れられ、スレッド セーフティの問題が発生します。
(追記: ここでの結論に注意してください。後でテストします!!! /狗头/狗头)
2. 2次キャッシュの進化推論の紹介
2.1 中間マップストレージのインスタンス化後の初期オブジェクトの導入 (二次レベルキャッシュの疑い)
ごく普通に考えて、新しいマップを追加し、インスタンス化後すぐに保存します。いずれにせよ、すでにインスタンス化されており、アドレスも固定されているので、後からどのように操作してもこのアドレスのオブジェクトを操作することになり、事前にこのオブジェクトを公開しておいても結果には全く影響しません。
上の図に示すように、以前にインスタンス化されたオブジェクトを保存するために中間キャッシュ マップを追加します。これは可能ですか? まあ、フローチャートから判断すると、これが本当に最終的な答えのようです。
しかし、[AOP をどうするか]、正確に言えば、必要なのは [プロキシ オブジェクトをどうするか] と尋ねたら、どう答えるでしょうか。? 明らかに、この中間テーブルには元のオブジェクトが格納されますが、場合によってはプロキシ オブジェクトが必要になることがあります。ちょっと調べてみると、別の問題があることがわかりました。さて、この計画をさらに改善していきましょう。
(追記: この質問は、このステップで事前に AOP プロキシを考慮する必要があることを意味します。誰もがこの結論を覚えておく必要があります)
(注: 私は AOP の必要性の例を示しているだけです。実際、AOP はプロキシを必要とするあらゆるプロセスを指します。マルチレベルのプロキシ状況があると思いますか)
(注: 私は AOP の必要性の例を示しているだけです。実際、AOP はプロキシを必要とするあらゆるプロセスを指します。マルチレベルのプロキシ状況があると思いますか)
(注: 私は AOP の必要性の例を示しているだけです。実際、AOP はプロキシを必要とするあらゆるプロセスを指します。マルチレベルのプロキシ状況があると思いますか)
2.2 2.1 でプロキシ化する必要がある問題を解決する (2 次キャッシュの疑い)
以上です。AOP プロセスにもう 1 つのステップを追加するだけです、ふふふ。でも、いつものやり方だと、もう「ふふふ」してしまっているので、本当に大丈夫ですか?はー、本当にそうだね!本当に大丈夫です。では、なぜ 3 次キャッシュが必要なのでしょうか?
3. 3次キャッシュの進化的推論の導入
3.1 L3 キャッシュを使用する理由
この時点で、私はふりを始めようとしています。(Spring がこのように書いているふりをしているのではないかとさえ疑っています。笑、冗談です。) 実際、
インターネット上では多くの議論があります。また、授業で先生が言ったことと組み合わせて、何百もの学校の長所を要約しました。そして次のような結論を導き出しました。
- ライフサイクルが壊れている!これが一番重要な理由だと思います, しかし、これもわかりにくいです。どうやって理解しますか?私が最初に春についてどのように説明したか覚えていますか? Springの核心は何ですか?AOP 実装が Bean のライフサイクルのどの部分にあるか知っていますか?
- 最初の質問: Spring は AOP テクノロジを実装する IOC コンテナです
- 2 番目の質問: Spring のコアは IOC と AOP ですが、すべての基盤は IOC から来ています。
- 3 番目の質問: AOP は Bean ライフサイクルの [初期化後] 段階で実装されます。なぜなら、AOP テクノロジーの現在の実装も、Spring によって提供される多くの拡張ポイントの一部に基づいています。。たとえば、AOP の実装では BeanPostProcessor が使用されます。ここで明かされる意味とは何でしょうか?それは次のことを意味すると思います:Spring 内部では、AOP は追加の拡張機能としてのみ使用されます。。Spring の拡張ポイントと SpringMVC をベースに Mybatis を実装したかのようです。
PS: それで、ここに来たら、これ[ライフサイクルが壊れている]ことをどう理解すればよいかわかりますか? インスタンス化後に AOP を行う必要があるかどうかを判断すると、[プロパティの注入]、[初期化前]、[初期化]、[初期化後] などのライフサイクルを行っていないことになります。AOP を開始する必要があります。 AOP プロセスを [初期化後] から [属性注入前] に直接移動します。また、この AOP を実装するプロセスでは、次のようなメソッドを呼び出す必要があります。
for(BeanPostProcessor bp : this.beanPostProcessorsCache) { bp.postProcessAfterInitialization(bean); }
ただし、このコードは実際には [初期化後] 後で呼び出されます。友人の中にはこう言う人もいると思います。「それなら、AOP を実装する指定された BeanPostProcessor をループできるのですか?」そうですね、本当に効果があります。ただし、上で述べたように、Spring の観点から見ると、AOP は私の IOC の拡張にすぎません。この観点から見ると、この実装は少し煩雑であり、セマンティクスもわずかに変更されています。
- 循環依存関係は頻繁に発生します。皆さんにお聞きしたいと思いますが、実際の使用シナリオでは循環依存関係が多くありますか? 兄さん、私は 4 年以上 Java コードを書いてきましたが、覚えているのは数回だけです。では、上記の解決策を振り返ってみてはどうでしょうか?Beanをインスタンス化して生成するたびに判断します。!ちょっと冗長ですか?
- コードスタイル。これは非常に抽象的で、あまり主流の発言ではありませんが、理にかなっています。この文をどう理解すればいいでしょうか?
実際には、2 と 3 を組み合わせて、[1] の最後の苦労に基づいて構築する必要があります。つまり、[インスタンス化] が完了した後も [AOP が必要かどうか] の判断を開始する必要があります。まず第一に、なぜ AOP が事前に判断される必要があるのか、まだ覚えていますか? 循環依存関係が必要なため。言い換えると、実際には【循環依存がある】場合に【AOPかどうかを判断】する必要がありますよね?つまり、[循環依存関係がない]場合は、まったく必要ありません。。では、ここでAOPを直接判断すると粒度が大きすぎるのでしょうか?
3.2 3.1の問題を解決する【粒度が大きい】
「粒度が大きい」という問題に対して、とても賢い学生さんたちは、先ほどお話しした「3次キャッシュ」の設計をすでに考えていると思います。ほら、私はそれをフック関数のキャッシュとして設計しました。 Springの[レベル1+レベル3]で構成される2次キャッシュでも粒度が大きいという問題は解決できるのでしょうか?(これは少し理解するのが難しいので、ラムダ式をよく読んでください)、次のようになります。
(追記: マップは Spring の 3 次キャッシュに相当します)
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)
singletonFactories.put(beanName, ()->{
determinedAop(bean);});
void determinedAop(Object bean) {
Object exposedObject = object;
if (object 是否需要被代理) {
exposedObject = 代理对象obejct;
}
return exposedObject;
}
はい、それは少し似ています。しかし、まだ 1 つの判断が欠けています。それは、[循環依存関係があるかどうか] の判断です。これも扱いは簡単で、上記のマップに値があるかどうかを判断するだけです。(追記: そうだと思います。しかし、作成中の Bean の名前を保存するための新しいセットが Spring に追加されました。興味のある方は、私の前回の内容を読んでください。) しかし、これで十分でしょうか
? 足りない。循環依存関係を必要とする 3 番目のクラスがある場合はどうなるでしょうか? 次のように:
@Component
public class CircularA {
@Autowired
CircularB b;
@Autowired
CircularC c;
}
@Component
public class CircularB {
@Autowired
CircularA a;
}
@Component
public class CircularC {
@Autowired
CircularA a;
}
効果は以下の通りです。
写真では伝わらないかもしれませんので、直接お伝えさせていただきます。以下:
上記の中間マップにはコールバック関数が格納されているため、この関数のプロキシがある場合、毎回新しいオブジェクトを返しますか? ? ? これは明らかに私たちの期待と一致しません。単一のケースでは、B と C に注入される A は同じであるはずです。何をするか?保存してください。しかし、それをシングルトン プールに直接入れることはできるのでしょうか? いや、なぜだか知っていますか?あ、理由は1.1と同じです。
3.3 3.2 でフック関数を複数回呼び出すことによって発生する問題を解決する
したがって、ここでは、上記の Bean をキャッシュするために別のキャッシュ マップが導入されています。ここでは「初期の豆」と呼んでください。(追記: マップは Spring の 2 次キャッシュに相当します)
ここまでで、3次キャッシュが導入されました。生徒たちは駄目なのか?
3.4 循環依存関係があるかどうかを判断する方法 (それほど重要ではありません)
前述したように、循環依存関係があるかどうかを判断する場合、第3レベルのマップで判断できると思いますが、結局のところ、この存在も【創造】を表しているのです。しかし、Spring は、作成される Bean の名前を保存するための新しいセットを追加しました。なぜそうなるのかというと、いくつかの理由があると思います。
- この第 3 レベルのキャッシュ マップは、理論的には使用後すぐに削除されます。これを行うことでどのようなメリットがあるのでしょうか? 複数の通話のリスクはある程度軽減されます。
- 今は
singletonsCurrentlyInCreation
Set という集合で判断されます。次に、大手企業がこの判決がどこで参照されているかを確認するためにクリックすると、多くの参照があり、ライフサイクルの範囲がより広いことがわかります。別の言い方をすると、多くの場所で Bean が [作成中] かどうかを判断する必要があるため、新しいセットを追加して保存します。第 3 レベルのマップを使用して判断するのは、[循環依存] のシーンにのみ適用されます。したがって、セットがすでに存在するので、それを直接使用するだけで、セマンティクスがより明確になります。
3.5 特別な記述
3.1、3.2、3.3 で私が行ったことは、2.2 の計画を改善するためだけに行われたことに注意してください。2.2 の結論をまだ覚えていますか? [このステップで事前に AOP を考慮する必要がある]、つまりそれは避けられず、その後の改善計画は粒度を可能な限り減らすだけです。
4. まとめ
ああ、はっきりとは言えなかったと思います。皆さんも理解してもらえなかったのではないかと思います。別の推論進化図を示します。
2 番目に、基礎となるソースコードの分析 (拡張)
循環依存関係のソース コード エントリはここにあります。AbstractAutowireCapableBeanFactory#doCreateBean()
名前を見ると、なんとなく感動するはずです。インスタンス化するときにもこのメソッドを渡しませんか。実際、厳密に言えば、ここでのソース コードについては何も説明する必要はありません。重要なのは、その基礎となる原理を理解することです。カジュアルに投稿させていただきます。中のコメントに注目してください。[循環依存関係] に関連するコード内でマークを付けておきます。
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// 实例化bean
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 合并beanDefinition Bean后置处理器
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// 【循环依赖】关键源码一
// 这里就是我们在分析中说的注册钩子方法,判断是否需要【循环依赖】
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 【循环依赖】关键源码二
// 但是这里看不出来,因为AOP就是在下面的initializeBean里面的,需要挖掘一下才能找到
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
// 【循环依赖】关键源码三
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] 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.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
上に示したように、上記には 3 つの重要なソース コードがあります。
2.1 最初の重要なソースコード
ここでの最初の重要なソース コードは次のとおりです。
// 【循环依赖】关键源码一
// 这里就是我们在分析中说的注册钩子方法,判断是否需要【循环依赖】
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
allowCircularReferences
[循環依存関係を許可する]かどうかを判断し、isSingletonCurrentlyInCreation(beanName)
[循環依存関係が存在する]かどうかを判断します。次に、addSingletonFactory
登録されたフック メソッドを呼び出しますgetEarlyBeanReference(beanName, mbd, bean)
。フック メソッドは次のように実装されます。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
ほら、[インテリジェントなインスタンス化対応 Bean ポストプロセッサSmartInstantiationAwareBeanPostProcessor
] があります。そして、そのメソッドを呼び出して、getEarlyBeanReference
[初期 Bean] オブジェクトへの参照を取得します。ねえ、この [初期 Bean] は、出てくるとすぐにおなじみの 2 次キャッシュですが、何も問題ありませんか?
この点から、この Bean ポストプロセッサが Spring の AOP 実装と深い関係があることがわかります。ただし、AOPのコアクラスは実はこのインターフェースの実装クラスであることを先に言っておきますが、AnnotationAwareAspectJAutoProxyCreator
その掘り下げ方については後ほどAOP講座を紹介します。しかし、最終的にはこのメソッドを徹底的に掘り下げgetEarlyBeanReference
、最終的にそのソース コードは次のようになりました。 (PS: 以下のメソッドを呼び出すと、AOP の判断が事前に行われることを意味します)
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
このソースコードは非常にシンプルで、基本的には名前を見れば何を意味するかがわかります。結局のところ、return wrapIfNecessary
[AOP が必要かどうか] に応じて、それは元のオブジェクトまたはプロキシ オブジェクトにすぎません。
偉い人は、this.earlyProxyReferences.put(cacheKey, bean);
キャッシュの 2 行目は何のためにあるのかと疑問に思うかもしれません。おい、通常の AOP は [初期化後] だ。予定より進んでいるから、記録してみないか? 記録しない場合、[初期化後]に進むときに再度AOPを実行しますか? ほら、それだけです。
2.2 2 番目の重要なソース コード
2 番目の重要なソース コードには次のように書かれています。
// 【循环依赖】关键源码二
// 但是这里看不出来,因为AOP就是在下面的initializeBean里面的,需要挖掘一下才能找到
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
initializeBean
しかし実際には、内部の[初期化後]処理について話したいと思います。経験豊富な学生、または以前に私の Spring 入門を見た学生は、[初期化後] がどのメソッドであるかを知っているはずですが、インデックスは付けず、呼び出し元のソース コードのみを示します。
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
呼び出し先はこんな感じですが、AOPを扱うBeanポストプロセッサのソースコードは以下の通りです、途中インデックスはしません、皆さんに見てもらった感想だけ載せておきます。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
earlyProxyReferences
ほら、ここではAOP が事前に実行されているかどうかを判断する使い方があります。ただし、ここには非常に重要な詳細があり、これは以下の 3 番目の重要なソース コードについて説明するときに使用されます。
今すぐ:そうであればthis.earlyProxyReferences.remove(cacheKey) == bean
、事前に AOP を実行したことを意味し、この時点ではプロキシ オブジェクトではなく、元のオブジェクトが直接返されます。(通常のロジックでは、事前に AOP がない場合、ここでプロキシ オブジェクトが返されます)。この結論を念頭に置いて検討していきます
2.3 3 番目の重要なソース コード
次のように:(注: このコードを入力するには重要な前提条件があります。つまりearlySingletonExposure==true
、以前に循環依存関係があったことを意味します。)
// 【循环依赖】关键源码三
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] 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.");
}
}
}
}
正直、話すのが面倒な上に、この知識は少々不評で、説明するのが難しく、あまり目にすることはないと思われます。ただし、ここで処理すべきロジックが何であるかは、最後にスローされる例外情報によって大まかに知ることができます。
例外のリテラル翻訳: 「beanName」という名前の Bean は、以前のバージョンで循環参照の一部として他の Bean [xxxx] に挿入されましたが、最終的にラップされました (ラップとはプロキシを意味します)。これは、上記の他の Bean が最終バージョンではない Bean を使用していることを意味します。
。。。。。。。。。。。。。。。。。。。。
人間の言葉で言えば、beanName という名前の Bean は、循環依存関係で [AOP プロキシ] によってすでにパッケージ化されていますが、後の他の処理で [プロキシ パッケージ] によって再度パッケージ化されています。その結果、以前に [循環依存関係] に挿入されたプロキシ Bean は最新のプロキシ Bean ではありません。この状況が発生した場合は、エラーを報告することしかできません。
上の翻訳を見ると、何が起こっているのかが非常に明らかです。それが問題であり、問題なのです。
しかし実際には、Spring のソース コードにはまだ問題があります。2.2 の結論が存在するため、次の図の黒い判定ボックスに示されている状況が発生し、エラーが報告されます。プロセスは、A をインスタンス化するプロセスで、循環があるためです
。 A と B、および A の AOP エージェント間の依存関係 このオブジェクトは、B のプロセスでフック メソッド オブジェクトを使用して生成されるため、A を作成するプロセスでは、A はそれが AOP プロキシとして生成されたことを認識できません。で、後の段階、[初期化後]の処理に至ったとき、2.2の結論により、元のオブジェクトが使用され、Asyncでプロキシされました!最悪!これは明らかに私たちが望んでいることではありません。
2.3.1 上記問題の解決方法
実はこの考え方は非常に単純で、この問題は循環依存によって引き起こされているので、循環依存を解消しましょう。ちょっと待って、私がクラス構成を変えるように言ったと思いませんか?いいえ。そこで考えてみて、クラス構造を変更せずに循環依存関係を解消する方法はあるでしょうか?
そういえば、循環依存関係の原因をまだ覚えていますか? 【シリアルインジェクション】が必要なのは属性のせいではないでしょうか?一般的な説明は、Bean の作成プロセスでプロパティを注入する必要があるためです。Bean の作成時にプロパティを注入しない方法はありますか? ありますよ、@Lazy
コメント。
要約する
- 循環依存関係の原因と 3 レベルのキャッシュ構造の設計原則を学びます。