はじめに
Tomcat + Jersey という構成の Web アプリケーションの動作を理解するために、Tomcat Embed を使って実験をしていきます。
いまどきこんな構成で開発を始めることは少ないかと思いますが、レガシーソフトウェアと戦う人たちの助けになれば幸いです。
関連記事の一覧(予定)
- 埋め込みTomcatでJerseyを動かしてみる
- HK2でDIしてみる ← イマココ
- BeanValidationで入力値検証してみる
- TomcatとJerseyでトランザクション管理してみる
リポジトリ
オブジェクトをDIする
Jersey 2.x では HK2 の DI フレームワークが使用されており、ユーザアプリケーションでも同じ仕組みで任意のオブジェクトを注入できます。
以下のリソースに定義された GreetingService
と IdentityService
を注入してみましょう。
...
@Path("greeting")
public class Greeting {
@Inject
private IdentityService identityService;
@Inject
private GreetingService greetingService;
@GET
public String sayHello() {
return String.format("%s from %s", greetingService.getMessage(), identityService.getName());
}
}
IdentityService
は挨拶をしてくれた人の名前を返すサービスです。誰が挨拶をするかはオブジェクトが生成されるたびにランダムに決めることにします。
...
public class IdentityService {
private static final List<String> names = List.of("Carol", "Charlie", "Dave", "Ellen", "Frank", "Eve", "Isaac",
"Ivan", "Justin", "Mallory", "Marvin", "Mallet", "Matilda", "Oscar", "Pat", "Peggy", "Victor", "Plod",
"Steve", "Trent", "Trudy", "Walter", "Zoe");
private final String name;
// ↓ 注入されるクラスには public コンストラクタが必要
public IdentityService() {
name = names.get(new Random().nextInt(names.size()));
}
public String getName() {
return name;
}
}
GreetingService
は挨拶を返すインターフェースです。ここでは具象クラスを HelloService
に定義しました。
...
public interface GreetingService {
public String getMessage();
}
...
public class HelloService implements GreetingService {
public String getMessage() {
return "Hello";
}
}
@Inject
アノテーションをもつプロパティに、実際に注入するクラスを関連付けます。
AbstractBinder
の具象クラスで依存関係を設定し、ResourceConfig
に登録します。注入されるプロパティのクラスそのもののインスタンスを注入する場合は bindAsContract
、スーパークラスに対してサブクラスのインスタンスを注入する場合は bind(サブクラス).to(スーパークラス)
という形で指定します1。
...
@ApplicationPath("app")
public class AppConfig extends ResourceConfig {
public AppConfig() {
packages(getClass().getPackage().getName());
...
register(new AbstractBinder() {
@Override
protected void configure() {
bindAsContract(IdentityService.class);
bind(HelloService.class).to(GreetingService.class);
}
});
}
}
アプリケーションを起動して、アクセスしてみましょう。
curl localhost:8080/app/greeting
# Hello from Steve
名前の部分はアクセスするたびに変化するはずです。これは DI が RequestScoped
である、つまり、リクエストのたびに注入されたオブジェクトを生成しているためです。
DI のライフサイクルを指定する
Web アプリケーションの複数のリソースから、ひとつのインスタンスを共通で参照したい場合は、依存関係に Singleton
を設定します2。
...
register(new AbstractBinder() {
@Override
protected void configure() {
bindAsContract(IdentityService.class).in(Singleton.class);
bind(HelloService.class).to(GreetingService.class);
...
アプリケーションを再起動して何度かアクセスしてみると、常に同じ人から挨拶してもらえるようになります。つまり、 IdentityService
のコンストラクタは一度だけ実行され、以降も同じインスタンスが使い回されていることが分かります。
curl localhost:8080/app/greeting
# Hello from Charlie
curl localhost:8080/app/greeting
# Hello from Charlie
依存関係を変更する
ここまで、GreetingService
には具象クラス HelloService
を注入していましたが、同じインターフェースを実装する別のクラスに依存関係を変更することも可能です。
たとえば、HelloService
ではなく AlohaService
を注入したい場合には ResourceConfig
内の設定を変更するだけで済みます。
...
register(new AbstractBinder() {
@Override
protected void configure() {
bindAsContract(IdentityService.class);
bind(AlohaService.class).to(GreetingService.class);
// ~~~~~~~~~~~~~~~~~~ ※ サンプルコードはありません
...
多くの場合、これらの設定は適用されるリソースに アクセスされてはじめて 検証されます。アプリケーションが正常に起動したからといって DI が思惑通りに実行されるとは限らないことには注意が必要です。
参考
-
実験した限りでは、依存関係にあるクラス同士を HK2 の
@Service
や@Contract
で注釈する方法では自動的に検出されませんでした。この実現方法については関心がないので追求しませんが、jersey - Not able to inject @Service and @Contract dependency in my resource class - Stack Overflow によるとプラグインなどが必要かもしれません。 ↩ -
こちらも実験した限りでは、プロパティや依存関係のあるクラスに対する
@Singleton
の注釈は自動的に検出されませんでした。一方、リソース自体を@Singleton
で注釈すると、リソースのインスタンスは一度だけ生成され、リソースへの(すべての) DI も一度しか行われません。リソースのライフサイクルについては Chapter 3. JAX-RS Application, Resources and Sub-Resources を参照。 ↩