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

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

- Bean
- DI(Dependency Injection)

ちなみに先日pivotalが主催する[Spring Professional v5.0 Exam](https://pivotal.io/training/certification)を受けたのでそのときに勉強した内容のまとめです。

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

## 初期化フェーズ
このフェーズでは主に

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

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

![bean-lifecycle.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/89007/b4e2c9de-2281-e8fd-768a-8173948eaa30.jpeg)

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

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

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

ここで読み込まれた情報をもとにBean定義情報一覧表のようなものが作成されます。（実体は[BeanDefinition](https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java)オブジェクトを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](https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanFactoryPostProcessor.java)です。
処理はBeanFactoryPostProcessorインターフェースを実装したクラスが行います。

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

`@Value`でプロパティ値を埋め込む処理はBeanFactoryPostProcessorを実装した[PropertySourcesPlaceholderConfigurer](https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java)クラスが担っています。（詳しい処理はリンク先参照）
ちなみに、`@Value`を使用する場合はPropertySourcesPlaceholderConfigurerを返すBeanを定義しておく必要があります。（Spring BootとSpring4.3以降であれば明示的に定義しなくてもよいらしいです）
このBeanはstaticメソッドで定義しておく必要があり、理由はSpringがBean生成前に実行するからです。

```java
@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`を付加したメソッドではインジェクションされたフィールドを使用して初期化処理を書くことができます。

```java
@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](https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanPostProcessor.java)インターフェースを実装したクラスが行います。

```java
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を実際に利用するフェーズです。

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

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

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

```java
@Component
public class HogeServiceImpl implements HogeService {

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

### DIコンテナの破棄
[ConfigurableApplicationContext](https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java)のcloseメソッドが呼ばれるとDIコンテナが破棄されます。

```java
context.close();
```

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

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

# 参考資料

- [Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発 | 株式会社NTTデータ |本 | 通販 | Amazon](https://www.amazon.co.jp/Spring%E5%BE%B9%E5%BA%95%E5%85%A5%E9%96%80-Spring-Framework%E3%81%AB%E3%82%88%E3%82%8BJava%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA-%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BENTT%E3%83%87%E3%83%BC%E3%82%BF/dp/4798142476)
- https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html