これは、なにをしたくて書いたもの?
Quakusでコマンドラインアプリケーションを作れそうな感じのガイドがあったので、試してみようかなと。
Quarkus - Command Mode Applications
Picocli向けのExtensionもあるようです。
Quarkus - Command Mode with Picocli
なのですが、こちらはexperimentalということと、先にシンプルな方からかなということで今回はパス。
環境
今回の環境は、こちらです。
$ java --version openjdk 11.0.7 2020-04-14 OpenJDK Runtime Environment (build 11.0.7+10-post-Ubuntu-2ubuntu218.04) OpenJDK 64-Bit Server VM (build 11.0.7+10-post-Ubuntu-2ubuntu218.04, mixed mode, sharing) $ mvn --version Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-106-generic", arch: "amd64", family: "unix"
では、簡単なコマンドラインアプリケーションを作成してみます。
プロジェクトを作成する
まずは、プロジェクトを作成してみます。
ドキュメントを見ても、必要そうなExtensionはわからなかったのですが
Quarkus - Command Mode Applications
説明に出てくる「@QuarkusMain」アノテーションはquarkus-coreにあるみたいなので
なにも考えずにプロジェクトを作成してみます。
$ mvn io.quarkus:quarkus-maven-plugin:1.5.1.Final:create \ -DprojectGroupId=org.littlewings \ -DprojectArtifactId=plain-commandline-application
pom.xmlに指定された依存関係。なにも指定しなくても、RESTEasyは入るんですね。
<dependencies> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-junit5</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <scope>test</scope> </dependency> </dependencies>
できあがったディレクトリに移動。
$ cd plain-commandline-application
シンプルに使う
とりあえず、mainクラスらしきものを作ってみます。
src/main/java/org/littlewings/quarkus/cli/ClipApp.java
package org.littlewings.quarkus.cli; import io.quarkus.runtime.QuarkusApplication; import io.quarkus.runtime.annotations.QuarkusMain; @QuarkusMain public class ClipApp implements QuarkusApplication { @Override public int run(String... args) throws Exception { String word; if (args.length > 0) { word = args[0]; } else { word = "World"; } System.out.printf("Hello %s!!%n", word); return 0; } }
QuarkusApplicationインターフェースを実装し、@QuarkusMainアノテーションを付与すればいいみたいです。
QuarkusApplicationインターフェースから要求されるのは、runメソッドのみです。
runメソッドの戻り値は、プロセスの終了ステータスになります。
「mvn quarkus:dev」してみます。
$ mvn compile quarkus:dev
起動したら、いきなり実行されます。
[INFO] --- quarkus-maven-plugin:1.5.1.Final:dev (default-cli) @ plain-commandline-application --- Listening for transport dt_socket at address: 5005 __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2020-06-18 22:52:48,548 INFO [io.quarkus] (Quarkus Main Thread) plain-commandline-application 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.1.Final) started in 0.938s. Listening on: http://0.0.0.0:8080 2020-06-18 22:52:48,551 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2020-06-18 22:52:48,551 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy] Hello World!! 2020-06-18 22:52:48,602 INFO [io.quarkus] (Quarkus Main Thread) plain-commandline-application stopped in 0.049s Quarkus application exited with code 0 Press Enter to restart or Ctrl + C to quit
Enterを押すと、もう1回実行されますね。
Restarting... __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2020-06-18 22:53:20,002 INFO [io.quarkus] (Quarkus Main Thread) plain-commandline-application 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.1.Final) started in 0.167s. Listening on: http://0.0.0.0:8080 2020-06-18 22:53:20,002 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2020-06-18 22:53:20,002 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy] Hello World!! 2020-06-18 22:53:20,003 INFO [io.quarkus] (Quarkus Main Thread) plain-commandline-application stopped in 0.000s Quarkus application exited with code 0 Press Enter to restart or Ctrl + C to quit
プログラムを変更していれば、それが反映されます。
プログラムの引数を指定するには、「quarkus.args」で指定すればいいみたいです。
$ mvn compile quarkus:dev -Dquarkus.args=Quarkus
引数が渡りました。
2020-06-18 22:54:11,939 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy] Hello Quarkus!!
引数を複数渡したい場合は、どうするんでしょう?
2つ要求するようにしてみました。
src/main/java/org/littlewings/quarkus/cli/ClipApp.java
package org.littlewings.quarkus.cli; import io.quarkus.runtime.QuarkusApplication; import io.quarkus.runtime.annotations.QuarkusMain; @QuarkusMain public class ClipApp implements QuarkusApplication { @Override public int run(String... args) throws Exception { String word; if (args.length > 0) { word = args[0]; } else { word = "World"; } String decoration; if (args.length > 1) { decoration = args[1]; } else { decoration = "!!"; } System.out.printf("Hello %s%s%n", word, decoration); return 0; } }
どうやら、引用符で囲ってスペース区切りで渡せば良さそうです。
$ mvn compile quarkus:dev -Dquarkus.args='Quarkus ?' 2020-06-18 22:59:27,717 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy] Hello Quarkus?
ヒントはこちら。
/** * The arguments passed to the command line. * <p> * We don't make it a list as the args are separated by a space, not a comma. */ @ConfigItem public Optional<String> args;
CDIを使ってみる
アプリケーション起動時のログを見ていると、CDI(ArC)が入っているので使えそうですね。
2020-06-18 22:53:20,002 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy]
新しくCDI管理Beanを作成し、先ほどのmainクラスのメッセージを組み立てていた部分をそのままCDI管理Beanに移動します。
src/main/java/org/littlewings/quarkus/cli/MessageService.java
package org.littlewings.quarkus.cli; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped public class MessageService { public String create(String... args) { String word; if (args.length > 0) { word = args[0]; } else { word = "World"; } String decoration; if (args.length > 1) { decoration = args[1]; } else { decoration = "!!"; } return String.format("Hello %s%s", word, decoration); } }
mainクラス側は、こんな感じに。
src/main/java/org/littlewings/quarkus/cli/ClipApp.java
package org.littlewings.quarkus.cli; import javax.inject.Inject; import io.quarkus.runtime.QuarkusApplication; import io.quarkus.runtime.annotations.QuarkusMain; @QuarkusMain public class ClipApp implements QuarkusApplication { @Inject MessageService messageService; @Override public int run(String... args) throws Exception { System.out.println(messageService.create(args)); return 0; } }
実行。
$ mvn compile quarkus:dev -Dquarkus.args='Quarkus ?' 2020-06-18 23:09:01,941 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy] Hello Quarkus?
OKです。
複数のmainクラスがある場合
@QuarkusMainアノテーションが付与されたクラスが、複数ある場合を試してみます。
先ほどのmainクラスをコピーして作成。
src/main/java/org/littlewings/quarkus/cli/ClipApp2.java
package org.littlewings.quarkus.cli; import javax.inject.Inject; import io.quarkus.runtime.QuarkusApplication; import io.quarkus.runtime.annotations.QuarkusMain; @QuarkusMain public class ClipApp2 implements QuarkusApplication { @Inject MessageService messageService; @Override public int run(String... args) throws Exception { System.out.println(messageService.create(args)); return 0; } }
※ プレーンなpublic static void mainメソッドを持ったクラスを定義するやり方もあるようですが、ちょっとうまくいかなかったので
今回はパスします
実行。
$ mvn compile quarkus:dev -Dquarkus.args='Quarkus ?'
こうすると、mainクラスが一意に定められずに実行に失敗します。
2020-06-18 23:15:36,767 ERROR [io.qua.dep.dev.DevModeMain] (main) Failed to start Quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors [error]: Build step io.quarkus.deployment.steps.MainClassBuildStep#mainClassBuildStep threw an exception: java.lang.RuntimeException: More than one @QuarkusMain method found with name '': org.littlewings.quarkus.cli.ClipApp and org.littlewings.quarkus.cli.ClipApp2
「mvn package」なども同じように失敗します。
このような場合、@QuarkusMainアノテーションのname属性の値を指定して
@QuarkusMain(name = "cli-app") public class ClipApp implements QuarkusApplication { ... } @QuarkusMain(name = "cli-app2") public class ClipApp2 implements QuarkusApplication { ... }
ビルド時に「-Dquarkus.package.main-class」システムプロパティで@QuarkusMainのname属性に指定した名前を指定するか、
$ mvn compile quarkus:dev -Dquarkus.package.main-class=cli-app
「application.properties」ファイルで指定します。
src/main/resources/application.properties
# Configuration file # key = value quarkus.package.main-class=cli-app
システムプロパティで指定できるので、pom.xmlのpropertiesでも書けます。
<properties> ... <quarkus.package.main-class>cli-app</quarkus.package.main-class> </properties>
アプリケーションの実行時ではなく、ビルド時の指定になるのはちょっと注意点ですね。
今回は、application.propertiesで指定することにしました。
src/main/resources/application.properties
# Configuration file # key = value quarkus.package.main-class=cli-app
パッケージングして実行してみる
パッケージングして実行してみましょう。
まずは、JARファイルから。
$ mvn clean package
実行。
$ java -jar target/plain-commandline-application-1.0-SNAPSHOT-runner.jar Quarkus ? __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2020-06-18 23:28:37,443 INFO [io.quarkus] (main) plain-commandline-application 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.1.Final) started in 0.674s. Listening on: http://0.0.0.0:8080 2020-06-18 23:28:37,463 INFO [io.quarkus] (main) Profile prod activated. 2020-06-18 23:28:37,464 INFO [io.quarkus] (main) Installed features: [cdi, resteasy] Hello Quarkus? 2020-06-18 23:28:37,515 INFO [io.quarkus] (main) plain-commandline-application stopped in 0.050s
続いてネイティブイメージは…ビルドがうちの環境だと辛かったので、諦めました…。なにかしら問題にぶつかることが多いので、
試しておいた方がいいんですけどねぇ…。
RESTEasyを外したい
ところで、今回コマンドラインアプリケーションであるにも関わらず、RESTEasyが起動してそうな感じのログが見えます。
2020-06-18 23:28:37,443 INFO [io.quarkus] (main) plain-commandline-application 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.1.Final) started in 0.674s. Listening on: http://0.0.0.0:8080
実際、デフォルトだとポート8080がかぶったりすると、起動に失敗しますし。
まあ、pom.xmlにRESTEasyが入っているので、そりゃあそうだろうという気はしますが。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy</artifactId> </dependency>
RESTEasyを外してみましょう。そんなことはドキュメントのどこにも書いていないので、完全に興味で試してるだけですが。
依存関係を「quarkus-core」にしてみます。
<dependency> <groupId>io.quarkus</groupId> <!-- <artifactId>quarkus-resteasy</artifactId> --> <artifactId>quarkus-core</artifactId> </dependency>
すると、ビルドはうまくいきますが、実行時にはCDIが必要なようで失敗します。
2020-06-19 00:05:35,599 ERROR [io.qua.run.Application] (main) Error running Quarkus application: java.lang.IllegalStateException: Unable to locate CDIProvider at javax.enterprise.inject.spi.CDI.findAllProviders(CDI.java:121) at javax.enterprise.inject.spi.CDI.getCDIProvider(CDI.java:82) at javax.enterprise.inject.spi.CDI.current(CDI.java:64) at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:93) at io.quarkus.runtime.Quarkus.run(Quarkus.java:61) at io.quarkus.runtime.Quarkus.run(Quarkus.java:38) at io.quarkus.runner.GeneratedMain.main(GeneratedMain.zig:30)
これは、アプリケーション内でCDIを使っていなくても、同じことになりました。
というわけで、今度は「quarkus-arc」にしてみます。
<dependency> <groupId>io.quarkus</groupId> <!-- <artifactId>quarkus-resteasy</artifactId> --> <artifactId>quarkus-arc</artifactId> </dependency>
今度はうまくいきました。
$ java -jar target/plain-commandline-application-1.0-SNAPSHOT-runner.jar Quarkus ? __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2020-06-19 00:07:52,050 INFO [io.quarkus] (main) plain-commandline-application 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.1.Final) started in 0.262s. 2020-06-19 00:07:52,069 INFO [io.quarkus] (main) Profile prod activated. 2020-06-19 00:07:52,069 INFO [io.quarkus] (main) Installed features: [cdi] Hello Quarkus? 2020-06-19 00:07:52,074 INFO [io.quarkus] (main) plain-commandline-application stopped in 0.003s
ExtensionはCDIのみです。
2020-06-19 00:07:52,069 INFO [io.quarkus] (main) Installed features: [cdi]
こんなところで。