[Jersey]HK2でDIしてみる

# はじめに

Tomcat + Jersey という構成の Web アプリケーションの動作を理解するために、[Tomcat Embed](https://mvnrepository.com/artifact/org.apache.tomcat.embed) を使って実験をしていきます。
いまどきこんな構成で開発を始めることは少ないかと思いますが、レガシーソフトウェアと戦う人たちの助けになれば幸いです。

## 関連記事の一覧（予定）

- [埋め込みTomcatでJerseyを動かしてみる](https://qiita.com/atti/items/fa3843f75c4bd669059a)
- HK2でDIしてみる ← イマココ
- [BeanValidationで入力値検証してみる](https://qiita.com/atti/items/2267a4b52b033dfabcca)
- [TomcatとJerseyでトランザクション管理してみる](https://qiita.com/atti/items/4d682f01cf6018172936)

## リポジトリ

https://github.com/at6ue/tomcat_embed-jersey

# オブジェクトをDIする

Jersey 2.x では HK2 の DI フレームワークが使用されており、ユーザアプリケーションでも同じ仕組みで任意のオブジェクトを注入できます。
以下のリソースに定義された `GreetingService` と `IdentityService` を注入してみましょう。

```java:Greeting.java
...
@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` は挨拶をしてくれた人の名前を返すサービスです。誰が挨拶をするかはオブジェクトが生成されるたびにランダムに決めることにします。

```java:IdentityService.java
...
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` に定義しました。

```java:GreetingService.java
...
public interface GreetingService {
    public String getMessage();
}
```

```java:HelloService.java
...
public class HelloService implements GreetingService {
   public String getMessage() {
        return "Hello";
    }
}
```

`@Inject` アノテーションをもつプロパティに、実際に注入するクラスを関連付けます。
`AbstractBinder` の具象クラスで依存関係を設定し、`ResourceConfig` に登録します。注入されるプロパティのクラスそのもののインスタンスを注入する場合は `bindAsContract` 、スーパークラスに対してサブクラスのインスタンスを注入する場合は `bind(サブクラス).to(スーパークラス)`　という形で指定します[^1]。

[^1]: 実験した限りでは、依存関係にあるクラス同士を HK2 の `@Service` や `@Contract` で注釈する方法では自動的に検出されませんでした。この実現方法については関心がないので追求しませんが、[jersey - Not able to inject @Service and @Contract dependency in my resource class - Stack Overflow](https://stackoverflow.com/questions/60731674/not-able-to-inject-service-and-contract-dependency-in-my-resource-class) によるとプラグインなどが必要かもしれません。


```java:AppConfig.java
...
@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);
            }
        });
    }
}
```

アプリケーションを起動して、アクセスしてみましょう。

```bash:Terminal
curl localhost:8080/app/greeting
# Hello from Steve
```

名前の部分はアクセスするたびに変化するはずです。これは DI が `RequestScoped` である、つまり、リクエストのたびに注入されたオブジェクトを生成しているためです。

# DI のライフサイクルを指定する

Web アプリケーションの複数のリソースから、ひとつのインスタンスを共通で参照したい場合は、依存関係に `Singleton` を設定します[^2]。

[^2]: こちらも実験した限りでは、プロパティや依存関係のあるクラスに対する `@Singleton` の注釈は自動的に検出されませんでした。一方、リソース自体を `@Singleton` で注釈すると、リソースのインスタンスは一度だけ生成され、リソースへの（すべての） DI も一度しか行われません。リソースのライフサイクルについては [Chapter 3. JAX-RS Application, Resources and Sub-Resources](https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/jaxrs-resources.html#d0e2692) を参照。

```java:AppConfig.java
...
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindAsContract(IdentityService.class).in(Singleton.class);
                bind(HelloService.class).to(GreetingService.class);
...
```

アプリケーションを再起動して何度かアクセスしてみると、常に同じ人から挨拶してもらえるようになります。つまり、 `IdentityService` のコンストラクタは一度だけ実行され、以降も同じインスタンスが使い回されていることが分かります。

```bash:Terminal
curl localhost:8080/app/greeting
# Hello from Charlie
curl localhost:8080/app/greeting
# Hello from Charlie
```

# 依存関係を変更する

ここまで、`GreetingService` には具象クラス `HelloService` を注入していましたが、同じインターフェースを実装する別のクラスに依存関係を変更することも可能です。
たとえば、`HelloService` ではなく `AlohaService` を注入したい場合には `ResourceConfig` 内の設定を変更するだけで済みます。

```java:AppConfig.java
...
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindAsContract(IdentityService.class);
                bind(AlohaService.class).to(GreetingService.class);
                //   ~~~~~~~~~~~~~~~~~~ ※ サンプルコードはありません
...
```

多くの場合、これらの設定は適用されるリソースに **アクセスされてはじめて** 検証されます。アプリケーションが正常に起動したからといって DI が思惑通りに実行されるとは限らないことには注意が必要です。

# 参考

- [Chapter 24. Custom Injection and Lifecycle Management](https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/ioc.html)
- [HK2 - Dependency Injection Kernel](https://javaee.github.io/hk2/introduction.html#basic-injection)