概要
この記事ではBeanのライフサイクルについてまとめています。
割と内部のお話です。
以下のキーワードは知っている前提で進めます。
- Bean
- DI(Dependency Injection)
ちなみに先日pivotalが主催するSpring Professional v5.0 Examを受けたのでそのときに勉強した内容のまとめです。
Beanのライフサイクル
Beanのライフサイクルは大きく3つに分かれます。
- 初期化フェーズ
- 利用フェーズ
- 終了フェーズ
初期化フェーズ
このフェーズでは主に
- Bean定義読み込み・書き換え
- Bean生成&DI
- 初期化処理
を行います。
このフェーズが最も複雑です。
大まかな流れはこんな感じ
では詳しく見ていきましょう。
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
で宣言したプレースホルダーにプロパティ値を埋め込む処理はここで行われます。
@Component("a")
public class AImpl implements A {
@Value("${url}")
private String url;
}
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は以下の順序で行われます。
- コンストラクタインジェクション
- フィールドインジェクション
- セッターインジェクション
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が登録されている