ApplicationContext における非遅延初期化 Bean の処理

ApplicationContext の初期化プロセス

ApplicationContext の起動プロセスが第 11 段階まで到達すると、すべての準備が完了し、残りの Bean 初期化処理が実行されます。この段階では遅延初期化としてマークされていない Bean がいよいよインスタンス化されます。

public void refresh() throws BeansException, IllegalStateException {
    // 非遅延 Bean の初期化処理を実行
    finishBeanFactoryInitialization(beanFactory);
}

finishBeanFactoryInitialization メソッドの役割

このメソッドは、遅延初期化の対象外となっているすべての Bean をインスタンス化します。処理の流れは以下の通りです。

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // ConversionService の登録(Bean 名は正確に "conversionService" である必要あり)
    if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
            beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
        beanFactory.setConversionService(
                beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
    }

    // LoadTimeWeaverAware インタフェースを実装した Bean の先行初期化
    String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
    for (String weaverAwareName : weaverAwareNames) {
        getBean(weaverAwareName);
    }

    // 一時的な ClassLoader の使用を終了
    beanFactory.setTempClassLoader(null);

    // Bean 定義の凍結(以降は変更不可となる)
    beanFactory.freezeConfiguration();

    // 実際の Bean 初期化処理
    beanFactory.preInstantiateSingletons();
}

ConversionService の設定

カスタム Converter の実装例

文字列から日付への変換を行う Converter を実装する場合、以下のように記述します。

public class StringToDateConverter implements Converter<String, LocalDate> {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public LocalDate convert(String source) {
        return LocalDate.parse(source, FORMATTER);
    }
}

XML による Bean 登録

ConversionServiceFactoryBean を使用して Converter を登録します。Bean 名は「conversionService」である必要があることに注意してください。

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.example.config.StringToDateConverter"/>
        </set>
    </property>
</bean>

動作確認用のテストコード

@Test
public void conversionServiceTest() {
    DefaultConversionService service = new DefaultConversionService();
    service.addConverter(new StringToDateConverter());

    LocalDate result = service.convert("2024-01-15", LocalDate.class);
    System.out.println("変換結果: " + result);
}

Bean 定義の凍結処理

freezeConfiguration メソッドを呼び出すことで、Bean 定義が確定します。これにより、以降の処理で Bean 定義が変更されることを防止できます。

public void freezeConfiguration() {
    this.configurationFrozen = true;
    this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames);
}

凍結された Bean 定義は読み取り専用となり、BeanFactoryPostProcessor による改変の対象外となります。

非遅延 Bean の実際の初期化

preInstantiateSingletons メソッドの処理内容

このメソッドでは、シングルトンスコープかつ遅延初期化でない Bean が順次インスタンス化されます。

public void preInstantiateSingletons() throws BeansException {
    if (logger.isDebugEnabled()) {
        logger.debug("シングルトン Bean の先行インスタンス化を開始します: " + this);
    }

    // すべての Bean 名を取得
    List<String> definedNames = new ArrayList<>(this.beanDefinitionNames);

    // フェーズ 1: 通常の Bean と FactoryBean の初期化
    for (String beanName : definedNames) {
        RootBeanDefinition beanDef = getMergedLocalBeanDefinition(beanName);

        if (!beanDef.isAbstract() && beanDef.isSingleton() && !beanDef.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                boolean eagerInit;

                if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                    eagerInit = AccessController.doPrivileged(
                        (PrivilegedAction<Boolean>) () ->
                            ((SmartFactoryBean<?>) factory).isEagerInit(),
                        getAccessControlContext()
                    );
                } else {
                    eagerInit = (factory instanceof SmartFactoryBean &&
                            ((SmartFactoryBean<?>) factory).isEagerInit());
                }

                if (eagerInit) {
                    getBean(beanName);
                }
            } else {
                getBean(beanName);
            }
        }
    }

    // フェーズ 2: SmartInitializingSingleton インタフェースのコールバック処理
    for (String beanName : definedNames) {
        Object singletonInstance = getSingleton(beanName);

        if (singletonInstance instanceof SmartInitializingSingleton) {
            final SmartInitializingSingleton initializer = (SmartInitializingSingleton) singletonInstance;

            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged(
                    (PrivilegedAction<Object>) () -> {
                        initializer.afterSingletonsInstantiated();
                        return null;
                    },
                    getAccessControlContext()
                );
            } else {
                initializer.afterSingletonsInstantiated();
            }
        }
    }
}

初期化プロセスの詳細

処理は大きく 2 つのフェーズに分かれています。第一フェーズでは、通常の Bean と FactoryBean のインスタンス化が行われます。FactoryBean については、SmartFactoryBean インタフェースを実装している場合に限り、即時初期化の判断が行われます。

第二フェーズでは、SmartInitializingSingleton インタフェースを実装した Bean に対して、afterSingletonsInstantiated メソッドが呼び出されます。@EventListener アノテーションを使用して登録されたイベントリスナーも、この段階で初期化が完了します。

タグ: spring-framework ApplicationContext bean-initialization singleton conversion-service

8月23日 03:47 投稿