はじめに
DIとはDependency Injectionの略であり、日本語では「依存性の注入」と訳されることが多いです。
新卒だったころにSpringに触れた私は、初めてこの概念を聞いた時に頭に浮かんだのは、はてなマークでした。DIの概念やDIコンテナ、Springにおけるアノテーションとの結びつきがピンとこなかったのです。
今回は初心者だったころの自分にあてて、DIの概念およびSpringにおけるDIについて自分なりに整理しつつ、簡単にまとめていきたいと思います。
依存性(Dependency)とは
依存(性)とは何かということはソースコードで示したほうがわかりやすいと思います。
public class Main {
public int hoge() {
var sub = new Sub();
return sub.calculate();
}
}
Mainクラスのhogeメソッド内で、Subクラスのインスタンスを生成し、利用しています。
このようなMainクラスがあったとき、「MainクラスはSubクラスに依存している」と表現します。
依存に関しては比較的わかりやすいと思います。
注入(Injection)とは
先程の例では、Subクラスのインスタンスを内部で生成していましたが、インスタンスを外部で生成し、それを用いるクラスに渡してあげることを注入と呼びます。
ここでは、コンストラクタを用いた例(コンストラクタインジェクション)を示します。
public class Main {
private Sub sub;
public int hoge() {
return sub.calculate();
}
// コンストラクタインジェクション
public Main(Sub sub) {
this.sub = sub;
}
}
先程はhogeメソッド内でSubクラスのインスタンスを生成していましたが、今回の例では、インスタンスをコンストラクタから受け取るようになっています。
このようにすることで、Mainクラスを実際に用いる際は、
var sub = new Sub();
var main = new Main(sub);
といった形で、SubクラスのインスタンスをMainクラスの外から渡す形になります。
これによるメリットは後述します。
DIコンテナとは
コンテナと聞くと現代ではDockerを連想するかもしれませんが、DIコンテナにおけるコンテナはDockerなどの文脈で用いられるコンテナと異なります。
誤解を恐れず一言で書くとすると、DIコンテナは「注入するインスタンスを管理してくれる入れ物」の役割をしてくれます。
先程のようにDIコンテナを用いずに、Mainクラスを用いようとすると、用いるたびにSubクラスのインスタンスが必要になります。
DIコンテナを用いると、このインスタンスを管理してくれるので、いちいちインスタンスを生成する必要がなくなります。
SpringにおけるDIコンテナ
Springではアノテーションでクラスを指定することで、そのクラスのインスタンスをDIコンテナで管理する対象として指定することができます。
具体的には以下のようなアノテーションです。
@Controller
@Service
@Repository
@Bean
ちなみに、DIコンテナで管理されているインスタンスはApplicationContextクラスのgetBeanメソッドを用いることで、取得することができます。
また、SpringではDIコンテナがインスタンスをいつまで管理するかという、スコープを設定することができます。
設定できるスコープは下記のとおりで、設定する際はScopeアノテーションを用います。
- singleton
- prototype
- request
- session
- global session
Springのデフォルトはsingletonになっていますが、設定を変更することにより、例えばsession単位で、コンテナに管理されているインスタンスを破棄し、再設定するといったことが可能になります。
初心者がやりがちな注意点としては、singletonにもかかわらず、クラスに変化しうるフィールドを持たせてしまうことです。以下に例を示します。
@Service
public class hogeService {
private String result;
public int fuga(String suffix) {
result = "test" + suffix;
return result;
}
}
hogeServiceにはServiceアノテーションが付与されており、特にスコープを設定していないので、スコープがsingletonになります。
そのため、resultフィールドを別のセッションで変更してしまう危険性があり、スレッドセーフでなくなってしまいます。
下記のようにクラスフィールドではなく、変数にしてあげることで、スレッドセーフな実装になります。
@Service
public class hogeService {
public int fuga(String suffix) {
String result = "test" + suffix;
return result;
}
}
SpringにおけるDI
Springでは、Autowiredアノテーションを用いることで、DIすることができます。
インジェクションの方法には先程のコンストラクタインジェクションも含め、下記の3つがあります。
- フィールドインジェクション
- セッターインジェクション
- コンストラクタインジェクション
それぞれ具体的には、このようになります。
public class Main {
// フィールドインジェクション
@Autowired
private Sub sub;
public int hoge() {
return sub.calculate();
}
}
public class Main {
private Sub sub;
public int hoge() {
return sub.calculate();
}
// セッターインジェクション
@Autowired
public setSub(Sub sub) {
this.sub = sub;
}
}
public class Main {
private Sub sub;
public int hoge() {
return sub.calculate();
}
// コンストラクタインジェクション
@Autowired
public Main(Sub sub) {
this.sub = sub;
}
}
Autowiredアノテーションを付与することで、フィールド、メソッドおよびコンストラクタの引数にDIコンテナからインスタンスを注入してくれます。
ちなみに自分がインジェクションする際は、こちらの記事にあるように、ライブラリのLombokのRequiredArgsConstructorアノテーションを用いて書いています。
DIのメリット
よく言われるメリットとして、単体テストが書きやすくなることが挙げられますが、
これはそのとおりだと私も思います。
冒頭の例をもう一度示します。
public class Main {
public int hoge() {
var sub = new Sub();
return sub.calculate();
}
}
仮にこのSubクラスのcalculateメソッドが、DBにアクセスする必要があったとすると、Mainクラスの単体テストは非常に難しくなります。
しかし、DIによって注入するインスタンスをモックにすることで、テストすることができます。
ちなみに私はモックを用いる際はMockitoを用いています。
まとめ
最後に、改めてDIおよびDIコンテナについて一言でまとめると下記のようになります。
DI:あるクラスに対して依存しているクラスのインスタンスを外部から渡してあげること
DIコンテナ:注入するインスタンスを管理してくれる入れ物
これらを用いることで、テストが書きやすくなったり、スコープの管理がしやすくなったりというメリットがあります。
内容は以上です。本記事が少しでもお役に立てば幸いです。
最後までお読み頂き、ありがとうございました。
参考文献
公式ドキュメント
Core Technologies