Spring 6の循環依存と早期プロキシ:3段階キャッシュの実際の限界
前提:Spring Framework 6、Spring Boot 3。具体的なプロキシ不整合のケースを1つ解説します。結論をすべてのBeanの挙動に一般化することはありません。
1. 問題のモデル
典型的な循環依存:
A -> B -> Repository -> A
依存の注入がBeanの初期化完了より前に行われる場合、Springは3段階キャッシュを通じて早期参照を公開することがあります。その後のBean初期化でBeanPostProcessorがプロキシを作成することもあり、次のような不一致が生じます:
Early Reference != Final Bean
2. 3段階キャッシュでできること
3段階キャッシュのObjectFactoryは早期参照を提供できます。SmartInstantiationAwareBeanPostProcessorを実装している後処理器は、getEarlyBeanReferenceを使って早期プロキシの処理に参加できる場合があります。
AbstractAutoProxyCreatorは早期プロキシの参照を記録し、初期化後に同じ自動プロキシが重複して作られないようにします。ただし、すべてのBeanPostProcessorが早期参照に介入するわけではありません。
一般的な後処理器がpostProcessAfterInitializationでのみBeanをラップすると、早期参照と最終オブジェクトの同一性が崩れる可能性があります。実際templ的发生するかは、Springのバージョン、後処理器の順序、プロキシの種類によって異なります。
3. Spring Boot 3での対処方針
Spring Boot 2.6以降は、循環依存をデフォルトでより厳しく扱うようになりました。Boot 3のプロジェクトでは、優先して循環依存を解消してください。spring.main.allow-circular-references=trueを既定の修正策として使うのは避けてください。
推奨する順序は次のとおりです:
- サービスとRepositoryの責務を分割する
- イベントやインターフェースを使って依存の向きを反転する
- 本当に遅延作成が必要な依存には@Lazyを使い、ライフサイクルを評価する
- トランザクション、キャッシュ、非同期処理、例外変換、カスタム後処理器がプロキシを作成するかを調べる
4. トラブルシューティング
実際に使われているSpring Frameworkのバージョンを記録します。Beanのプロキシ種別とAdvisorを確認します。getEarlyBeanReferenceとpostProcessAfterInitializationを個別に追跡します。複数の後処理器が同じBeanを重複してラップしていないか確認します。allowCircularReferencesとBeanDefinitionの順序を確認します。
3段階キャッシュは、「すべての循環依存を解決できる」ことを保証するものではありません。コンテナが特定のライフサイクル段階で早期参照を提供する仕組みにすぎず、最終的には明確で保守しやすい依存グラフが必要です。