Spring FrameworkのDependency Injection
Spring FrameworkのDependency Injectionはモジュール間の関係を疎にするために非常に有効です。それによりソフトウェアの再利用性や保守性の向上につながります。
アノテーションだけで簡単に実現ができるのですが、より詳しく理解をしていないと今後の開発において問題が発生するため、Dependency Injectionについて調査をしました。
まず、基本となるWeb Applicationは以下のようになります。単純にアクセスする度にcountをインクリメントして返します。
http://localhost:8080/count
{
count: 3
}
Rest ControllerからはCountService I/Fを実現したBeanに@Autowiredを使用して接続し、利用しています。
CountRestController.java
@RestController
@RequestMapping("/count")
public class CountRestController {
@Autowired
private CountService service;
@RequestMapping(method = RequestMethod.GET)
public Count getCount() {
return service.count();
}
}
CountService.java
public interface CountService {
Count count();
}
CountService I/Fを実現したクラスには、@Serviceアノテーションを付加して、Spring FrameworkのDIコンテナに登録をしています。
CountServiceIncrement.java
@Service
public class CountServiceIncrement implements CountService {
private int counter;
public CountServiceIncrement() {
counter = 1;
}
@Override
public Count count() {
Count count = new Count(counter);
counter++;
return count;
}
}
複数のコンポーネントの登録
CountService I/Fを実現した別のサービスをDIコンテナに登録します。
@Service
public class CountServiceDouble implements CountService {
private int counter;
public CountServiceDouble() {
counter = 1;
}
@Override
public Count count() {
Count count = new Count(counter);
counter = counter * 2;
return count;
}
}
この場合、DIコンテナは、CountRestControllerの@Autowiredで指定された変数にどちらを紐付ければ良いか分からないため以下のようなエラーを発生します。
failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private jp.gr.java.conf.ulexite.spring.multicmp.service.CountService jp.gr.java.conf.ulexite.spring.multicmp.controller.CountRestController.service; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [jp.gr.java.conf.ulexite.spring.multicmp.service.CountService] is defined: expected single matching bean but found 2: countServiceDouble,countServiceIncrement
これを解決するためには@Qualifierアノテーションを用います。
まず二つのサービスにそれぞれ名前をつけます
@Service("increment")
public class CountServiceIncrement implements CountService {
...
}
@Service("double")
public class CountServiceDouble implements CountService {
...
}
次に使用するCountRestController側で@Qualifierでサービスの名前指定を行います。
public class CountRestController {
@Autowired
@Qualifier("double")
private CountService service;
これでCountServiceDoubleを使用することができます。
以下にソースコードがあります
https://github.com/yokobonbon/spring-di-sample/tree/master/multicmp
Scope
@Scopeアノテーションで、コンポーネントの生成タイミングを制御することが可能です。@Scopeを付ける位置は、@Service, @ControllerなどDIコンポーネントの定義の下に記述します。
Scopeには以下の4種類が指定可能です。Scope指定しない場合は、デフォルトで"singleton"になります。
Scope | Description |
---|---|
singleton | インスタンスをSingletonとする(デフォルト) |
prototype | 利用する都度、インスタンス化する |
request | Servlet APIのリクエストスコープの間だけインスタンスが生存する |
session | Servlet APIのSessionスコープの間だけインスタンスが生存する |