これは、なにをしたくて書いたもの?
Quarkusという、Red Hatが開発しているフレームワークを、ちょっと試してみようと。
Javaフレームワーク「Quarkus」登場。Javaコードからネイティブバイナリを生成し瞬時にJavaアプリが起動、コンテナへの最適化を実現。Red Hatがリリース - Publickey
ネイティブビルドも可能なフレームワークとして、けっこう話題になったイメージがあります。
そろそろ、試してみたいなぁと。
Quarkus
Quarkusについて。
Quarkus - Supersonic Subatomic Java
オフィシャルサイトを見ると、
- コンテナファースト … メモリフットプリントも軽く、Kubernetesのようなコンテナオーケストレーション環境に適している
- 命令形のスタイルも、リアクティブも両方使える
- デプロイが簡単
といったことが特徴として挙げられています。
とりあえず、Getting Startedを見ながら試していこうと思います。
環境
今回の環境は、こちらです。
$ java -version openjdk version "1.8.0_191" OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.18.04.1-b12) OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode) $ mvn -version Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-05T04:00:29+09:00) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 1.8.0_191, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-48-generic", arch: "amd64", family: "unix"
利用するQuarkusのバージョンは、0.14とします。
はじめてのQuakusアプリケーション
とりあえず、対象のディレクトリを作成。
$ mkdir getting-started $ cd getting-started
作成したディレクトリ内で、Mavenプロジェクトを作成します。
$ mvn io.quarkus:quarkus-maven-plugin:0.14.0:create \ -DprojectGroupId=org.littlewings.quarkus \ -DprojectArtifactId=getting-started
こんな感じのメッセージが出ます。
[INFO] ======================================================================================== [INFO] Your new application has been created in /path/to/getting-started/. [INFO] Navigate into this directory and launch your application with mvn compile quarkus:dev [INFO] Your application will be accessible on http://localhost:8080 [INFO] ========================================================================================
プロジェクト作成時に指定できるオプションは、こんな感じです。
ちなみに、Gettins Startedだと作成するJAX-RSリソースクラスとパスを指定していますが、今回は省略しました。
指定した場合は、JAX-RSリソースクラスとテストコードが作成されます。
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings.quarkus</groupId> <artifactId>getting-started</artifactId> <version>1.0-SNAPSHOT</version> <properties> <surefire-plugin.version>2.22.0</surefire-plugin.version> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <quarkus.version>0.14.0</quarkus.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-bom</artifactId> <version>${quarkus.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <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> <build> <plugins> <plugin> <groupId>io.quarkus</groupId> <artifactId>quarkus-maven-plugin</artifactId> <version>${quarkus.version}</version> <executions> <execution> <goals> <goal>build</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>${surefire-plugin.version}</version> <configuration> <systemProperties> <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> </systemProperties> </configuration> </plugin> </plugins> </build> <profiles> <profile> <id>native</id> <activation> <property> <name>native</name> </property> </activation> <build> <plugins> <plugin> <groupId>io.quarkus</groupId> <artifactId>quarkus-maven-plugin</artifactId> <version>${quarkus.version}</version> <executions> <execution> <goals> <goal>native-image</goal> </goals> <configuration> <enableHttpUrlHandler>true</enableHttpUrlHandler> </configuration> </execution> </executions> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>${surefire-plugin.version}</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> <configuration> <systemProperties> <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path> </systemProperties> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> </project>
RESTEasyが利用できそうです。ネイティブイメージのビルドもできそうな設定も入っています。
ターゲットは、Java 8みたいですね。
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source>
生成されたファイル一覧。
$ find -type f ./.dockerignore ./.mvn/wrapper/MavenWrapperDownloader.java ./.mvn/wrapper/maven-wrapper.jar ./.mvn/wrapper/maven-wrapper.properties ./pom.xml ./mvnw ./mvnw.cmd ./src/main/docker/Dockerfile.jvm ./src/main/docker/Dockerfile.native ./src/main/resources/META-INF/resources/index.html ./src/main/resources/application.properties
とりあえず、JAX-RSリソースクラスを作成してみます。
src/main/java/org/littlewings/quarkus/gettingstarted/HelloResource.java
package org.littlewings.quarkus.gettingstarted; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("hello") public class HelloResource { @GET @Path("message") @Produces(MediaType.TEXT_PLAIN) public String message() { return "Hello Quarkus!!"; } }
mainメソッドを持ったクラスはありません。
アプリケーション作成時に指示された、Mavenのコマンドを実行してみます。
$ mvn compile quarkus:dev
起動。ログを見ると、CDIも使えそうですね。
Listening for transport dt_socket at address: 5005 2019-04-29 15:06:19,305 INFO [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation 2019-04-29 15:06:19,862 INFO [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 557ms 2019-04-29 15:06:20,048 INFO [io.quarkus] (main) Quarkus 0.14.0 started in 0.809s. Listening on: http://[::]:8080 2019-04-29 15:06:20,049 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
確認。
$ curl localhost:8080/hello/message Hello Quarkus!!
動きました。
アプリケーションを動かしたままの状態で、少しソースコードを変更してみます。
@GET @Path("message") @Produces(MediaType.TEXT_PLAIN) public String message() { return "Hello Quarkus!!??"; }
curlでアクセスすると、ソースコードの変更を検知して反映してくれるようです。
2019-04-29 15:07:51,079 INFO [io.qua.dev] (executor-thread-1) Changed source files detected, recompiling [/path/to/getting-started/src/main/java/org/littlewings/quarkus/gettingstarted/HelloResource.java] 2019-04-29 15:07:51,399 INFO [io.quarkus] (executor-thread-1) Quarkus stopped in 0.002s 2019-04-29 15:07:51,399 INFO [io.qua.dep.QuarkusAugmentor] (executor-thread-1) Beginning quarkus augmentation 2019-04-29 15:07:51,506 INFO [io.qua.dep.QuarkusAugmentor] (executor-thread-1) Quarkus augmentation completed in 107ms 2019-04-29 15:07:51,520 INFO [io.quarkus] (executor-thread-1) Quarkus 0.14.0 started in 0.121s. Listening on: http://[::]:8080 2019-04-29 15:07:51,520 INFO [io.quarkus] (executor-thread-1) Installed features: [cdi, resteasy] 2019-04-29 15:07:51,520 INFO [io.qua.dev] (executor-thread-1) Hot replace total time: 0.442s
結果。
$ curl localhost:8080/hello/message Hello Quarkus!!??
とりあえず、元に戻しておきます。
@GET @Path("message") @Produces(MediaType.TEXT_PLAIN) public String message() { return "Hello Quarkus!!"; }
他に生成されたファイルを見てみましょう。
設定ファイル。まだ中身は無いようです。
src/main/resources/application.properties
# Configuration file # key = value
HTMLが含まれています。
src/main/resources/META-INF/resources/index.html
どうも、「ようこそ」っぽいページだなぁと思ったので
<div class="banner lead"> Your new Cloud-Native application is ready! </div> <div class="container"> <div class="left-column"> <p class="lead"> Congratulations, you have created a new Quarkus application.</p> <h2>Why do you see this?</h2> <p>This page is served by Quarkus. The source is in <code>src/main/resources/META-INF/resources/index.html</code>.</p> <h2>What can I do from here?</h2> <p>If not already done, run the application in <em>dev mode</em> using: <code>mvn compile quarkus:dev</code>. </p> <ul> <li>Add REST resources, Servlets, functions and other services in <code>src/main/java</code>.</li> <li>Your static assets are located in <code>src/main/resources/META-INF/resources</code>.</li> <li>Configure your application in <code>src/main/resources/application.properties</code>. </li> </ul> <h2>How do I get rid of this page?</h2> <p>Just delete the <code>src/main/resources/META-INF/resources/index.html</code> file.</p> </div>
アクセスしてみると、やっぱりそんな感じのページが表示されました。不要になったら、削除するページとなるでしょう。
デバッグしてみる
Quarkusで作ったアプリケーションをデバッグするには、「mvn quarkus:dev」で起動したアプリケーションにアタッチすることに
なるようです。
そういえば、起動時にこんな表示も出ていました。
Listening for transport dt_socket at address: 5005
IntelliJの場合、メニューの「Run」→「Attach to Process...」からアタッチするプロセスを選択します。
デバッグできました、と。
パッケージングしてみる
続いて、パッケージングしてみます。
$ mvn package
「-runner」とついたJARファイルを使って起動できます。
※ちなみに、このJARファイルはUber JARではありません
$ java -jar target/getting-started-1.0-SNAPSHOT-runner.jar 2019-04-29 15:04:22,781 INFO [io.quarkus] (main) Quarkus 0.14.0 started in 0.583s. Listening on: http://[::]:8080 2019-04-29 15:04:22,799 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
確認。
$ curl localhost:8080/hello/message Hello Quarkus!!
続いて、Dockerイメージを作成してみます。
プロジェクト作成時にDockerfileが作成されているので、この中身を見てみます。
src/main/docker/Dockerfile.jvm
#### # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode # # Before building the docker image run: # # mvn package # # Then, build the image with: # # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/getting-started-jvm . # # Then run the container using: # # docker run -i --rm -p 8080:8080 quarkus/getting-started-jvm # ### FROM fabric8/java-alpine-openjdk8-jre ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" ENV AB_ENABLED=jmx_exporter COPY target/lib/* /deployments/lib/ COPY target/*-runner.jar /deployments/app.jar ENTRYPOINT [ "/deployments/run-java.sh" ]
Java 8がベースイメージのようです。
パッケージングした(JARファイルを作った)状態で
$ mvn package
Dockerイメージをビルドしてみます。
$ docker image build -f src/main/docker/Dockerfile.jvm -t kazuhira/quarkus-getting-started:1.0 .
できあがったDockerイメージを起動。
$ docker container run -it --rm -p 8080:8080 kazuhira/quarkus-getting-started:1.0 exec java -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -javaagent:/opt/agent-bond/agent-bond.jar=jmx_exporter{{9779:/opt/agent-bond/jmx_exporter_config.yml}} -XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40 -XX:+ExitOnOutOfMemoryError -cp . -jar /deployments/app.jar 2019-04-29 06:14:40,736 INFO [io.quarkus] (main) Quarkus 0.14.0 started in 0.636s. Listening on: http://0.0.0.0:8080 2019-04-29 06:14:40,757 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
確認。
$ curl localhost:8080/hello/message Hello Quarkus!!
ネイティブイメージを作ってみる
続いて、ネイティブイメージを作成してみます。
ローカルにGraalVMをインストールしていない場合
ネイティブイメージのビルドにはGraalVMが必要になりますが、GraalVM自体のインストールをしていなくてもネイティブイメージの
ビルドは行うことはできます(その場合は、Dockerが必要になります)。
とりあえず、GraalVMは置いておいてビルドしてみましょう。Profileを「native」にして、「native-image.docker-build」をtrueにします。
$ mvn package -Pnative -Dnative-image.docker-build=true
すると、Quarkusのネイティブイメージビルド用のDockerイメージをダウンロードしてきて
Unable to find image 'quay.io/quarkus/centos-quarkus-native-image:graalvm-1.0.0-rc15' locally graalvm-1.0.0-rc15: Pulling from quarkus/centos-quarkus-native-image
このコンテナ内でビルが始まります。
Status: Downloaded newer image for quay.io/quarkus/centos-quarkus-native-image:graalvm-1.0.0-rc15 [getting-started-1.0-SNAPSHOT-runner:6] classlist: 4,070.13 ms [getting-started-1.0-SNAPSHOT-runner:6] (cap): 1,039.38 ms [getting-started-1.0-SNAPSHOT-runner:6] setup: 2,459.11 ms 06:18:42,745 INFO [org.jbo.threads] JBoss Threads version 3.0.0.Alpha4 06:18:43,382 INFO [org.xnio] XNIO version 3.7.0.Final 06:18:43,479 INFO [org.xni.nio] XNIO NIO Implementation Version 3.7.0.Final [getting-started-1.0-SNAPSHOT-runner:6] (typeflow): 12,833.82 ms [getting-started-1.0-SNAPSHOT-runner:6] (objects): 13,022.13 ms [getting-started-1.0-SNAPSHOT-runner:6] (features): 770.32 ms [getting-started-1.0-SNAPSHOT-runner:6] analysis: 27,357.96 ms Printing call tree to /project/reports/call_tree_getting-started-1.0-SNAPSHOT-runner_20190429_061911.txt Printing list of used classes to /project/reports/used_classes_getting-started-1.0-SNAPSHOT-runner_20190429_061913.txt Printing list of used packages to /project/reports/used_packages_getting-started-1.0-SNAPSHOT-runner_20190429_061913.txt [getting-started-1.0-SNAPSHOT-runner:6] universe: 670.15 ms [getting-started-1.0-SNAPSHOT-runner:6] (parse): 2,536.38 ms [getting-started-1.0-SNAPSHOT-runner:6] (inline): 4,012.46 ms [getting-started-1.0-SNAPSHOT-runner:6] (compile): 17,990.18 ms [getting-started-1.0-SNAPSHOT-runner:6] compile: 26,063.85 ms [getting-started-1.0-SNAPSHOT-runner:6] image: 2,574.95 ms [getting-started-1.0-SNAPSHOT-runner:6] write: 682.75 ms [getting-started-1.0-SNAPSHOT-runner:6] [total]: 68,396.51 ms [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 02:27 min [INFO] Finished at: 2019-04-29T15:19:45+09:00 [INFO] ------------------------------------------------------------------------
いやぁ、さすがに時間がかかりますねぇ…。
で、ネイティブイメージ用のDockerfileを使って、Dockerイメージをビルド。
$ docker image build -f src/main/docker/Dockerfile.native -t kazuhira/quarkus-getting-started-native:1.0 .
起動。
$ docker container run -it --rm -p 8080:8080 kazuhira/quarkus-getting-started-native:1.0 2019-04-29 06:22:32,735 INFO [io.quarkus] (main) Quarkus 0.14.0 started in 0.003s. Listening on: http://0.0.0.0:8080 2019-04-29 06:22:32,735 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
こちらもOKそうです。
$ curl localhost:8080/hello/message Hello Quarkus!!
あ、ネイティブイメージ自体を見ていませんでしたね。targetディレクトリ配下に、「-runner」が付与されたファイルが
ネイティブイメージです。
$ ./target/getting-started-1.0-SNAPSHOT-runner
ネイティブイメージビルド時のオプションは、こんな感じに出力されます。
[INFO] [io.quarkus.creator.phase.nativeimage.NativeImagePhase] docker run -v /path/to/getting-started/target:/project:z --rm --user 1000:1000 quay.io/quarkus/centos-quarkus-native-image:graalvm-1.0.0-rc15 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -jar getting-started-1.0-SNAPSHOT-runner.jar -J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 -H:FallbackThreshold=0 -H:+PrintAnalysisCallTree -H:-AddAllCharsets -H:EnableURLProtocols=http -H:-SpawnIsolates -H:-JNI --no-server -H:-UseServiceLoaderFeature -H:+StackTrace
このオプションを組み立てているのは、このあたりですね。
ベースイメージの指定。
private String builderImage = "quay.io/quarkus/centos-quarkus-native-image:graalvm-1.0.0-rc15";
で、プロセス実行です、と。
ローカルにGraalVMをインストールしている場合
ローカルにインストールされているGraalVMを使う場合は、環境変数「GRAALVM_HOME」を使ってGraalVMのインストール先を
指定します。
※GraalVM CE 1.0.0 RC15を使うべきみたいですが、今回はRC16を使いました…。
$ export GRAALVM_HOME=/usr/local/graalvm-ce $GRAALVM_HOME/bin/native-image --version GraalVM Version 1.0.0-rc16 CE
GraalVMのNative Image Maven Pluginを使うわけではないので、JAVA_HOMEにGraalVMを設定する必要はありません。
もしくは、Quarkus Maven Pluginのconfigurationに、graalvmHomeとして設定しても良さそうです。
ビルド。
$ mvn package -Pnative
すると、ローカルのGraalVMを使うようになりました。
[INFO] [io.quarkus.creator.phase.nativeimage.NativeImagePhase] /usr/local/graalvm-ce/bin/native-image -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -jar getting-started-1.0-SNAPSHOT-runner.jar -J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 -H:FallbackThreshold=0 -H:+PrintAnalysisCallTree -H:-AddAllCharsets -H:EnableURLProtocols=http -H:NativeLinkerOption=-no-pie -H:-SpawnIsolates -H:-JNI --no-server -H:-UseServiceLoaderFeature -H:+StackTrace
できあがったJARとネイティブイメージのサイズ比較。
$ ll -h ./target/getting-started-1.0-SNAPSHOT-runner* -rwxrwxr-x 1 xxxxx xxxxx 20M 4月 29 15:36 ./target/getting-started-1.0-SNAPSHOT-runner* -rw-r--r-- 1 xxxxx xxxxx 43K 4月 29 15:36 ./target/getting-started-1.0-SNAPSHOT-runner.jar
「-runner.jar」の方がやたらサイズが小さいです。
これには理由があって、実はUber JARではなく、一緒に依存ライブラリも必要な形態になっています。
依存するJARファイルは、「target/lib」配下にあります。
$ ll target/lib 合計 10204 drwxrwxr-x 2 xxxxx xxxxx 4096 4月 29 17:05 ./ drwxrwxr-x 10 xxxxx xxxxx 4096 4月 29 17:05 ../ -rw-rw-r-- 1 xxxxx xxxxx 65690 4月 29 17:05 com.sun.activation.jakarta.activation-1.2.1.jar -rw-rw-r-- 1 xxxxx xxxxx 335042 4月 29 17:05 commons-codec.commons-codec-1.11.jar -rw-rw-r-- 1 xxxxx xxxxx 208700 4月 29 17:05 commons-io.commons-io-2.5.jar -rw-rw-r-- 1 xxxxx xxxxx 61829 4月 29 17:05 commons-logging.commons-logging-1.2.jar -rw-rw-r-- 1 xxxxx xxxxx 113572 4月 29 17:05 io.quarkus.arc.arc-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 21874 4月 29 17:05 io.quarkus.quarkus-arc-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 110492 4月 29 17:05 io.quarkus.quarkus-core-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 16756 4月 29 17:05 io.quarkus.quarkus-jaxb-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 2549 4月 29 17:05 io.quarkus.quarkus-jsonb-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 2516 4月 29 17:05 io.quarkus.quarkus-jsonp-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 17662 4月 29 17:05 io.quarkus.quarkus-resteasy-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 6123 4月 29 17:05 io.quarkus.quarkus-resteasy-common-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 2984 4月 29 17:05 io.quarkus.quarkus-resteasy-jsonb-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 47118 4月 29 17:05 io.quarkus.quarkus-undertow-0.14.0.jar -rw-rw-r-- 1 xxxxx xxxxx 48421 4月 29 17:05 io.smallrye.smallrye-config-1.3.5.jar -rw-rw-r-- 1 xxxxx xxxxx 2263313 4月 29 17:05 io.undertow.undertow-core-2.0.19.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 532073 4月 29 17:05 io.undertow.undertow-servlet-2.0.19.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 26586 4月 29 17:05 javax.annotation.javax.annotation-api-1.3.2.jar -rw-rw-r-- 1 xxxxx xxxxx 73063 4月 29 17:05 javax.el.javax.el-api-3.0.0.jar -rw-rw-r-- 1 xxxxx xxxxx 107519 4月 29 17:05 javax.enterprise.cdi-api-2.0.SP1.jar -rw-rw-r-- 1 xxxxx xxxxx 2497 4月 29 17:05 javax.inject.javax.inject-1.jar -rw-rw-r-- 1 xxxxx xxxxx 24404 4月 29 17:05 javax.interceptor.javax.interceptor-api-1.2.jar -rw-rw-r-- 1 xxxxx xxxxx 23665 4月 29 17:05 javax.json.bind.javax.json.bind-api-1.0.jar -rw-rw-r-- 1 xxxxx xxxxx 93107 4月 29 17:05 javax.validation.validation-api-2.0.1.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 128076 4月 29 17:05 javax.xml.bind.jaxb-api-2.3.1.jar -rw-rw-r-- 1 xxxxx xxxxx 2254 4月 29 17:05 net.jcip.jcip-annotations-1.0.jar -rw-rw-r-- 1 xxxxx xxxxx 767916 4月 29 17:05 org.apache.httpcomponents.httpclient-4.5.7.jar -rw-rw-r-- 1 xxxxx xxxxx 326874 4月 29 17:05 org.apache.httpcomponents.httpcore-4.4.11.jar -rw-rw-r-- 1 xxxxx xxxxx 16772 4月 29 17:05 org.eclipse.microprofile.config.microprofile-config-api-1.3.jar -rw-rw-r-- 1 xxxxx xxxxx 313516 4月 29 17:05 org.eclipse.yasson-1.0.3.jar -rw-rw-r-- 1 xxxxx xxxxx 128770 4月 29 17:05 org.glassfish.javax.json-1.1.4.jar -rw-rw-r-- 1 xxxxx xxxxx 430053 4月 29 17:05 org.graalvm.sdk.graal-sdk-1.0.0-rc15.jar -rw-rw-r-- 1 xxxxx xxxxx 66469 4月 29 17:05 org.jboss.logging.jboss-logging-3.3.2.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 20593 4月 29 17:05 org.jboss.logging.jboss-logging-annotations-2.1.0.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 257131 4月 29 17:05 org.jboss.logmanager.jboss-logmanager-embedded-1.0.3.jar -rw-rw-r-- 1 xxxxx xxxxx 706440 4月 29 17:05 org.jboss.resteasy.resteasy-core-4.0.0.CR2.jar -rw-rw-r-- 1 xxxxx xxxxx 171097 4月 29 17:05 org.jboss.resteasy.resteasy-core-spi-4.0.0.CR2.jar -rw-rw-r-- 1 xxxxx xxxxx 15733 4月 29 17:05 org.jboss.resteasy.resteasy-json-binding-provider-4.0.0.CR2.jar -rw-rw-r-- 1 xxxxx xxxxx 13019 4月 29 17:05 org.jboss.resteasy.resteasy-json-p-provider-4.0.0.CR2.jar -rw-rw-r-- 1 xxxxx xxxxx 9977 4月 29 17:05 org.jboss.slf4j.slf4j-jboss-logging-1.1.0.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 300862 4月 29 17:05 org.jboss.spec.javax.servlet.jboss-servlet-api_4.0_spec-1.0.0.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 144090 4月 29 17:05 org.jboss.spec.javax.ws.rs.jboss-jaxrs-api_2.1_spec-1.0.2.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 131532 4月 29 17:05 org.jboss.spec.javax.xml.bind.jboss-jaxb-api_2.3_spec-1.0.1.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 94766 4月 29 17:05 org.jboss.threads.jboss-threads-3.0.0.Alpha4.jar -rw-rw-r-- 1 xxxxx xxxxx 585290 4月 29 17:05 org.jboss.xnio.xnio-api-3.7.0.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 121458 4月 29 17:05 org.jboss.xnio.xnio-nio-3.7.0.Final.jar -rw-rw-r-- 1 xxxxx xxxxx 14769 4月 29 17:05 org.osgi.org.osgi.annotation.versioning-1.0.0.jar -rw-rw-r-- 1 xxxxx xxxxx 2097 4月 29 17:05 org.reactivestreams.reactive-streams-1.0.2.jar -rw-rw-r-- 1 xxxxx xxxxx 41203 4月 29 17:05 org.slf4j.slf4j-api-1.7.25.jar -rw-rw-r-- 1 xxxxx xxxxx 306888 4月 29 17:05 org.wildfly.common.wildfly-common-1.5.0.Final-format-001.jar -rw-rw-r-- 1 xxxxx xxxxx 37911 4月 29 17:05 org.wildfly.security.wildfly-elytron-asn1-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 12632 4月 29 17:05 org.wildfly.security.wildfly-elytron-auth-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 255834 4月 29 17:05 org.wildfly.security.wildfly-elytron-auth-server-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 45306 4月 29 17:05 org.wildfly.security.wildfly-elytron-base-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 259181 4月 29 17:05 org.wildfly.security.wildfly-elytron-credential-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 63438 4月 29 17:05 org.wildfly.security.wildfly-elytron-permission-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 160200 4月 29 17:05 org.wildfly.security.wildfly-elytron-ssl-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 41636 4月 29 17:05 org.wildfly.security.wildfly-elytron-util-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 36464 4月 29 17:05 org.wildfly.security.wildfly-elytron-x500-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 72291 4月 29 17:05 org.wildfly.security.wildfly-elytron-x500-cert-2.0.0.Alpha1.jar -rw-rw-r-- 1 xxxxx xxxxx 7998 4月 29 17:05 org.wildfly.security.wildfly-elytron-x500-cert-util-2.0.0.Alpha1.jar
これをUber JARにするには、Quarkus Maven Pluginの設定で、「uberJar」をtrueにする必要があります。
<build> <plugins> <plugin> <groupId>io.quarkus</groupId> <artifactId>quarkus-maven-plugin</artifactId> <version>${quarkus.version}</version> <executions> <execution> <goals> <goal>build</goal> </goals> </execution> </executions> <configuration> <uberJar>true</uberJar> </configuration> </plugin>
パッケージングしなおすと、10M近くになりました。
-rw-r--r-- 1 xxxxx xxxxx 9.5M 4月 29 17:07 getting-started-1.0-SNAPSHOT-runner.jar
単純な比較だとネイティブイメージの方が大きいんですけど、Java自体のランタイムのサイズを考えると、この差が軽くひっくり
返りますね…。
CDIも足してみる
せっかくなので、もう少し処理を足してみましょう。
とりあえず、アプリケーションは起動したままにします。
$ mvn compile quarkus:dev
こんなクラスを作り、
src/main/java/org/littlewings/quarkus/gettingstarted/CalcService.java
package org.littlewings.quarkus.gettingstarted; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped public class CalcService { public int add(int a, int b) { return a + b; } }
JAX-RSリソースクラスは、エンドポイントを追加。
src/main/java/org/littlewings/quarkus/gettingstarted/HelloResource.java
package org.littlewings.quarkus.gettingstarted; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; @Path("hello") @ApplicationScoped public class HelloResource { @Inject CalcService calcService; @GET @Path("message") @Produces(MediaType.TEXT_PLAIN) public String message() { return "Hello Quarkus!!"; } @GET @Path("add") @Produces(MediaType.TEXT_PLAIN) public int add(@QueryParam("a") int a, @QueryParam("b") int b) { return calcService.add(a, b); } }
特に再起動などしないまま、変更が反映されました。
$ curl 'localhost:8080/hello/add?a=5&b=3' 8
すごいですね。
リクエストとレスポンスにJSONを使ってみる
pom.xmlに、RESTEasy JSON-B Extensionを追加します。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-jsonb</artifactId> </dependency>
起動。
$ mvn compile quarkus:dev
JAX-RSクラスに、以下のようなメソッドを追加して
@POST @Path("add") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public Map<String, ?> add(Map<String, Integer> request) { Map<String, Integer> result = new HashMap<>(); result.put("result", calcService.add(request.get("a"), request.get("b"))); return result; }
確認。
$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/hello/add -d '{"a": 5, "b": 3}' {result=8}
とりあえず、最初はこんな感じで。
オマケ:プロジェクト作成時に、JAX-RSリソースクラスを指定していたら?
$ mvn io.quarkus:quarkus-maven-plugin:0.14.0:create \ -DprojectGroupId=org.littlewings.quarkus \ -DprojectArtifactId=getting-started \ -DclassName="org.littlewings.quarkus.gettingstarted.HelloResource" \ -Dpath="/hello"
$ find -type f ./.dockerignore ./.mvn/wrapper/MavenWrapperDownloader.java ./.mvn/wrapper/maven-wrapper.jar ./.mvn/wrapper/maven-wrapper.properties ./pom.xml ./mvnw ./mvnw.cmd ./src/main/docker/Dockerfile.jvm ./src/main/docker/Dockerfile.native ./src/main/resources/META-INF/resources/index.html ./src/main/resources/application.properties ./src/main/java/org/littlewings/quarkus/gettingstarted/HelloResource.java ./src/test/java/org/littlewings/quarkus/gettingstarted/HelloResourceTest.java ./src/test/java/org/littlewings/quarkus/gettingstarted/NativeHelloResourceIT.java
作成された、JAX-RSリソースクラス。
src/main/java/org/littlewings/quarkus/gettingstarted/HelloResource.java
package org.littlewings.quarkus.gettingstarted; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/hello") public class HelloResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "hello"; } }
この場合、テストも出力されます。
src/test/java/org/littlewings/quarkus/gettingstarted/HelloResourceTest.java
package org.littlewings.quarkus.gettingstarted; import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; @QuarkusTest public class HelloResourceTest { @Test public void testHelloEndpoint() { given() .when().get("/hello") .then() .statusCode(200) .body(is("hello")); } }
ネイティブイメージ用のテストも出力されますが、こちらは通常のテストを継承したものですね。
src/test/java/org/littlewings/quarkus/gettingstarted/NativeHelloResourceIT.java
package org.littlewings.quarkus.gettingstarted; import io.quarkus.test.junit.SubstrateTest; @SubstrateTest public class NativeHelloResourceIT extends HelloResourceTest { // Execute the same tests but in native mode. }
ネイティブイメージ用のテストはインテグレーションテストとして動作するので、以下のようにして実行します。
※環境変数GRAALVM_HOMEを使う場合
$ mvn -Pnative integration-test $ mvn -Pnative verify
ビルドプロセスにネイティブイメージのビルドが入るので、それなりに時間はかかります…。