[Java] サービスプロバイダーを作ってみよう 〜Java9以降〜

2020年4月13日月曜日

Java

Java9のモジュール機構

サービスプロバイダーを作ってみよう Java8以前ではJavaのサービスプロバイダーを作った。
このときはjarの中にMETA-INF/services/ファイルを配置したけど、Java9以降ではこの辺に新しいやり方が導入されている。
まずはJava8以前と同様にサービス、サービスプロバイダー、アプリケーションを作っていこう。
※.java自体はJava8以前と同じ。

サービス

まずはサービスのインターフェースを定義する。
local/api/MyService.java
package local.api;

public interface MyService {
    void show();
}
Java8と違うところは、MyServiceをjarの外部に公開するためにmodule-info.javaを用意するところ。
META-INF/servicesと違いコンパイル時に必要となる。
module-info.java
module local.api {
    exports local.api;
    uses local.api.MyService;
}
これによってlocal.apiパッケージのMyServiceがjarの外部に公開される。
これをコンパイルしてjarにする。
$ javac -d bin src/*.java src/local/api/*.java
$ jar --create --file local-api.jar -C bin .

サービスプロバイダー

次にサービスプロバイダー。試しに2つ作ってみる。
local/provider/MyServiceProvider1.java
package local.provider;

import local.api.MyService;

public class MyServiceProvider1 implements MyService {
    @Override public void show() {
        System.out.println("MyService1");
    };
}
local/provider/MyServiceProvider2.java
package local.provider;

import local.api.MyService;

public class MyServiceProvider2 implements MyService {
    @Override public void show() {
        System.out.println("MyService2");
    };
}
これもMETA-INF/servicesではなく、module-info.javaを作成する。
今度はrequiresで外部から取り込むパッケージを指定して、providesでサービスを実装するサービスプロバイダーを指定する。
module-info.java
module local.provider {
    requires local.api;
    provides local.api.MyService with local.provider.MyServiceProvider1, local.provider.MyServiceProvider2;
}
これをコンパイルしてjarにする。
$ javac -d bin --module-path ../api src/*.java src/local/provider/*.java
$ jar --create --file local-provider.jar -C bin .

アプリケーション

最後にサービスプロバイダーを実行するアプリケーションを作成する。
local/app/Main.java
package local.app;

import java.util.ServiceLoader;

import local.api.MyService;

public class Main {
    public static void main(String ... args) {
        for (MyService service : ServiceLoader.load(MyService.class)) {
            System.out.println(service.getClass().getName());
            service.show();
        }
    }
}
module-info.java
module local.app {
    requires local.api;
    uses local.api.MyService;
}
$ javac -d bin --module-path ../api src/*.java src/local/app/*.java
$ jar --create --file local-app.jar -C bin .

実行

すべてのjarができたので、実行してみよう。jarをlibに集めておく。
$ ls lib
local-api.jar      local-app.jar      local-provider.jar
$ java --module-path lib --module local.app/local.app.Main
local.provider.MyServiceProvider1
MyService1
local.provider.MyServiceProvider2
MyService2

全体のディレクトリイメージ

$ tree
.
├── api
│   ├── bin
│   │   ├── module-info.class
│   │   └── local
│   │       └── api
│   │           └── MyService.class
│   ├── local-api.jar
│   └── src
│       ├── module-info.java
│       └── local
│           └── api
│               └── MyService.java
├── app
│   ├── bin
│   │   ├── module-info.class
│   │   └── local
│   │       └── app
│   │           └── Main.class
│   ├── local-app.jar
│   └── src
│       ├── module-info.java
│       └── local
│           └── app
│               └── Main.java
├── lib
│   ├── local-api.jar
│   ├── local-app.jar
│   └── local-provider.jar
└── provider
    ├── bin
    │   ├── module-info.class
    │   └── local
    │       └── provider
    │           ├── MyServiceProvider1.class
    │           └── MyServiceProvider2.class
    ├── local-provider.jar
    └── src
        ├── module-info.java
        └── local
            └── provider
                ├── MyServiceProvider1.java
                └── MyServiceProvider2.java