0
0

More than 3 years have passed since last update.

[Jersey]HK2でDIしてみる

Last updated at Posted at 2021-07-07

はじめに

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

関連記事の一覧(予定)

リポジトリ

オブジェクトをDIする

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

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

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 に定義しました。

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

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

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);
            }
        });
    }
}

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

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

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

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

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

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

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

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

依存関係を変更する

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

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

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

参考


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

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0