Bean Lifcycle の確認

今回の検証内容のリポジトリ

Lifecycle の説明は こちら の記事が Spring の実装にも言及しており、わかりやすかった。

今回は、ライフサイクル内にカスタムの実装を行い挙動を確かめてみる。

まずは、コンフィギュレーションクラス(Bean定義を行うクラス)を用意。

@ComponentScan
@Configuration
public class AppConfig {

    @Bean
    public static CustomBeanFactoryPostProcessor getCustomBeanFactoryPostProcessor(){
        return new CustomBeanFactoryPostProcessor();
    }

    @Bean
    public static CustomBeanPostProcessor getCustomBeanPostProcessor(){
        return new CustomBeanPostProcessor();
    }
}

メインクラスは Bean(DIコンテナに登録されたコンポーネント) を取得(ルックアップ)するだけの処理。

public static void main(String[] args) {
    try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            var bean1 = context.getBean(Bean1.class);
    }
}

最初にBean定義が読み込まれた後に、BeanFactoryPostProcessor による定義情報の書き換え処理が行われる。 ここでは、指定した文字列を含む Bean を表示している。

public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println(getClass().getSimpleName() + "::postProcessBeanFactory Listing Beans Start\n");

        Arrays.stream(beanFactory.getBeanDefinitionNames())
                .map(beanFactory::getBeanDefinition)
                .filter(beanDefinition -> beanClassNameContains(beanDefinition, "com.kiyotakeshi.beanLifecycle.beans"))
                .map(BeanDefinition::getBeanClassName)
                .forEach(System.out::println);

        System.out.println("\n" +getClass().getSimpleName() + "::postProcessBeanFactory Listing Beans End\n");
    }

    private boolean beanClassNameContains(BeanDefinition beanDefinition, String regex) {
        return beanDefinition != null && beanDefinition.getBeanClassName().contains(regex);
    }
}

次いで、Beanのインスタンスが生成され、インジェクション(コンストラクター -> フィールド -> セッター の順)が行われる。

以下のケースだと、 Bean1 のコンストラクタが呼ばれた後、 依存している Bean2 のインスタンスを作成するため Bean2 のコンストラクタが呼ばれる。

@Component
public class Bean1 {

    private Bean2 bean2;
    private Bean3 bean3;

    public Bean1() {
        System.out.println(getClass().getSimpleName() + "::constructor");
    }

    @Autowired
    public void setBean2(Bean2 bean2) {
        System.out.println(getClass().getSimpleName() + "::setSpringBean2");
        this.bean2 = bean2;
    }

    @Autowired
    public void setBean3(Bean3 bean3) {
        System.out.println(getClass().getSimpleName() + "::setSpringBean3");
        this.bean3 = bean3;
    }

    @PostConstruct
    public void init() {
        System.out.println(getClass().getSimpleName() + "::init");
    }

    @PreDestroy
    public void destroy() {
        System.out.println(getClass().getSimpleName() + "::destroy");
    }
}

Bean2 は依存しているものがないため、

@Component
public class Bean2 {

    public Bean2() {
        System.out.println(getClass().getSimpleName() + "::constructor");
    }

    @PostConstruct
    public void init() {
        System.out.println(getClass().getSimpleName() + "::init");
    }

    @PreDestroy
    public void destroy() {
        System.out.println(getClass().getSimpleName() + "::destroy");
    }
}

インジェクションが終わった後の BeanPostProcessor の前処理と @PostConstruct と BeanPostProcessor の後処理が行われる。

public class CustomBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(String.format("%s::postProcessBeforeInitialization %s %s", getClass().getSimpleName(), bean.getClass().getSimpleName(), beanName));
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(String.format("%s::postProcessAfterInitialization %s %s", getClass().getSimpleName(), bean.getClass().getSimpleName(), beanName));
        return bean;
    }
}

そして、 Bean1 において、 Bean2 のセッターインジェクションが行われる。 さらに Bean1 は Bean3 にも依存しているため同様の処理を行う。

すべての Beanがインスタンス化され、 メインクラスにて、 Bean1 をルックアップし、処理は終了となります。

try-with-resources を使っているため、最後に @PreDestroy が呼ばれます。

public static void main(String[] args) {
    try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            var bean1 = context.getBean(Bean1.class);
    }
}

これは、 AnnotationConfigApplicationContext の親クラスである、 ConfigurableApplicationContext が Closeable(AutoCloseable) を継承しているためです。

// org/springframework/context/ConfigurableApplicationContext.java
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

try-with-resources を使用しない場合は、 application context を .close() することで消去しないと、 @PreDestroy が呼ばれないです。

また、 .registerShutdownHook() も使用できます。 ※サンプルコード

ちなみに Spring Boot を使用する場合は、自動で行ってくれているので明示的に消去しなくてもよい。 ※サンプルコード

public static void main(String[] args) {
    var context = new AnnotationConfigApplicationContext(AppConfig.class);
    var bean1 = context.getBean(Bean1.class);
    context.close();
}