OpenJFX + OpenJDK で JavaFX を動かす
今までOracle JDKでJavaFXを使っていたみなさま、こんにちは。
なんと、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からできます。
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
これで、以下のようなウィンドウが表示されるはずです。