JavaFXアプリケーションでJava 9のモジュールを使うときの注意点
このエントリは JavaFX Advent Calendar 2017 の 1 日目のエントリです。最初を飾るのは初めてです。まだ参加者が少ないので、みんな参加してね!
いよいよJava SE 9がリリースされました。やはり9の注目はJigsawことモジュールシステムですね。特にJavaFXにとってはjavapackagerを用いてパッケージングした際の配布サイズを小さくすることができるので、特に重要ですね。
ですが、JavaFXアプリケーションでモジュールシステムを利用すると早速引っ掛かるポイントがあります。それはFXMLです。
次のような FXML を使った簡単なアプリケーションを見てみましょう。
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.StackPane?> <BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller"> <center> <StackPane prefHeight="150.0" prefWidth="200.0" BorderPane.alignment="CENTER"> <children> <Label text="This is Label." fx:id="label" /> </children> </StackPane> </center> <bottom> <HBox alignment="CENTER" BorderPane.alignment="CENTER"> <children> <Button alignment="CENTER" mnemonicParsing="false" text="Button" onAction="#click" /> </children> <BorderPane.margin> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> </BorderPane.margin> </HBox> </bottom> </BorderPane>
package sample; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Label; public class Controller { @FXML private Label label; @FXML void click(ActionEvent event) { label.setText("You clicked!"); } }
FXMLのいいところとして、ビューXMLとペアになるControllerクラスに対して、XML側に記述したコンポーネントへのアクセスをDIを使って提供している点ですね。publicなフィールドだけでなく、package privateやprivateなフィールドとしての宣言も可能で、その場合は @FXML
アノテーションを付与すると、インスタンスがインジェクトされるようになります。イベント実行メソッドも同様に @FXML
アノテーションでバインドできます。
ですが、この @FXML
アノテーションを使う場合は注意が必要です。次のように module-info.java
を作ってみましょう。
module fxml9sample { requires javafx.controls; requires javafx.fxml; exports sample; }
すると次のような例外が飛びます。 javafx.fxml
モジュールに属するクラスがこのControllerクラスに対してリフレクションによるアクセスを試みるためです。
Exception in Application start method java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) (中略) Caused by: javafx.fxml.LoadException: fxml9sample/out/production/fxml9sample/sample/sample.fxml:15 at javafx.fxml/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2603) (中略) Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private javafx.scene.control.Label sample.Controller.label accessible: module fxml9sample does not "opens sample" to module javafx.fxml at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281) at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176) at java.base/java.lang.reflect.Field.setAccessible(Field.java:170) at javafx.fxml/javafx.fxml.FXMLLoader$ControllerAccessor.addAccessibleFields(FXMLLoader.java:3495) at javafx.fxml/javafx.fxml.FXMLLoader$ControllerAccessor.access$3900(FXMLLoader.java:3344) at javafx.fxml/javafx.fxml.FXMLLoader$ControllerAccessor$1.run(FXMLLoader.java:3460) at javafx.fxml/javafx.fxml.FXMLLoader$ControllerAccessor$1.run(FXMLLoader.java:3456) at java.base/java.security.AccessController.doPrivileged(Native Method) at javafx.fxml/javafx.fxml.FXMLLoader$ControllerAccessor.addAccessibleMembers(FXMLLoader.java:3455) at javafx.fxml/javafx.fxml.FXMLLoader$ControllerAccessor.getControllerFields(FXMLLoader.java:3394) at javafx.fxml/javafx.fxml.FXMLLoader.injectFields(FXMLLoader.java:1170) at javafx.fxml/javafx.fxml.FXMLLoader.access$1600(FXMLLoader.java:105) at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processValue(FXMLLoader.java:865) at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:759) at javafx.fxml/javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2722) at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552) ... 17 more Exception running application sample.Main
これを防ぐためには、FXMLと対になるControllerクラスが属しているパッケージに対して javafx.fxml
モジュールからのリフレクションによるアクセスを許可する必要があります。そのために次のように opens
文を使って javafx.fxml
モジュールに対してアクセスを許可する必要があります。
module fxml9sample {
requires javafx.controls;
requires javafx.fxml;
exports sample;
opens sample to javafx.fxml; // <- これを追加
}
既存のJavaFXアプリケーションをJava 9に移行する際に意外と引っ掛かるポイントなので注意してください。
というわけで、1 日目のエントリは小ネタでした。19 日にも書く予定ですが、そちらはもう少し大きなネタにする予定です。
明日は...執筆時点でまだいない! 誰か書いてー。