Help us understand the problem. What is going on with this article?

【Spring】Beanのライフサイクル〜生成から破棄まで〜

概要

この記事ではBeanのライフサイクルについてまとめています。
割と内部のお話です。
以下のキーワードは知っている前提で進めます。

  • Bean
  • DI(Dependency Injection)

ちなみに先日pivotalが主催するSpring Professional v5.0 Examを受けたのでそのときに勉強した内容のまとめです。

Beanのライフサイクル

Beanのライフサイクルは大きく3つに分かれます。
1. 初期化フェーズ
2. 利用フェーズ
3. 終了フェーズ

初期化フェーズ

このフェーズでは主に

  • Bean定義読み込み・書き換え
  • Bean生成&DI
  • 初期化処理

を行います。
このフェーズが最も複雑です。
大まかな流れはこんな感じ

bean-lifecycle.001.jpeg

では詳しく見ていきましょう。

Bean定義の読み込み

まずBeanの生成に必要な情報の読み込みが行われます。

  • @Configurationの書かれたJava Config
  • @Component, @Controller, @RestController, @Service, @Repositoryの付いたクラス
  • Bean定義が書かれたXMLファイル

ここで読み込まれた情報をもとにBean定義情報一覧表のようなものが作成されます。(実体はBeanDefinitionオブジェクトをMap化したもの?)
Bean定義情報一覧表にはBeanの実装クラス、スコープ、依存Bean、フィールドなどが書き込まれます。

Class Name Scope Depends on Property ...
com.example.hoge.AImpl a Singleton b url=${url} ...
com.example.fuga.BImpl b Prototype c age=10 ...
com.example.piyo.CImpl c Singleton ...

Bean定義の書き換え

ここではBeanの定義情報の書き換えが行われます。
前ステップで作成されたBean定義情報一覧表が修正されていくイメージです。
例えば@Valueで宣言したプレースホルダーにプロパティ値を埋め込む処理はここで行われます。

AImpl.java
@Component("a")
public class AImpl implements A {

    @Value("${url}")
    private String url;
}
application.properties
url=http://www.sample.com

実際には@Valueでインジェクションするプロパティ値をもとにBeanDefinitionが修正されます。
Bean定義情報の書き換えを実現するのがBeanFactoryPostProcessorです。
処理はBeanFactoryPostProcessorインターフェースを実装したクラスが行います。

public interface BeanFactoryPostProcessor {
   void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}

@Valueでプロパティ値を埋め込む処理はBeanFactoryPostProcessorを実装したPropertySourcesPlaceholderConfigurerクラスが担っています。(詳しい処理はリンク先参照)
ちなみに、@Valueを使用する場合はPropertySourcesPlaceholderConfigurerを返すBeanを定義しておく必要があります。(Spring BootとSpring4.3以降であれば明示的に定義しなくてもよいらしいです)
このBeanはstaticメソッドで定義しておく必要があり、理由はSpringがBean生成前に実行するからです。

@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}

ここまででBean定義情報の生成が完了しました。
実はまだBeanは生成されていません。
次のフェーズからBeanが生成されます。

Beanの生成 & DI

ここでやっとBeanのインスタンスが生成され、DI(Dependency Injection)が行われます。
DIは以下の順序で行われます。
1. コンストラクタインジェクション
2. フィールドインジェクション
3. セッターインジェクション

Bean生成後の初期化処理

Bean生成後の初期化処理が行われます。
ここで行える処理の特徴として、生成したBeanを使って初期化処理を行うことができます。
例えば@PostConstructを付加したメソッドの処理はこの段階で行われます。
@PostConstructを付加したメソッドではインジェクションされたフィールドを使用して初期化処理を書くことができます。

@Component
public class HogeServiceImpl implements HogeService {

    private final Fuga fuga;

    @Autowired
    public HogeServiceImpl(Fuga fuga) {
        this.fuga = fuga;
    }

    // DIが終わった後で呼ばれる
    // 戻り値はvoid、引数はなしにしなければならない
    @PostConstruct
    public void populateCache() {
        ...
    }
}

このステップの初期化処理には前後に前処理・後処理を挟むことができます。
この処理はBeanPostProcessorインターフェースを実装したクラスが行います。

public interface BeanPostProcessor {
    // 前処理
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    // 後処理
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

利用フェーズ

Beanを実際に利用するフェーズです。

ApplicationContext context = SpringApplication.run(AppConfig.class);
HogeService service = context.getBean("hogeService", HogeService.class); // Beanの取得
service.do(); // Beanの利用

終了フェーズ

DIコンテナが破棄されるフェーズです。

破棄前処理

DIコンテナ破棄前に処理が行われます。
@PreDestroyを付加したメソッドはこの段階で処理されます。

@Component
public class HogeServiceImpl implements HogeService {

    // DIコンテナが破棄される前に呼ばれる
    // 戻り値はvoid、引数はなしにしなければならない
    @PreDestroy
    public void clearCache() {
        ...
    }
}

DIコンテナの破棄

ConfigurableApplicationContextのcloseメソッドが呼ばれるとDIコンテナが破棄されます。

context.close();

SpringApplication.run()で生成した場合はJVMのシャットダウンにフックしてDIコンテナが破棄されます。

ConfigurableApplicationContext context = SpringApplication.run(AppConfig.class);
// JVMにshutdownHookが登録されている

参考資料

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away