IoCコンテナとDIコンテナは、それぞれIoC(制御の逆転)を行うコンテナ、DI(依存性の注入)を行うコンテナを指します。
DI ⊂ IoC(DIはIoCの実現手法の一つ)
DIコンテナはIoCコンテナの一種です。
開発者が制御権を手放す(制御の逆転を行う)ための手法は様々ありますが、その代表的な例がDIです。
IoCには非常に多くのパターンが存在し、例えばJUnitなどのテストフレームワークにおいてもIoCが行われています。
JUnitを使用する際も、コードの実行主導権は開発者ではなくフレームワーク側にあります。
そのため、「IoCコンテナ」という言葉は抽象的で範囲が広すぎるため、あまり使われません。
現在では、より具体的に「DIコンテナ」と呼ばれることが一般的です。
最もシンプルなDIコンテナの例を作成すると、以下のようになります。
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
ただし、厳密にはこれをそのまま「DIコンテナ」と呼ぶのは少し大げさかもしれません。
正確には、以下のように表現するのが適切な表現です。
「手動DI設定クラス(コンテナの役割を模倣したもの)」
本来のコンテナが担うべき役割には、以下の機能が含まれます。
- オブジェクトの保存(管理)
- オブジェクトの再利用(シングルトン管理)
- ライフサイクルの管理
これらを行って初めて「コンテナ」としての機能を果たしていると言えます。
上記のコードは、単にオブジェクトを生成して依存関係を接続しているだけの役割にとどまっています。
DIコンテナの代表的な実装として、Spring Frameworkが挙げられます。
ここで、先ほど作成した非常にシンプルな AppConfig をSpringを用いた実装へと切り替えてみます。
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
Springコンテナ (ApplicationContext)
従来は開発者が AppConfig を直接使用してオブジェクトを生成し、DIを行っていましたが、今後はSpringコンテナ経由でオブジェクトを利用します。
Springコンテナは @Configuration が付与された AppConfig を構成情報(設定情報)として使用します。
ここで @Bean が指定されたメソッドをすべて呼び出し、返却されたオブジェクトをSpringコンテナに登録します。このようにSpringコンテナに登録されたオブジェクトを「Spring Bean」と呼びます。
クライアントコードからSpring Beanを取得する方法は以下の通りです。
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
Springコンテナに登録されたすべてのBeanの取得・照会
package hello.core.beanfind;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
void findAllBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name=" + beanDefinitionName + " object=" + bean);
}
}
@Test
void findApplicationBean() { // ApplicationBeanの意味: 自身で登録したBeanのみ(Spring内部のBeanを除外)
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
// Role ROLE_APPLICATION: ユーザーが直接登録したアプリケーションBean
// Role ROLE_INFRASTRUCTURE: Springが内部で使用するBean
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name=" + beanDefinitionName + " object=" + bean);
}
}
}
}
補足ノート
ac.getBeansOfType(Object.class)と上記コードの決定的な違いは、「Beanを新しく生成するか・しないか」にあります。
getBeansOfType(Object.class)既に存在するBeanのみを取得する
新しくインスタンスを生成しない
**
getBeanDefinitionNames()+getBean()**
ac.getBean(name)呼び出し時、該当のBeanが存在しなければその場で新規生成する(重要)このような差が生じる理由は、Lazy(遅延初期化)設定されているBeanの扱いにあります。
後者の方式では「名前を取得したため、対象のBeanをすべてインスタンス化する」という動作になり、Lazy設定が無効化されて全Beanが生成されてしまいます。
要約
getBeansOfType: 照会(取得)のみを行うgetBeanDefinitionNames+getBean: 照会を行いながら、必要に応じて生成まで実行する