JavaFX 8からJavaFX 11以降へのマイグレーション

前回のエントリーでJavaFX 8はやめようという話をしました。

skrb.hatenablog.com

JavaFX 8からJavaFX 11以降のバージョンへの移行は、ほとんどの場合、ビルド/実行方法を変えるだけです。今回はサンプルを使って、それを説明していきます。

今回使用するサンプルは、散布図と一次回帰を表示するというアプリケーションです。表示するデータはCSVで、その読み込みにはOpenCSVを使用しています。また、一次回帰の計算にはApache Commons Mathを使用しました。

f:id:skrb:20190630212447p:plain

ソースはこちら。

github.com

JavaFX 8で作成したJARをJavaFX 12で動かす

GitHubのscatterplotterのブランチjavafx8でJavaFX 8用のソースになります。

今回はこれをLiberica JDK 8u212を使ってビルドしました。ビルドにはMavenを使用し、前述したようにOpenCSVとApache Commons Mathを使っているので、依存性に記述しています。

では実行してみましょう。ここでは、Windowsで実行しています。

もちろん、Mavenを使っても実行できるのですが、ここではJava 8とJava 11の違いが分かるように、コマンドラインで実行してみました。

Mavenなので、targetディレクトリにJARファイルが作られます。また、クラスパスで指定できるように、依存ライブラリをtarget/dependencyにコピーしておきます。

C:\scatterplotter> mvn dependency:copy-dependencies

実行は次のように行います。

C:\scatterplotter>java -cp target\scatterplotter-1.0-SNAPSHOT.jar;target\dependency\* net.javainthebox.scatterplotter.Main

2カラムのCSVで、先頭行が軸の名前になっているファイルを読み込むと上のような散布図と一次回帰線が表示されるはずです。

では、これをJavaFX 12.0.1とJava 11.0.3 (ここでは、AdoptOpenJDKのLTSを使用しました)を使って実行してみます。

JavaFX 12.0.1を使うには、以下のURLの"Latest Release"からJavaFX SDKをダウンロードして、適当な場所に展開します。ここではC:\Program Files\Javaに展開しました。

gluonhq.com

では、実行してみます。

C:\scatterplotter>"C:\Program Files\AdoptOpenJDK\jdk-11.0.3.7-hotspot\bin\java" --module-path "C:\Program Files\Java\javafx-sdk-12.0.1\lib" --add-modules javafx.fxml,javafx.controls -cp target\scatterplotter-1.0-SNAPSHOT.jar;target\dependency\* net.javainthebox.scatterplotter.Main

Java 8の場合と異なるのは、--module-pathオプションと--add-modulesオプションです。--module-pathオプションには先ほど展開したJavaFX SDKのlibディレクトリを指定します。--add-modulesオプションにはJavaFXで使用するモジュールを追加します。

ここでは、javafx.controlsとjavafx.fxmlを指定しています。JavaFXのモジュールとしては、javafx.baseとjavafx.graphicsを使用していますが、javafx.controlsもこの2つのモジュールに依存しているので、--add-modulesオプションに指定する必要はありません。

逆にいうと、このオプションだけを指定すれば、ソースを変更することなくJavaFX 12でも実行することができます。

でも、JavaFX SDKを展開しておかなくてはいけないなど、事前の準備が必要です。そこで、JavaFX 11以降のバージョン用にビルド・実行環境を作成します。

JavaFX 12でのビルド・実行環境を作る

といっても、Mavenのpom.xmlを変更するだけです。

まず、プロジェクトの依存性にJavaFXを加えます。OpenCSVとCommons Mathだけだったところに、javafx-fxmlとjavafx-controlsを加えます。

    <dependencies>
        <dependency>
            <groupId>com.opencsv</groupId>
            <artifactId>opencsv</artifactId>
            <version>4.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-math3</artifactId>
            <version>3.6.1</version>
        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-fxml</artifactId>
+            <version>12.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-controls</artifactId>
+            <version>12.0.1</version>
+        </dependency>
    </dependencies>

行頭に+がついている部分が追加した部部分です。

ここでは、javafx-fxmlとjavafx-controlsだけですが、必要に応じて他のモジュールを加えます。たとえば、ムービーやサウンドを使う場合はjavafx-media、WebViewを使いたいのであればjavafx-webを加えます。

前述したように、javafx-controlsはjavafx-graphicsとjavafx-baseに依存しているので、自動的に取り込まれます。

これだけでもいいのですが、MavenJavaFX Pluginがあるので、これをExec Maven Pluginの代わりに使ってみましょう。

        <plugins>
            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>exec-maven-plugin</artifactId>
-                <version>1.6.0</version>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>java</goal>
-                        </goals>
-                    </execution>
-                </executions>
+                <groupId>org.openjfx</groupId>
+                <artifactId>javafx-maven-plugin</artifactId>
+                <version>0.0.2</version>
                <configuration>
                    <mainClass>net.javainthebox.scatterplotter.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>

行頭の-が削除した行で、+が追加した行です。

JavaFX Pluginは以下のURLに情報があります。

github.com

これで、JavaFX SDKをダウンロードして、展開する必要がなくなります。

Exec Maven Pluginではないので、実行にはjavafx:runを使います。

C:\scatterplotter>mvn clean javafx:run

簡単ですね!

もちろん、コマンドラインからでも実行できます。Java 8で実行するときに説明したように依存するライブラリをコピーしてから実行するようにします。

C:\scatterplotter>mvn dependency:copy-dependencies
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< net.javainthebox:scatterplotter >-------------------
[INFO] Building scatterplotter 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:copy-dependencies (default-cli) @ scatterplotter ---
          <<省略>>
C:\scatterplotter>java --module-path target\dependency --add-modules javafx.controls,javafx.fxml -cp target\scatterplotter-1.0-SNAPSHOT.jar;target\dependency\* net.javainthebox.scatterplotter.Main

Java 8の時とは異なり、dependencyディレクトリにJavaFXのJARファイルもコピーされます。このため、モジュールパスとクラスパスで同じディレクトリを指定しているのですが、これで問題なく動きます。

JREをバンドルしたパッケージを作成

最後にアプリケーション用にカスタマイズしたJREを作成して、アプリケーションパッケージを作ってみましょう。

先ほどまではJavaFX SDKを使っていたのですが、ここで使用するのはJavaFX jmodsです。jmodsもSDKと同じページからダウンロードできます。

SDKとjmodsの違いは、SDKにはJARファイルが含められていますが、jmodsの方はJMODファイルだということです。

JMODファイルなんて聞いたことがないという人も多いと思いますが、JMODファイルはDLLとかシェアードライブラリなどのネイティブライブラリを一緒にパッケージングできるファイル形式です。たとえば、JDKjava.baseなどのモジュールもJMODファイルで提供されています。JDKディレクトリの下にあるjmodsファイルに配置されています。

JavaFXはプラットフォームごとにネイティブのライブラリを含むので、JMODファイルの方がやりやすいのです。でも、JavaFXのJMODファイルは通常実行時には使えません。jlinkでカスタムJREを作成するときに使うことができます。

では、jlinkでカスタムJREを作成してみましょう。

ここでは、ダウンロードしたJavaFX jmodsのZIPファイルをC:\scatterplotterディレクトリに展開しました。

jlinkは使用するモジュールだけをJREにしますが、scatterplotterで直接使用しているのはjava.baseモジュール、javafx.controlsモジュール、javafx.fxmlモジュールの3つです。この中で、java.baseモジュールはJavaのアプリケーションであれば必ず使用するので、デフォルトで追加されます。

また、OpenCSVもApache Commons Mathもモジュールではないので、JREには含めません。

では、jlinkでカスタムJREを作成しましょう。

C:\scatterplotter>jlink --module-path javafx-jmods-12.0.1 --add-modules javafx.controls,javafx.fxml --compress=2 --output scatterplotter

モジュールパス(--module-path)には展開したJavaFX jmodsのディレクトリを指定します。--add-modulesは前述したようにjavafx.controlsとjavafx.fxmlをしてします。--compressオプションはJREの圧縮率を示すオプションで0, 1, 2のいずれかを指定します。0は圧縮なし、2が圧縮率が高いことになります。

最後の--outputオプションでJREの出力先ディレクトリを指定します。ここでは、scatterplotterを指定しているので、scatterplotterディレクトリにJREが作成されます。

作成したJREで以下のように実行すると、JREに組み込まれたモジュールが分かります。

C:\scatterplotter>scatterplotter\bin\java --list-modules
java.base@12.0.1
java.datatransfer@12.0.1
java.desktop@12.0.1
java.prefs@12.0.1
java.scripting@12.0.1
java.xml@12.0.1
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
jdk.unsupported@12.0.1

scatterplotterで直接使用するのは前述した3種類のモジュールですが、javafx.controlsとjavafx.fxmlが依存しているモジュールも芋づる式にインクルードされます。そのため、java.desktopモジュールなどがJREに含まれています。

後は、target\dependencyディレクトリにあるJavaFX以外のJARファイル群と、scatterplotterのJARファイルをscatterplotterディレクトリにコピーします。ここではどちらもscatterplotterディレクトリ直下にdependencyディレクトリを作成しして、そこにコピーしました。

最後に起動用のバッチファイルを用意しましょう。ここではscatterplotter.batとしました。内容は以下の通り。

@echo off
bin\java -cp dependency\* net.javainthebox.scatterplotter.Main

ここではWindows版のJREを作ったのでバッチファイルですが、Linuxであればシェルスクリプトファイルを作ります。

作成したjreディレクトリはこんな構成になってます。

C:\scatterplotter\scatterplotter>dir
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は 6868-701E です

 C:\scatterplotter\scatterplotter のディレクトリ

2019/07/05  21:37    <DIR>          .
2019/07/05  21:37    <DIR>          ..
2019/07/05  21:17    <DIR>          bin
2019/07/05  21:17    <DIR>          conf
2019/07/05  21:31    <DIR>          dependency
2019/07/05  21:17    <DIR>          include
2019/07/05  21:17    <DIR>          legal
2019/07/05  21:17    <DIR>          lib
2019/07/05  21:17               182 release
2019/07/05  21:33                75 scatterplotter.bat
               2 個のファイル                 257 バイト
               8 個のディレクトリ  868,741,357,568 バイトの空き領域

C:\scatterplotter\scatterplotter>dir dependency
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は 6868-701E です

 C:\scatterplotter\scatterplotter\dependency のディレクトリ

2019/07/05  21:31    <DIR>          .
2019/07/05  21:31    <DIR>          ..
2019/07/04  20:10           246,174 commons-beanutils-1.9.3.jar
2019/07/04  20:10           588,337 commons-collections-3.2.2.jar
2019/07/04  20:10           752,798 commons-collections4-4.2.jar
2019/07/04  20:10           501,879 commons-lang3-3.8.1.jar
2019/07/04  20:10            61,829 commons-logging-1.2.jar
2019/07/04  20:10         2,213,560 commons-math3-3.6.1.jar
2019/07/04  20:10           182,954 commons-text-1.3.jar
2019/07/04  20:10           170,348 opencsv-4.6.jar
2019/07/04  20:18             7,929 scatterplotter-1.0-SNAPSHOT.jar
               9 個のファイル           4,725,808 バイト
               2 個のディレクトリ  868,741,292,032 バイトの空き領域

C:\scatterplotter\scatterplotter>

後は、JREを作成したscatterplotterディレクトリをZIPかなにかでまとめてしまえば、配布用パッケージの完成です。

ちなみに、アプリケーションがモジュールになっていると、JavaFX Maven PluginでJREを作成できるのですが、JavaFXアプリケーションをモジュールにする意味ってほとんどないんですよね。なので、ここでは非モジュールアプリケーションをサンプルにして、jlinkを直接使用する方法を説明しました。