ControlsFX 準備編 その2
このEntryは JavaFX Advent Calendar の最終日のエントリーです。
昨日は id:yumix_h さんの JavaFXのHTMLEditorの機能と限界 でした。
今日もControlsFXの紹介です。
23日のエントリー でモジュールではないアプリケーションからの使い方を紹介しました。
今日はモジュールアプリケーションからControlsFXを使用します。
でも、その前にProject Jigsawのモジュールについて簡単に解説しましょう。ここでは、JavaFXでの例で示していますけど、他のモジュールアプリケーションでも同じようなことはあるはずです。
モジュール
module-info.javaがあればモジュールです。そして、module-info.javaには依存性(requires文)、公開範囲(exports文)を記述します。
基本的にはこれだけですが、FXMLを使用している場合、コントローラクラスがリフレクションでアクセスされるため、それを許可するopens文も欠かせません。
では、前回のサンプルがどのようなモジュールに依存しているか調べてみましょう。そのためには、jdepsコマンドを使用します。
C:\controlsfx\controlsfxdemo>jdeps -cp lib\controlsfx-9.0.0.jar -s dist\controlsfxdemo.jar controlsfxdemo.jar -> lib\controlsfx-9.0.0.jar controlsfxdemo.jar -> java.base controlsfxdemo.jar -> javafx.fxml controlsfxdemo.jar -> javafx.graphics
java.base、javafx.fxml、javafx.graphicsの3つのモジュールに依存していることが分かります。ただし、java.baseだけはmodule-info.javaに記述する必要はありません。
公開するのは、net.javainthebox.fxパッケージなので、これを含めてmodule-info.javaを記述します。
ここでは、モジュール名をnet.javainthebox.controlsfxdemoとします。
module net.javainthebox.controlsfxdemo { requires javafx.fxml; requires javafx.graphics; exports net.javainthebox.fx; opens net.javainthebox.fx to javafx.fxml; }
しかし、これだけではビルド、実行させることができません。もちろん、ControlsFXの扱いのためです。
controlsfx-9.0.0.jarはモジュールではないので、注意する必要があります。
モジュールの種類
実をいうと、モジュールには3種類あります。
module-info.javaが存在するのが普通のモジュールですが、それ以外に2種類のモジュールがあります。
- 通常のモジュール
- 自動モジュール (Automatic Module)
- 名前なしモジュール (Unnamded Module)
Automatic ModuleもUnnamed Moduleもmodule-info.javaがないモジュールです。これらの違いはモジュールが直接依存しているのか、間接的に依存しているかという違いです。
- モジュールが直接アクセスするJARファイル -> Automatic Module
- モジュールがアクセスするJARファイルがアクセスするJARファイル -> Unnamed Module
ということになります。
moudle-info.javaを含むモジュールはクラスパスを使用することができず、すべてモジュールパスで扱います。モジュールでないJARファイルもモジュールパスで指定したディレクトリにおけば、モジュールとして扱われるのです。
そして、そのモジュールとして扱われるJARファイルをAutomatic Moduleと呼びます。
モジュールパスでアクセスするということは、module-info.javaに記述しなくてはいけません。そのため、Automatic Moduleとして扱われるJARファイルはモジュール名が必要になります。
この場合、マニフェストファイルMANIFEST.MFのAutomatic-Module-Name属性で指定された名前を使用します。Automatic-Module-Name属性が指定されていない場合、JARファイルの名前がモジュール名として使用されます(バージョン表記などが含まれていても、それは取り除かれます)。
controlsfx-9.0.0.jarファイル場合、Automatic-Module-Name属性は記述されていません。そのため、ファイル名からcontrolsfxがモジュール名として使用されます。
さらに、Automatic Moduleが使用するJARファイルがUnnamed Moduleとして扱われます。これは名前がないので、今までと同じようにクラスパスで指定します。
では、module-info.javaにAutomatic Moduleのcontrolsfxモジュールを追加してみます。
module net.javainthebox.controlsfxdemo { requires javafx.fxml; requires javafx.graphics; requires controlsfx; exports net.javainthebox.fx; opens net.javainthebox.fx to javafx.fxml; }
これで、ビルドはできるようになります。しかし、実行してみると...
C:\controlsfx\controlsfxdemo>java -p dist;lib -m net.javainthebox.controlsfxdemo/net.javainthebox.fx.ControlsFXDemo Exception in Application start method java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.base/java.lang.reflect.Method.invoke(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(Unknown Source) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.base/java.lang.reflect.Method.invoke(Unknown Source) at java.base/sun.launcher.LauncherHelper$FXHelper.main(Unknown Source) Caused by: java.lang.RuntimeException: Exception in Application start method at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source) Caused by: java.lang.NoClassDefFoundError: javafx/scene/control/TextField at java.base/java.lang.ClassLoader.defineClass1(Native Method) at java.base/java.lang.ClassLoader.defineClass(Unknown Source) at java.base/java.lang.ClassLoader.defineClass(Unknown Source) at java.base/java.security.SecureClassLoader.defineClass(Unknown Source) at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(Unknown Source) at java.base/jdk.internal.loader.BuiltinClassLoader.findClassInModuleOrNull(Unknown Source) at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(Unknown Source) at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source) at java.base/java.lang.ClassLoader.loadClass(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadTypeForPackage(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadType(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.importClass(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.processImport(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.processProcessingInstruction(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(Unknown Source) at javafx.fxml/javafx.fxml.FXMLLoader.load(Unknown Source) at net.javainthebox.controlsfxdemo/net.javainthebox.fx.ControlsFXDemo.start(ControlsFXDemo.java:14) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$11(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(Unknown Source) at java.base/java.security.AccessController.doPrivileged(Native Method) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(Unknown Source) at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source) at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(Unknown Source) ... 1 more Caused by: java.lang.ClassNotFoundException: javafx.scene.control.TextField at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source) at java.base/java.lang.ClassLoader.loadClass(Unknown Source) ... 33 more Exception running application net.javainthebox.fx.ControlsFXDemo C:\controlsfx\controlsfxdemo>
javafx.scene.control.TextFieldクラスが見つからないという例外が起こってしまいました。
これはcontrolsfxがJavaFXのモジュールに依存しているためです。しかし、Automatic Moduleでは依存性が記述できないため、TextFieldクラスを含むモジュールをロードできないためです。
その場合、controlsfxが依存するモジュールも一緒にmodule-info.javaに記述します(起動時オプションで指定する方法もあります)。
controlsfxが依存しているモジュールを調べるために、jdepsコマンドを使用してみましょう。
C:\controlsfx\controlsfxdemo>jdeps -s lib\controlsfx-9.0.0.jar controlsfx-9.0.0.jar -> java.base controlsfx-9.0.0.jar -> java.desktop controlsfx-9.0.0.jar -> javafx.base controlsfx-9.0.0.jar -> javafx.controls controlsfx-9.0.0.jar -> javafx.graphics controlsfx-9.0.0.jar -> javafx.media controlsfx-9.0.0.jar -> javafx.web
ここで表示されたすべてのモジュールを記述する必要はありません。実際に使用しているモジュール、ここではTextFieldクラスが含まれるjavafx.controlsモジュールだけを追加すればOKです。
module net.javainthebox.controlsfxdemo { requires javafx.controls; requires javafx.fxml; requires javafx.graphics; requires controlsfx; exports net.javainthebox.fx; opens net.javainthebox.fx to javafx.fxml; }
これでモジュールのアプリケーションからControlsFXを使用することができます。
なお、NetBeansではビルドはできるものの、実行はエラーになってしまいます。いろいろ試してみたのですが、結局実行できませんでした。
なので、実行だけはコマンドラインから行っています。
準備編だけで長くなってしまったので、個々のコントロールについてはまた後日。