依存性の注入(DI)
依存性の注入(いそんせいのちゅうにゅう、英: Dependency injection)とは、コンポーネント間の依存関係をプログラムのソースコードから排除するために、外部の設定ファイルなどでオブジェクトを注入できるようにするソフトウェアパターンである。英語の頭文字からDIと略される。
コンポーネント間の依存関係をプログラムのソースコードから排除し、外部の設定ファイルなどで実行時に注入できるようにするソフトウェアパターンです。
ここで言うコンポーネントとは、クラスと置き換えてもさしつかえありません。
依存性
クラスAでクラスBをインスタンス化すると、クラスAはクラスBなしでは動作しません。
この状態は、クラスAとクラスBが依存関係にある、と言えます。
コードだと以下のような状態。
public class ClassA {
public static void main(String... args) {
ClassB classB = new ClassB();
System.out.println(classB.execute());
}
}
public class ClassB {
public execute() {
System.out.println("ClassB::execute()");
}
}
注入
直接new
するのではなく、設定などの外部ファイルに記載しておいて、実行時に指定クラスのインスタンスを割り当てることを言います。
こういったことをやってくれる機能をDIコンテナと言います。
SpringにはこのDIコンテナがあるので、設定をしておくと実行時にインスタンスを注入してくれます。
Spring BootのDI
SpringのDIコンテナにbeanとして登録するために、Springアノテーションを使います。
以下のアノテーションは動作としてはいずれも同じなので、名前通りの使い分けで良いようです。
アノテーション | 使い所 |
---|---|
@Service |
サービス層に配置 |
@Repository |
データ層に配置 |
@Controller |
コントローラ層に配置 |
@Component |
その他 |
DIコンテナに登録したクラスでは、注入が使えるようになります。
注入はアノテーションで表現でき、以下の種類があります。
アノテーション | パッケージ |
---|---|
@Autowired |
org.springframework.bean.factory |
@Inject |
javax.inject |
@Resource |
javax.annotation |
動作は変わらないので、ここでは@Autowired
を使っていきます。
@Autowired
で指定したインスタンスが注入されます。
@Autowired
の使い方
準備
注入する方を作成します。
入れ替えを行いたいので、interface
を使ったものにします。
left to right direction
interface ICommand {
void execute()
}
class FirstCommand
FirstCommand --|> ICommand
SecondCommand --|> ICommand
コンストラクタインジェクション
コンストラクタに@Autowired
をつけます。
この方法はSpringチームが推奨している方法ですが、フィールドがfinalにできることや、コンストラクタに別のインスタンスが指定できるところがメリットです。
@Slf4j
@Service
public class ConstructorAutowiredService {
private final IComponent component;
@Autowired
public ConstructorAutowiredService(IComponent secondComponent) {
this.component = secondComponent;
}
@PostConstruct
public void execute() {
log.info("execute()");
this.component.execute();
}
}
コンストラクタの引数名(ここではsecondComponent
)をクラス名のlower camelにすることで、自動的にSecondComponent
を注入します。
ここをfirstComponent
とすることで、FirstComponent
が注入されます。
ConstructorAutowiredService.java
では、わかりやすくうるために、コンストラクタを作ってそこに@Autowired
をつけましたが、もっと簡潔に書くために、final
のフィールドをコンストラクタ引数にとるアノテーション@RequiredArgsConstructor
を使います。
@Slf4j
@RequiredArgsConstructor
@Service
public class RequiredArgsConstructorService {
// @RequiredArgsConstructorを使った場合は@Autowiredは省略できる
private final IComponent firstComponent;
// もちろんConstructorも省略できる
@PostConstruct
public void execute() {
log.info("execute()");
this.firstComponent.execute();
}
}
この書き方だと、@Autowired
やコンストラクタが省略できます。
フィールドインジェクション
フィールドに@Autowired
をつけます。
わかりやすい方法です。
@Slf4j
@Service
public class FirstComponentService {
@Autowired
private IComponent firstComponent;
@PostConstruct
public void execute() {
log.info("execute()");
this.firstComponent.execute();
}
}
@Slf4j
@Service
public class SecondComponentService {
@Autowired
private IComponent secondComponent;
@PostConstruct
public void execute() {
log.info("execute()");
this.secondComponent.execute();
}
}
注入するクラスの判別は名前になるところは、コンストラクタインジェクションと同様です。
セッターインジェクション
名前の通り、セッターに@Autowired
をつけるものです。
使い所が思いつかなかったので、サンプルはなしです。
Configurationを使う
ここまで基本的な方法を見てきましたが、依存性の注入と言いながら、フィールド名がクラス名に紐付いていて使いにくかったです。
そこで名前を指定して注入するインスタンスを分ける方法を示します。
Configurationは、Beanを定義します。 *
つまり、DIコンテナに対して、どのインスタンスをどの初期値で注入するのかを設定します。
Configurationは、
- 設定ファイル(xml)
- クラス
- アノテーション
で指定可能ですが、ここではアノテーションで作ります。
@Configuration
クラスを作る
Configurationクラスには@Configuration
アノテーションをつけます。
メソッドには@Bean
アノテーションをつけます。名前で判別可能にするために、引数を取ることができます。
ここではシンプルにnew
しかしてませんが、書き方からわかるように、ここでインスタンスに値の設定をすることが可能です。
@Configuration
public class App {
@Bean("component1")
public IComponent getComponent1() {
return new FirstComponent();
}
@Bean("component2")
public IComponent getComponent2() {
return new SecondComponent();
}
}
Configurationを使う
Configurationで設定したBeanを使って注入してみます。
ここではコンストラクタインジェクションを例示します。
@Slf4j
@Service
public class UsinAppConfigService {
private final IComponent component;
@Autowired
public UsinAppConfigService(@Qualifier("component1") IComponent component) {
this.component = component;
}
@PostConstruct
public void execute() {
log.info("service executed");
this.component.execute();
}
}
名前を指定してBeanを取得するためには、@Qualifier
アノテーションを使います。
@Qualifier
は@Autowired
と組み合わせて使います。
コンストラクタの引数のとこにある@Qualifier
で名前を指定しています。
実行すると、FirstComponentが実行されます。
まとめ
ユニットテスト用にスタブにしたりできるので、開発には重宝しそうです。
たとえば、RestTemplateはURLに問い合わせてJSONを持ち帰りますが、これをインジェクションして、固定のJSONを戻すことも可能ではないかと考えています。
ビジネス層では、ストラテジーパターンが必要な開発でも有用な機能です。
用意されているDIコンテナを活用して、開発スピードをもっと上げていきたいですね。
参考資料
DI(依存性注入)について
@Component、@Service、@Repository、@Controllerの違いについて
@Autowired、@Inject、@Resourceの違いについての検証