OpenJFX + OpenJDK で JavaFX を動かす

今までOracle JDKJavaFXを使っていたみなさま、こんにちは。

なんと、Java SE 11からOracle JDKは有償化ですよ!しかたないので、OpenJDKを使わざるをえないと思われている方も多いと思います。

ところが、OpenJDKにはJavaFXが含まれていないのです!!

「えっ、OpenJFXってOpenJDKのプロジェクトじゃないの?」と思われるかもしれません。確かにOpenJFXはOpenJDKのプロジェクトなのです。でも、OpenJDKのプロジェクトがJava SEのRIであるOpenJDKに含まれるというわけではありません。

OpenJFXも、Java SEのRIであるところのOpenJDKには含まれていないのです。

とはいっても、OpenJFXを自分でビルドして使うのはハードルが高すぎますね。そうしたら、OpenJFXのビルドを公開してくれるようになりました!

これで、OpenJFXとOpenJDKでJavaFXを使えます!!

ところが、結論を先にいうと、「OpenJFX + OpenJDKはモジュールではないアプリケーションでも、モジュールを指定しないと動きません」です。

以下に解説しますが、手っ取り早くやり方を知りたい人は途中をすっ飛ばして、「OpenJFXでのJavaFXの起動設定」を読んでください。

OpenJDKとOpenJFXのセットアップ

OpenJFXのダウンロードは下のURLからできます。

OpenJFX Early-Access Builds

http://jdk.java.net はOpenJDKも公開しています。今回は、まだリリース前ですがOpenJDK 11のEaryly Accessを試してみましょう。使用したのは、5月25日に公開されたjdk-11-ea+15です。

OpenJDKはインストーラーは含まれていないので、自分でtar.gzのファイルを展開して、binにパスを通しておきます。

次にOpenJFXです。OpenJFXは5月9日に公開されたopenjfx-11-ea+13を使用しました。

ダウンロードしたopenjfx-11-ea+13_windows-x64_bin-sdk.zipを展開すると、bin、legal、libの3つのディレクトリが含まれていることが分かります。binディレクトリにはネイティブライブラリ(Windowsの場合はDLL)が配置されているので、ここにもパスを通しておきます。

libディレクトリにはJARファイルがモジュールごとに配置されてます。

とりあえず、JavaFXのサンプルを実行してみる

では、JavaFXのプログラムをビルドして、実行してみましょう。

今回のサンプルはこちら。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class FxSample extends Application {
    public void start(Stage stage) {
        Button button = new Button("OK");
        button.setOnAction(e -> System.out.println("Clicked!"));

        Scene scene = new Scene(button, 300, 200);
        stage.setScene(scene);

        stage.setTitle("FxSample");
        stage.show();
    }

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

ボタンを表示して、ボタンをクリックすると"Clicked!"と標準出力に出力するだけのプログラムです。

もちろん、モジュールにはしません。

では、さっそくコンパイルして、実行してみましょう。

C:\javafx-sdk-11>java -version
openjdk version "11-ea" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11-ea+15)
OpenJDK 64-Bit Server VM 18.9 (build 11-ea+15, mixed mode)

C:\javafx-sdk-11>javac -cp lib\* FxSample.java

C:\javafx-sdk-11>java -cp lib\*;. FxSample
エラー: JavaFXランタイム・コンポーネントが不足しており、このアプリケーションの実行に必要です

C:\javafx-sdk-11>

コンパイルはできたのですが、実行ができません!

しかも、このエラーメッセージ、全然意味が分かりません。ランタイムコンポーネントって何なんでしょう?

はじめはDLLが読み込まれていないのかと思ったのですが、どうやら違ったようです。

そこで、何が起こっているのか知るためにverboseオプションをつけて起動してみました。

C:\javafx-sdk-11>java -verbose -cp lib\*;. FxSample
[0.008s][info][class,load] opened: C:\jdk-11\lib\modules
[0.018s][info][class,load] java.lang.Object source: jrt:/java.base
[0.019s][info][class,load] java.io.Serializable source: jrt:/java.base
[0.019s][info][class,load] java.lang.Comparable source: jrt:/java.base
[0.023s][info][class,load] java.lang.CharSequence source: jrt:/java.base
[0.024s][info][class,load] java.lang.String source: jrt:/java.base
[0.025s][info][class,load] java.lang.reflect.AnnotatedElement source: jrt:/java.base
[0.025s][info][class,load] java.lang.reflect.GenericDeclaration source: jrt:/java.base
[0.026s][info][class,load] java.lang.reflect.Type source: jrt:/java.base
[0.027s][info][class,load] java.lang.Class source: jrt:/java.base
     <<以下、長くなるので省略>>

verboseオプションをつけると、どのクラスがロードされているのが分かります。たとえば、一番初めにロードされたのがjava.baseモジュールに含まれるjava.lang.Objectクラスだということが分かります。

ロードされたクラスをチェックしてみると、なんとJavaFXのクラスがまったくロードされていないのでした。

それじゃ、実行できるわけがないですね。

しかし、クラスパスは指定しているのに、なぜクラスがロードされていないのでしょう?

ここで、思い浮かぶのがJava SE 10でのJAXBなどJava EE系のモジュールの扱いです。これらのモジュールは標準のモジュールパスからは外されているので、使用するときにはモジュールでないアプリケーションであっても--add-modulesで指定する必要があるのです。

もしかしたら、ここで起こっていることも同じことなのかもしれません。

だとしたら、解決は簡単で、モジュールパスにlibを追加して、ロードするモジュールを指定すればいいのです。

OpenJFXでのJavaFXの起動設定

では、実際に起動設定を見ていきましょう。

モジュールパスは--module-pathオプションもしくは-pオプションで指定します。モジュールパスで指定するのはモジュールが配置されているディレクトリです。

モジュール化したアプリケーションであれば、どのモジュールを使用するかはmodule-infoに記述します。

しかし、モジュールではないアプリケーションでは、--add-modulesオプションで指定する必要があります。

どのモジュールを使用しているかはjdepsコマンドで調べることができます。

C:\javafx-sdk-11>jdeps --module-path lib -s FxSample.class
FxSample.class -> java.base
FxSample.class -> javafx.base
FxSample.class -> javafx.controls
FxSample.class -> javafx.graphics
javafx.base -> java.base
javafx.base -> java.desktop
javafx.controls -> java.base
javafx.controls -> javafx.base
javafx.controls -> javafx.graphics
    <<以下、省略>>

jdepsコマンドでは、モジュールパスの指定に-pは使用できないので、--module-pathを使用します。もう1つの-sオプションはシンプル表示のためのオプションです。

この結果を見ると、FxSampleクラスはjava.base、javafx.base、javafx.controls、javafx.graphicsの4つのモジュールを使用していることが分かります。

しかし、java.baseモジュールはデフォルトでロードされるので、指定する必要はありません。

また、javafx.controlsモジュールはjavafx.baseモジュールとjavafx.graphicsモジュールを使用しています。このため、javafx.controlsだけを指定すれば、javafx.baseモジュールとjavafx.graphicsモジュールは自動的にロードされます。

この結果、--add-modulesオプションで指定するのはjavafx.controlsモジュールだけでよいことが分かりました。

では、実行してみましょう。

C:\javafx-sdk-11>java -p lib --add-modules javafx.controls FxSample

これで、以下のようなウィンドウが表示されるはずです。