META-INF/services generatorというライブラリを使うと、サービス・プロバイダーが使用するMETA-INF/services配下のファイルを
自動生成することができます。
Generates META-INF/services files automatically
使い方はサイトを見るとほぼわかるのですが、試しておきましょう。
環境
確認環境は、以下のとおり。
$ java -version openjdk version "1.8.0_151" OpenJDK Runtime Environment (build 1.8.0_151-8u151-b12-0ubuntu0.16.04.2-b12) OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode) $ mvn -version Apache Maven 3.5.3 (3383c37e1f9e9b3bc3df5050c29c8aff9f295297; 2018-02-25T04:49:05+09:00) Maven home: /usr/local/maven3/current Java version: 1.8.0_151, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-8-openjdk-amd64/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.4.0-104-generic", arch: "amd64", family: "unix"
準備
Maven依存関係は、こちらになります。
<dependency> <groupId>org.kohsuke.metainf-services</groupId> <artifactId>metainf-services</artifactId> <version>1.7</version> <optional>true</optional> </dependency>
Pluggable Annotation Processing APIを使用して動作するので、依存関係はoptionalとして大丈夫です。つまり、コンパイル時にのみ
必要なライブラリで、実行時には不要となります。
あとは、テスト用にライブラリを追加。
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.1.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.9.1</version> <scope>test</scope> </dependency>
使ってみる
それでは、使ってみましょう。
こういうインターフェースに対して実装クラスを用意し、META-INF/services配下にファイル出力をするサンプルとしてみましょう。
src/main/java/org/littlewings/metainf/servicegenerator/MessageService.java
package org.littlewings.metainf.servicegenerator; public interface MessageService { String decorate(String message); }
使い方は簡単で、クラスに対して「@MetaInfServices」というアノテーションを付与するだけです。
src/main/java/org/littlewings/metainf/servicegenerator/StarMessageService.java
package org.littlewings.metainf.servicegenerator; import org.kohsuke.MetaInfServices; @MetaInfServices public class StarMessageService implements MessageService { @Override public String decorate(String message) { return "★★★" + message + "★★★"; } }
これで、コンパイル時にMETA-INF/services配下にファイルが生成されます。
target/classes/META-INF/services/org.littlewings.metainf.servicegenerator.MessageService org.littlewings.metainf.servicegenerator.StarMessageService
もうひとつ、実装クラスを用意してみましょう。
src/main/java/org/littlewings/metainf/servicegenerator/SharpMessageService.java
package org.littlewings.metainf.servicegenerator; import org.kohsuke.MetaInfServices; @MetaInfServices public class SharpMessageService implements MessageService { @Override public String decorate(String message) { return "###" + message + "###"; } }
テストコードで確認。
src/test/java/org/littlewings/metainf/servicegenerator/MessageServiceTest.java
package org.littlewings.metainf.servicegenerator; import java.util.ServiceLoader; import java.util.Spliterators; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class MessageServiceTest { @Test public void startMessage() { ServiceLoader<MessageService> loader = ServiceLoader.load(MessageService.class); MessageService messageService = StreamSupport .stream(Spliterators.spliteratorUnknownSize(loader.iterator(), 0), false) .filter(ms -> ms.getClass().isAssignableFrom(StarMessageService.class)) .collect(Collectors.toList()).get(0); assertThat(messageService.decorate("Hello World")) .isEqualTo("★★★Hello World★★★"); } @Test public void sharpMessage() { ServiceLoader<MessageService> loader = ServiceLoader.load(MessageService.class); MessageService messageService = StreamSupport .stream(Spliterators.spliteratorUnknownSize(loader.iterator(), 0), false) .filter(ms -> ms.getClass().isAssignableFrom(SharpMessageService.class)) .collect(Collectors.toList()).get(0); assertThat(messageService.decorate("Hello World")) .isEqualTo("###Hello World###"); } }
OKそうです。
複数のインターフェースを実装していたり、なにか別のクラスを継承している場合
複数のインターフェースを実装していたり、なにか別のクラスを継承しているクラスに対して、「@MetaInfServices」アノテーションを付与する時には、
ちょっと注意が必要です。
例えば、こういうクラスを用意。
src/main/java/org/littlewings/metainf/servicegenerator/MixedMessageService.java
package org.littlewings.metainf.servicegenerator; import java.io.Closeable; import java.io.IOException; import java.io.Serializable; import org.kohsuke.MetaInfServices; @MetaInfServices public class MixedMessageService implements Closeable, Serializable, MessageService { @Override public String decorate(String message) { return message; } @Override public void close() throws IOException { // no-op } }
複数のインターフェースを実装しつつ、「@MetaInfServices」アノテーションを付与しています。
また、抽象クラスを用意して
src/main/java/org/littlewings/metainf/servicegenerator/AbstractMessageService.java
package org.littlewings.metainf.servicegenerator; public abstract class AbstractMessageService implements MessageService { }
作成した抽象クラスを継承して、「@MetaInfServices」アノテーションを付与したクラスを用意。
src/main/java/org/littlewings/metainf/servicegenerator/SimpleMessageService.java
package org.littlewings.metainf.servicegenerator; import org.kohsuke.MetaInfServices; @MetaInfServices public class SimpleMessageService extends AbstractMessageService { @Override public String decorate(String message) { return message; } }
すると、それぞれコンパイルするとこういう結果になってしまいます。
target/classes/META-INF/services/java.io.Closeable
org.littlewings.metainf.servicegenerator.MixedMessageService
target/classes/META-INF/services/org.littlewings.metainf.servicegenerator.AbstractMessageService
org.littlewings.metainf.servicegenerator.SimpleMessageService
複数のインターフェースを実装しているクラスは最初にimplementsを宣言したインターフェースが選ばれ、抽象クラスを継承したクラスについては抽象クラスの
クラス名(要するに、直接継承しているクラスが対象になっている)でMETA-INF/services配下にファイルが生成されてしまっています。
これでは困るのですが、回避するには「@MetaInfServices」アノテーションに対象のClassクラスを指定すればOKです。
つまり、こういうことになります。
@MetaInfServices(MessageService.class) public class MixedMessageService implements Closeable, Serializable, MessageService { @Override public String decorate(String message) { return message; } @Override public void close() throws IOException { // no-op } }
@MetaInfServices(MessageService.class) public class SimpleMessageService extends AbstractMessageService { @Override public String decorate(String message) { return message; } }
これで、それぞれ「@MetaInfServices」アノテーションに指定したClassクラスに対するファイルが、META-INF/services配下に生成されます。