IoC コンテナの内部構造:BeanFactory の継承階層と Bean 生成パイプライン
前提知識
- ›ユーザー視点での Spring の @Bean/@Component の使用経験
- ›デザインパターン(Template Method、Factory)の基本的な理解
IoC コンテナの内部構造:BeanFactory の継承階層と Bean 生成パイプライン
Spring アプリケーションの中心にあるのが IoC コンテナです。アプリケーション内のすべてのオブジェクトを生成・設定・配線し、そのライフサイクルを管理するサブシステムです。外から見ると単純に映ります。getBean() を呼び出せばオブジェクトが返ってくる、それだけのことに見えます。しかし内部は、徹底したインターフェース分離、Template Method パターン、そして循環依存を解決するための洗練されたキャッシュ機構からなる、幾重にも積み重なったアーキテクチャになっています。本記事ではその全体像を解説します。
インターフェースの階層構造:読み取り・書き込み・列挙の分離
Spring のコンテナは、関心事を精巧に分離したインターフェース継承階層によって定義されています。最も基本的なクライアントインターフェースは BeanFactory です。
spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java#L123-L156
BeanFactory が提供するのは getBean()、containsBean()、isSingleton() といった読み取り専用の操作です。アプリケーションコードが必要とするのは、基本的にこれだけです。この階層はここから枝分かれしていきます。
classDiagram
class BeanFactory {
<<interface>>
+getBean(String name) Object
+containsBean(String name) boolean
+isSingleton(String name) boolean
}
class ListableBeanFactory {
<<interface>>
+getBeanDefinitionNames() String[]
+getBeansOfType(Class) Map
}
class HierarchicalBeanFactory {
<<interface>>
+getParentBeanFactory() BeanFactory
+containsLocalBean(String) boolean
}
class ConfigurableBeanFactory {
<<interface>>
+addBeanPostProcessor(BeanPostProcessor)
+setParentBeanFactory(BeanFactory)
+registerScope(String, Scope)
}
class AutowireCapableBeanFactory {
<<interface>>
+createBean(Class) Object
+autowireBean(Object)
}
class ConfigurableListableBeanFactory {
<<interface>>
+preInstantiateSingletons()
+getBeanDefinition(String) BeanDefinition
}
class BeanDefinitionRegistry {
<<interface>>
+registerBeanDefinition(String, BeanDefinition)
+removeBeanDefinition(String)
}
BeanFactory <|-- ListableBeanFactory
BeanFactory <|-- HierarchicalBeanFactory
HierarchicalBeanFactory <|-- ConfigurableBeanFactory
ConfigurableBeanFactory <|-- ConfigurableListableBeanFactory
ListableBeanFactory <|-- ConfigurableListableBeanFactory
BeanFactory <|-- AutowireCapableBeanFactory
ConfigurableListableBeanFactory <|.. DefaultListableBeanFactory
BeanDefinitionRegistry <|.. DefaultListableBeanFactory
なぜこれほど多くのインターフェースが必要なのでしょうか。理由は三つあります。
-
読み取りと書き込みの分離。
BeanFactoryとListableBeanFactoryは読み取り専用です。書き込み操作はConfigurableBeanFactoryが担います。アプリケーションコードには読み取り用インターフェースを、インフラストラクチャコードには設定可能なインターフェースを渡すという設計です。 -
列挙と階層の分離。
ListableBeanFactoryはすべての Bean を列挙できます。HierarchicalBeanFactoryは親子ファクトリのチェーンをサポートします(Web アプリケーションで各サーブレットが独自の子コンテキストを持つ構成に使われます)。これらは直交する関心事です。 -
SPI とクライアント API の分離。
BeanDefinitionRegistryは Bean の定義を登録するための純粋な SPI です。DefaultListableBeanFactoryに実装されていますが、意図的にBeanFactoryのクライアント階層から切り離されています。
Template Method チェーン:Registry から Factory まで
実装の継承階層はインターフェースの階層に対応しており、それぞれが明確な責務を担う四つのクラスで構成されています。
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java#L118
classDiagram
class DefaultSingletonBeanRegistry {
-singletonObjects: Map
-singletonFactories: Map
-earlySingletonObjects: Map
+getSingleton(String) Object
#addSingleton(String, Object)
}
class AbstractBeanFactory {
#getBean(String) Object
#doGetBean(String) Object
#createBean(String, RootBeanDefinition)*
}
class AbstractAutowireCapableBeanFactory {
#createBean(String, RootBeanDefinition) Object
#doCreateBean(String, RootBeanDefinition) Object
#populateBean(String, RootBeanDefinition, BeanWrapper)
#initializeBean(String, Object, RootBeanDefinition) Object
}
class DefaultListableBeanFactory {
-beanDefinitionMap: Map
+preInstantiateSingletons()
+registerBeanDefinition(String, BeanDefinition)
}
DefaultSingletonBeanRegistry <|-- AbstractBeanFactory
AbstractBeanFactory <|-- AbstractAutowireCapableBeanFactory
AbstractAutowireCapableBeanFactory <|-- DefaultListableBeanFactory
各レイヤーの役割は明確です。
- DefaultSingletonBeanRegistry:シングルトンキャッシュ(循環参照のための三段階キャッシュ)を管理します。
- AbstractBeanFactory:
getBean()と template method のcreateBean()を実装します。マージ済み Bean 定義の処理、FactoryBean の逆参照、スコープの解決もここで行います。 - AbstractAutowireCapableBeanFactory:
createBean()を実装します。コンストラクタの解決、プロパティの注入、初期化処理を含む、実際の Bean 生成パイプラインがここにあります。 - DefaultListableBeanFactory:Bean 定義の格納、列挙(
getBeansOfType())、シングルトンの事前インスタンス化を追加します。
Bean 生成パイプライン:createBean() から使用可能な状態まで
getBean("myService") を呼び出すと、コンテナは最終的に AbstractAutowireCapableBeanFactory.createBean() に到達します。このメソッドは五つのフェーズからなるパイプラインを制御します。
sequenceDiagram
participant Client
participant ABF as AbstractBeanFactory
participant AACBF as AbstractAutowireCapableBeanFactory
participant BPP as BeanPostProcessors
Client->>ABF: getBean("myService")
ABF->>AACBF: createBean(name, mbd, args)
Note over AACBF: Phase 0: resolveBeforeInstantiation
AACBF->>BPP: postProcessBeforeInstantiation()
Note right of BPP: Can short-circuit with proxy
AACBF->>AACBF: doCreateBean(name, mbd, args)
Note over AACBF: Phase 1: createBeanInstance
AACBF->>AACBF: Constructor resolution + instantiation
Note over AACBF: Phase 2: applyMergedBeanDefinitionPostProcessors
AACBF->>BPP: postProcessMergedBeanDefinition()
Note right of BPP: @Autowired metadata caching
Note over AACBF: Phase 3: Early singleton exposure
AACBF->>AACBF: addSingletonFactory() for circular refs
Note over AACBF: Phase 4: populateBean
AACBF->>BPP: postProcessProperties()
Note right of BPP: Actually injects @Autowired fields
Note over AACBF: Phase 5: initializeBean
AACBF->>AACBF: invokeAwareMethods()
AACBF->>BPP: postProcessBeforeInitialization()
AACBF->>AACBF: invokeInitMethods() [afterPropertiesSet]
AACBF->>BPP: postProcessAfterInitialization()
Note right of BPP: AOP proxies created here
AACBF-->>Client: Fully initialized bean
各フェーズが実行される doCreateBean() を詳しく見ていきましょう。
フェーズ 1 — createBeanInstance():コンストラクタ(またはファクトリメソッド)を解決し、生のオブジェクトをインスタンス化します。この時点では、依存関係はまだ注入されていません。
フェーズ 2 — applyMergedBeanDefinitionPostProcessors():MergedBeanDefinitionPostProcessor の実装が Bean クラスをスキャンして注入メタデータを収集します。AutowiredAnnotationBeanPostProcessor が @Autowired のついたフィールドやメソッドを検出し、フェーズ 4 のために結果をキャッシュするのもここです。
フェーズ 3 — Early singleton exposure(早期シングルトン公開):循環参照が許可されている場合、まだ完全には初期化されていない Bean が、596 行目の singletonFactory ラムダを通じて公開されます。これが循環依存を解消する仕組みです。
フェーズ 4 — populateBean():依存関係の注入を実際に行うフェーズです。InstantiationAwareBeanPostProcessor.postProcessProperties() が呼び出され、AutowiredAnnotationBeanPostProcessor がフェーズ 2 で見つけた依存関係を注入します。
フェーズ 5 — initializeBean():最終的な初期化シーケンスです。次のセクションで詳しく見ていきましょう。
initializeBean の処理手順
initializeBean() は決まった順序で処理を実行します。
invokeAwareMethods()— 対応するAwareインターフェースを実装した Bean に対して、setBeanName()、setBeanClassLoader()、setBeanFactory()を呼び出します。applyBeanPostProcessorsBeforeInitialization()— 登録されているすべてのBeanPostProcessorが、初期化コールバックの前に Bean を検査または置き換える機会を得ます。invokeInitMethods()—InitializingBean.afterPropertiesSet()を呼び出した後、Bean 定義で宣言されたカスタムの init-method を実行します。applyBeanPostProcessorsAfterInitialization()— AOP プロキシが生成される重要なフックです。@Transactional、@Asyncなどプロキシベースの機能が、ここで元の Bean をラップします。
BeanFactory の Javadoc(69〜98 行目)には、このライフサイクル全体が順番に記載されています。フレームワーク内で最も重要な Javadoc の一つと言えるでしょう。
spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java#L69-L98
循環参照の解決:三段階キャッシュ
Spring の機能の中でも特に複雑で、あまりドキュメント化されていないのが、シングルトン Bean 間の循環依存を解決する仕組みです。Bean A が Bean B に依存し、Bean B が Bean A に依存している——何も手を打たなければ無限ループに陥ります。
Spring はこの問題を、DefaultSingletonBeanRegistry にある三つの Map で解決しています。
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Creation-time registry of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
stateDiagram-v2
[*] --> Creating: getBean("A") called
Creating --> InSingletonFactories: createBeanInstance() done,<br/>addSingletonFactory() called
InSingletonFactories --> InEarlySingletonObjects: Another bean requests "A",<br/>factory.getObject() called
InEarlySingletonObjects --> InSingletonObjects: initializeBean() completes,<br/>addSingleton() called
InSingletonObjects --> [*]: Fully ready
note right of InSingletonFactories
Level 3: ObjectFactory lambda
that may create AOP proxy
end note
note right of InEarlySingletonObjects
Level 2: Raw or proxied reference
before full initialization
end note
note right of InSingletonObjects
Level 1: Fully initialized singleton
end note
getSingleton() の解決アルゴリズムはキャッシュの各レベルを順番にチェックします。
singletonObjects(レベル 1)を確認する — Bean が完全に準備できている状態。earlySingletonObjects(レベル 2)を確認する — すでにファクトリから昇格済みの状態。singletonFactories(レベル 3)を確認する — ファクトリを呼び出し、レベル 2 に昇格させて早期参照を返す。
レベル 3 のファクトリは、doCreateBean() のフェーズ 3 で登録されるラムダ () -> getEarlyBeanReference(beanName, mbd, bean) です。AOP が絡む場合、getEarlyBeanReference() は生のインスタンスではなくプロキシを返すことがあります。生のインスタンスを直接キャッシュするのではなくファクトリを使う理由はここにあります。
注意: 循環依存が解決できるのは、セッター注入またはフィールド注入を使ったシングルトンスコープの Bean に限られます。コンストラクタ注入での循環依存は、コンストラクタが依存先を必要とする時点では生のオブジェクトがまだ存在しないため、常に
BeanCurrentlyInCreationExceptionがスローされます。
BeanPostProcessor:汎用拡張フック
spring-beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java#L67-L111
BeanPostProcessor は、フレームワーク全体で最も重要なインターフェースと言っても過言ではありません。postProcessBeforeInitialization() と postProcessAfterInitialization() というデフォルトメソッドが二つあるだけですが、これだけで Spring のほぼすべての機能が実現されています。
flowchart TD
BPP[BeanPostProcessor]
BPP --> AABPP["AutowiredAnnotationBeanPostProcessor<br/>@Autowired, @Value injection"]
BPP --> CABPP["CommonAnnotationBeanPostProcessor<br/>@PostConstruct, @Resource"]
BPP --> AAPC["AbstractAutoProxyCreator<br/>AOP proxies for @Transactional, @Async, @Cacheable"]
BPP --> ALP["ApplicationListenerDetector<br/>Registers @EventListener methods"]
BPP --> SCBPP["ScheduledAnnotationBeanPostProcessor<br/>@Scheduled methods"]
BPP --> Custom["Your custom BeanPostProcessor"]
サブインターフェースの階層も存在します。InstantiationAwareBeanPostProcessor はインスタンス化の前にフックを追加します(AOP で使用)。SmartInstantiationAwareBeanPostProcessor はさらに、型の予測的解決と循環参照処理のための早期プロキシ生成を追加します。
順序付けの仕組みも重要です。PriorityOrdered と Ordered インターフェースにより、実行順序が制御されます。AutowiredAnnotationBeanPostProcessor のようなインフラストラクチャ系のプロセッサは PriorityOrdered を実装することで、ユーザー定義のプロセッサより先に実行されます。
次のステップ
ここまでで、IoC コンテナの全体像を把握できました。読み取り・書き込み・列挙の関心事を分離するインターフェース階層、Bean を層ごとに構築するテンプレートメソッドチェーン、循環依存を解決する三段階キャッシュ、そして Spring を拡張可能にする汎用フック BeanPostProcessor——これらすべてを見てきました。次回の記事では、個々の Bean 生成から視点を引き上げ、アプリケーション起動シーケンス全体に焦点を当てます。Spring アプリケーション全体を起動させる 13 ステップのメソッド AbstractApplicationContext.refresh() を詳しく解説します。