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

2020年3月16日月曜日

Java

サービスと言っても、今回やろうとしているのはWebサービスとかではなくJavaが備えているモジュール切り替えの仕組み。
例えば、JDBCドライバは様々なDBMSに対応しているけど、インターフェースに従ってさえいれば、切り替えは比較的容易になっている。

登場人物

登場人物はサービス、サービスプロバイダー、アプリケーションの3つ。

サービスは実装を伴わず、インターフェースを定義する。
サービスプロバイダーはサービスを実装する。
アプリケーションはサービスを通じてサービスプロバイダーを呼び出す。

この3つをそれぞれ別のjarにして、呼び出してみよう。

サービス

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

public interface MyService {
    void show();
}
これをコンパイルしてjarにする。
$ javac -d bin src/local/api/*.java
$ jar cvf 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");
    };
}
先ほど作ったlocal-api.jarを参照してコンパイル。
$ javac -d bin -cp .:../api/local-api.jar src/local/provider/*.java

jarを作る前にMETA-INF/services/にMyServiceを実装したサービスプロバイダーとして公開するクラスを記載しておく必要がある。
META-INF/services/local.api.MyService
local.provider.MyServiceProvider1
local.provider.MyServiceProvider2
$ jar cvf local-provider.jar -C bin .

アプリケーション

最後にサービスプロバイダーを実行するアプリケーションを作成する。
サービスを呼び出すためにはjava.util.ServiceLoaderを使って、特定のサービスインターフェースを実装したサービスプロバイダーを探す必要がある。
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();
        }
    }
}
$ javac -d bin -cp .:../api/local-api.jar src/local/app/*.java
$ jar cvf local-app.jar -C bin .

実行

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

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

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