CLOVER🍀

That was when it all began.

はじめてのQuarkus

これは、なにをしたくて書いたもの?

Quarkusという、Red Hatが開発しているフレームワークを、ちょっと試してみようと。

Javaフレームワーク「Quarkus」登場。Javaコードからネイティブバイナリを生成し瞬時にJavaアプリが起動、コンテナへの最適化を実現。Red Hatがリリース - Publickey

ネイティブビルドも可能なフレームワークとして、けっこう話題になったイメージがあります。

そろそろ、試してみたいなぁと。

Quarkus

Quarkusについて。

Quarkus - Supersonic Subatomic Java

オフィシャルサイトを見ると、

  • コンテナファースト … メモリフットプリントも軽く、Kubernetesのようなコンテナオーケストレーション環境に適している
  • 命令形のスタイルも、リアクティブも両方使える
  • デプロイが簡単

といったことが特徴として挙げられています。

とりあえず、Getting Startedを見ながら試していこうと思います。

Quarkus - Get 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] ========================================================================================

プロジェクト作成時に指定できるオプションは、こんな感じです。

Maven Tooling

https://github.com/quarkusio/quarkus/blob/0.14.0/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java#L71-L93

ちなみに、Gettins Startedだと作成するJAX-RSリソースクラスとパスを指定していますが、今回は省略しました。
指定した場合は、JAX-RSリソースクラスとテストコードが作成されます。

生成された、pom.xmlを見てみます。
pom.xml

<?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

Running the application

起動。ログを見ると、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>

アクセスしてみると、やっぱりそんな感じのページが表示されました。不要になったら、削除するページとなるでしょう。

f:id:Kazuhira:20190429145401p:plain

デバッグしてみる

Quarkusで作ったアプリケーションをデバッグするには、「mvn quarkus:dev」で起動したアプリケーションにアタッチすることに
なるようです。

Development Mode

そういえば、起動時にこんな表示も出ていました。

Listening for transport dt_socket at address: 5005

IntelliJの場合、メニューの「Run」→「Attach to Process...」からアタッチするプロセスを選択します。

f:id:Kazuhira:20190429150243p:plain

デバッグできました、と。

f:id:Kazuhira:20190429145858p:plain

パッケージングしてみる

続いて、パッケージングしてみます。

$ 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!!

ネイティブイメージを作ってみる

続いて、ネイティブイメージを作成してみます。

Building Native Image Guide

ローカルにGraalVMをインストールしていない場合

ネイティブイメージのビルドにはGraalVMが必要になりますが、GraalVM自体のインストールをしていなくてもネイティブイメージの
ビルドは行うことはできます(その場合は、Dockerが必要になります)。

とりあえず、GraalVMは置いておいてビルドしてみましょう。Profileを「native」にして、「native-image.docker-build」をtrueにします。

$ mvn package -Pnative -Dnative-image.docker-build=true

Producing a container

すると、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

このオプションを組み立てているのは、このあたりですね。

https://github.com/quarkusio/quarkus/blob/0.14.0/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java#L352-L490

ベースイメージの指定。

https://github.com/quarkusio/quarkus/blob/0.14.0/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java#L104

    private String builderImage = "quay.io/quarkus/centos-quarkus-native-image:graalvm-1.0.0-rc15";

で、プロセス実行です、と。

https://github.com/quarkusio/quarkus/blob/0.14.0/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java#L493-L496

ローカルに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を設定する必要はありません。

https://github.com/quarkusio/quarkus/blob/0.14.0/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java#L339-L346

もしくは、Quarkus Maven Pluginのconfigurationに、graalvmHomeとして設定しても良さそうです。

https://github.com/quarkusio/quarkus/blob/0.14.0/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java#L92

ビルド。

$ 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>

Uber-Jar Creation

パッケージングしなおすと、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

ビルドプロセスにネイティブイメージのビルドが入るので、それなりに時間はかかります…。