Read OSS

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

なぜこれほど多くのインターフェースが必要なのでしょうか。理由は三つあります。

  1. 読み取りと書き込みの分離。 BeanFactoryListableBeanFactory は読み取り専用です。書き込み操作は ConfigurableBeanFactory が担います。アプリケーションコードには読み取り用インターフェースを、インフラストラクチャコードには設定可能なインターフェースを渡すという設計です。

  2. 列挙と階層の分離。 ListableBeanFactory はすべての Bean を列挙できます。HierarchicalBeanFactory は親子ファクトリのチェーンをサポートします(Web アプリケーションで各サーブレットが独自の子コンテキストを持つ構成に使われます)。これらは直交する関心事です。

  3. SPI とクライアント API の分離。 BeanDefinitionRegistry は Bean の定義を登録するための純粋な SPI です。DefaultListableBeanFactory に実装されていますが、意図的に BeanFactory のクライアント階層から切り離されています。

Template Method チェーン:Registry から Factory まで

実装の継承階層はインターフェースの階層に対応しており、それぞれが明確な責務を担う四つのクラスで構成されています。

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L76

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java#L118

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L121

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java#L132-L133

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:シングルトンキャッシュ(循環参照のための三段階キャッシュ)を管理します。
  • AbstractBeanFactorygetBean() と template method の createBean() を実装します。マージ済み Bean 定義の処理、FactoryBean の逆参照、スコープの解決もここで行います。
  • AbstractAutowireCapableBeanFactorycreateBean() を実装します。コンストラクタの解決、プロパティの注入、初期化処理を含む、実際の Bean 生成パイプラインがここにあります。
  • DefaultListableBeanFactory:Bean 定義の格納、列挙(getBeansOfType())、シングルトンの事前インスタンス化を追加します。

Bean 生成パイプライン:createBean() から使用可能な状態まで

getBean("myService") を呼び出すと、コンテナは最終的に AbstractAutowireCapableBeanFactory.createBean() に到達します。このメソッドは五つのフェーズからなるパイプラインを制御します。

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L488-L540

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() を詳しく見ていきましょう。

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L556-L607

フェーズ 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 の処理手順

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java#L1799-L1824

initializeBean() は決まった順序で処理を実行します。

  1. invokeAwareMethods() — 対応する Aware インターフェースを実装した Bean に対して、setBeanName()setBeanClassLoader()setBeanFactory() を呼び出します。
  2. applyBeanPostProcessorsBeforeInitialization() — 登録されているすべての BeanPostProcessor が、初期化コールバックの前に Bean を検査または置き換える機会を得ます。
  3. invokeInitMethods()InitializingBean.afterPropertiesSet() を呼び出した後、Bean 定義で宣言されたカスタムの init-method を実行します。
  4. 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 で解決しています。

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L86-L95

/** 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() の解決アルゴリズムはキャッシュの各レベルを順番にチェックします。

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java#L208-L237

  1. singletonObjects(レベル 1)を確認する — Bean が完全に準備できている状態。
  2. earlySingletonObjects(レベル 2)を確認する — すでにファクトリから昇格済みの状態。
  3. 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 はさらに、型の予測的解決と循環参照処理のための早期プロキシ生成を追加します。

順序付けの仕組みも重要です。PriorityOrderedOrdered インターフェースにより、実行順序が制御されます。AutowiredAnnotationBeanPostProcessor のようなインフラストラクチャ系のプロセッサは PriorityOrdered を実装することで、ユーザー定義のプロセッサより先に実行されます。

次のステップ

ここまでで、IoC コンテナの全体像を把握できました。読み取り・書き込み・列挙の関心事を分離するインターフェース階層、Bean を層ごとに構築するテンプレートメソッドチェーン、循環依存を解決する三段階キャッシュ、そして Spring を拡張可能にする汎用フック BeanPostProcessor——これらすべてを見てきました。次回の記事では、個々の Bean 生成から視点を引き上げ、アプリケーション起動シーケンス全体に焦点を当てます。Spring アプリケーション全体を起動させる 13 ステップのメソッド AbstractApplicationContext.refresh() を詳しく解説します。