DIとは
依存性の注入。
依存しているクラスの中で被依存クラスのインスタンスを作成するのではなく、
依存しているクラスのフィールド変数として被依存クラスを持たせるようにする。そのために、コンストラクタなどで外部から被依存クラスを渡すようにする。
習うより慣れよ。
DIのプロセス
DIに関わるアノテーションはたくさんあるが、基本的に以下の3段階のどこかに分類されると考えるとわかりやすい。
DIの3段階とは
- DIコンテナに登録するクラスの定義
- DIコンテナへの登録
- DIコンテナからの注入
である。
DIコンテナに登録するクラスの定義
- Javaによる定義 (=JavaConfig)
- アノテーションによる定義
- (xmlによる定義) <- 最近はあまりやらない
Javaによる定義
JavaConfigと呼ばれる。
- @Configurationアノテーションを付与したクラスを作成する
- @Beanアノテーションを付与したメソッドを作成する
@Configuration
public class AppConfiguration {
// ServiceImplクラスをDIコンテナに登録する
@Bean
public Service service() {
return new ServiceImpl();
}
// RepositoryImplクラスをDIコンテナに登録する
@Bean
public Repository repository() {
return new RepositoryImpl();
}
// 以下同様
.
.
.
}
インターフェースを定義し、実装クラスを@Configurationに入れるのが普通?
アノテーションによる定義
- 登録するクラスに@Componentアノテーションを追加する。
- @Componentアノテーションは様々なアノテーションの定義に含まれる。(->@Controller, @Service, @Repository, @Configurationなど)
//@Componentアノテーションは@Serviceアノテーションに含まれる
@Service
public class ServiceImpl implements Service {
}
DIコンテナへの登録
DIコンテナにインスタンスを実際に登録する。
コンポーネントスキャンを行うことで、DIへのインスタンスの登録を実現する。
JavaConfigによるコンポーネントスキャン
- @Configurationアノテーションを付与したクラスに@ComponentScanアノテーションを追加する
- 基本はカレントディレクトリ以下をスキャンするが、指定したいときはbasePackages属性を指定する
- スコープを指定したいときは、@Scopeアノテーションに属性を持たせる ->(singleton(デフォルト), prototype, request, session)
@Configuration
@ComponentScan(basePackages = "jp.co.yahoo")
// @ComponentScan <- カレントディレクトリ以下でいい場合
public class AppConfiguration {
}
DIコンテナからの注入
@Autowiredアノテーションを付与する。
以下の3つの方法がある。
- コンストラクタインジェクション
- フィールドインジェクション
- セッタインジェクション
コンストラクタインジェクション
コンストラクタに@Autowiredを付与する。
- 対象フィールドをfinal化できる (=実行中に依存するインスタンスの差し替えを禁止する)
- @Autowiredの記述を省略できる
というメリットがある。推奨される方法。
private final Service service;
public Controller(Service service) {
this.service = service;
}
フィールドインジェクション
@Autowired
private Service service;
セッタインジェクション
private Service service;
@Autowired
public void setService(Service service) {
this.service = service;
}
唯一神@SpringBootApplication
アプリケーションの起動の際に、@SpringBootApplicationアノテーションを付与することがある。
これさえあれば上記のアノテーションの多くを勝手にやってくれる。そのプロセスを以下で見ていく。
参考
Many Spring Boot developers always have their main class annotated with @Configuration, @EnableAutoConfiguration and @ComponentScan. Since these annotations are so frequently used together (especially if you follow the best practices above), Spring Boot provides a convenient @SpringBootApplication alternative.
The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration and @ComponentScan with their default attributes:
package com.example.myproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
準備1: AutoConfigurationについて
SpringBootにはあらかじめ定義されたJavaConfigクラスがある。
考えてみればこれは当たり前だ。外部ライブラリを追加するごとにDI対象クラスを定義し、DIコンテナに登録する作業を繰り返す必要があるようではフレームワークとは呼べない。
このクラスで定義されたインスタンスを自動的にDIコンテナに登録する機能をAutoConfigurationという。
AutoConfigurationによってDIコンテナに登録される条件
- Auto-configurationが有効
- 依存ライブラリがpom.xmlに指定されている
- 開発者独自のJavaConfigであらかじめ存在したJavaConfigの中で定義されたインスタンスを定義し直していない
Auto-configurationの有効化は@EnableAutoConfigurationアノテーションが付与されたクラスを作成することで行える。
一般的には、@Configurationアノテーションが付与されたクラスに@EnableAutoConfigurationアノテーションを付与する
準備2: SpringBootの起動
アプリケーション起動クラスの作成には2つポイントがある。
- @SpringBootApplicationアノテーションを付与する
- @SpringApplicationクラスのrunメソッドを実行する
@SpringBootAnnotationは以下のアノテーションを含む。
scanBasePackages属性を用いて、スキャンするパッケージの指定もできる。
@SpringBootApplication(scanBasePackages = "com.example")
結論
@SpringBootApplicationアノテーションを付与することで、デフォルトのJavaConfigの定義、DIコンテナへの登録を省略できる。
自分で登録する際(デフォルトのJavaConfigとの差分を追加する際)、アノテーションによる定義しか行わないのであれば、@ComponentアノテーションをつけるだけでDIコンテナへの登録ができ、@Autowiredアノテーションで注入ができる。
補足
- 差分があまりに巨大な場合は自分でJavaConfigを定義することになるのだろうが、現状ではアノテーションによる定義しかしていない。
- アノテーションによる定義の際、で@SpringBootApplicationアノテーションでコンポーネントスキャンを自動化することになるので、@Componentアノテーションを付与するクラスは実行クラスのカレントディレクトリ以下になければならない。
(まあ、普通に考えて実行クラスは一番上の階層に置くからあまり意識する必要はないと思う)