非モジュールJavaFXのアプリをJlinkで配布可能パッケージにする

このエントリーはJavaFX Advent Calendarの24日目です。
qiita.com

Java SE 9からjlinkコマンドが使えるようになり、カスタムJREを作れるようになりました。

ところが、jlinkを使うにはモジュールアプリケーションでないとダメだというような間違った言説が流れているような気がします。

jlinkで作るカスタムJREは非モジュールアプリケーションでも可能なんです!!!!

ということで、JavaFXの非モジュールアプリでカスタムJREを作ってみましょう。

サンプルは超簡単なラベルを表示するだけのものです。

package net.javainthebox.hello;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class Hello extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        var label = new Label("Hello, JavaFX!");
        label.setAlignment(Pos.CENTER);
        var scene = new Scene(label, 200, 50);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String... args) {
        launch(args);
    }

}

これをMavenでビルドするのは、以下のエントリーでも書いた通り(pom.xmlに記述した各種バージョンは新しいものにしてくださいね)。

skrb.hatenablog.com

もちろん、自分でビルドするのでも全然かまいません。

ここでは、C:\hello\srcにソースファイルがあるとします。また、JavaFX SDKは"C:\Program Files\Java\javafx-sdk-11.0.1"に配置してあります。

C:\hello\src>javac --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\lib" --add-modules javafx.controls -d ..\bin net\javainthebox\hello\Hello.java

C:\hello\src>cd ..

C:\hello>jar --create --file hello.jar -C bin .

これで、helloディレクトリ直下にhello.jarができました。試しに実行してみましょう。

C:\hello>java --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\lib" --add-modules javafx.controls -cp hello.jar net.javainthebox.hello.Hello

これでラベルだけのフレームが表示されるはずです。

カスタムJREを作成する

jlinkを使う前にjdepsを使わなくてはいけないような風潮がありますが、使っているモジュール(ここではJavaFXのモジュール)が明らかであれば、jdepsを使う必要はありません。

また、非モジュールアプリケーションの場合、jdepsの--print-module-depsや--list-depsオプションは使用できません(JDKのモジュールしか表示されません)。

もし、jdepsを使うのであれば、オプションなしに使用します(もしくはサマリー表示の-s)。

C:\hello>jdeps --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\lib" -s hello.jar
hello.jar
hello.jar -> java.base
hello.jar -> javafx.controls
hello.jar -> javafx.graphics
javafx.base -> java.base
javafx.base -> java.desktop
javafx.controls -> java.base
javafx.controls -> javafx.base
javafx.controls -> javafx.graphics
javafx.fxml -> java.base
javafx.fxml -> java.scripting
javafx.fxml -> java.xml
javafx.fxml -> javafx.base
javafx.fxml -> javafx.graphics
javafx.graphics -> java.base
javafx.graphics -> java.desktop
javafx.graphics -> java.xml
javafx.graphics -> javafx.base
javafx.graphics -> jdk.unsupported
javafx.media -> JDK removed internal API
javafx.media -> java.base
javafx.media -> javafx.base
javafx.media -> javafx.graphics
javafx.swing -> java.base
javafx.swing -> java.datatransfer
javafx.swing -> java.desktop
javafx.swing -> javafx.base
javafx.swing -> javafx.graphics
javafx.swing -> jdk.unsupported.desktop
javafx.swt -> java.base
javafx.swt -> javafx.base
javafx.swt -> javafx.graphics
javafx.swt -> 見つかりません
javafx.web -> java.base
javafx.web -> java.desktop
javafx.web -> java.xml
javafx.web -> javafx.base
javafx.web -> javafx.controls
javafx.web -> javafx.graphics
javafx.web -> javafx.media
javafx.web -> jdk.jsobject
javafx.web -> jdk.xml.dom

ずらずらと出てきますが、重要なのは上の方の3行。hello.jarが直接使用しているのはjava.base、javafx.controls、javafx.graphicsモジュールの3つだということ。javafx.graphicsモジュールはjavafx.controlsモジュールが依存しているので、実質はjava.baseとjavafx.controlsだけでOKです。

これが分かればjlinkでJREが作れます。というか、javacやjavaで実行するときに--add-modulesでjavafx.controlsを加えているので、jdepsなど使わなくても何を使っているかは自明ですけどね。

なお、jlinkでJREを作る時、JavaFX SDKのJARファイルよりJMODファイルを使う方がおススメです。JARでもいいのですが、JARファイルにはネイティブライブラリ(DLLやSO)が含まれていないので、別途コピーする必要があります。

JMODファイルであればネイティブライブラリも含んでいるので、簡単です。ここではJMODファイルは"C:\Program Files\Java\javafx-sdk-11.0.1\mods"に置いてあるとします。

C:\hello>jlink --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\mods" --add-modules java.base,javafx.controls --compress=2 --output jre

これで、jreディレクトリにJREができました。

お分かりだろうとは思いますが、このJREにはアプリケーションのJARファイルは含まれていません。カスタムJREに含められるのはあくまでもモジュールだけです。この場合は標準のモジュールとJavaFXのモジュールで構成されています。

他にモジュールで提供されているライブラリを使用しているのであれば、それもJREに含めることができます。

逆にいうと、非モジュールのライブラリやアプリケーションJARはJREには含まれていないので、実行にはクラスパスで指定する必要があります。

では、実行してみましょう。ここでは、クラスパスでhello.jarを指定しています。

C:\hello>jre\bin\java -cp hello.jar net.javainthebox.hello.Hello

JREのモジュールにjavafx.controlsなどが含まれているので、--module-pathや--add-modulesなどのオプションは必要ありません。

では、配布可能にするためにjreディレクトリにhello.jarもコピーしておきましょう。

C:\hello>copy hello.jar jre
        1 個のファイルをコピーしました。

後は、実行に便利なようにバッチファイル、もしくはシェルスクリプトを作っておきましょう。たとえば、hello.batを作ったとします。ファイルの内容は以下の1行だけ。

bin\java -cp hello.jar net.javainthebox.hello.Hello

試しに、hello.batで正しく実行できるかどうか試しておいてください。

これで、jreディレクトリをそのまま配布することができます(ZIPファイルにするとか、ディレクトリ名を変えるとかはご自由に)。