2015/12/13

JRE をカスタマイズ - jlink

このエントリーをはてなブックマークに追加

この記事は、Java Advent Calendar 2015 の 13 日目の記事です。

昨日は @cero_t さんのStream APIをつくろう でした。明日は opengl_8080 さんです。

 

JavaOne にいってから、Project Jigsaw で遊ぶことが多くなりました。で、モジュールを作った後の話を紹介します。ちょうど、JavaFX in the Box の方の このエントリー の後の話題のようなものです。

このエントリーでは JavaFX のサンプルのモジュールの依存性を調べたのですが、せっかく依存性を調べたのですから、モジュールを作ってみましょう。

サンプルはこれです。

package fxdemo;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class FXDemo extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Label label = new Label("Label");
        label.setFont(Font.font(24));
        
        StackPane root = new StackPane(label);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setTitle("FXDemo");
        stage.show();
    }
    
    public static void main(String... args) {
        launch(args);
    }
}

このサンプルが依存しているのは、java.base モジュール、javafx.controls モジュール、そして javafx.graphics モジュールです。ですので、module-info.java は次のようにしました。

module fxdemo {
    requires javafx.controls;
    requires javafx.graphics;
}

では、コンパイルして、モジュールを作ってみましょう。ソースは src ディレクトリ、クラスは bin ディレクトリ、モジュールは mods ディレクトリに置くとしましょう。

C:\fxdemo>javac -d bin src\module-info.java src\fxdemo\FXDemo.java

C:\fxdemo>jar --create --file mods\fxdemo.jar --module-version 1.0 -C bin .

これで、モジュールができました。JAR ファイルなので、これだけだとモジュールかどうかよく分からないのが玉にキズ。

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

C:\fxdemo>java -mp mods -m fxdemo/fxdemo.FXDemo
Exception in Application constructor
Exception in thread "main" java.lang.RuntimeException: Unable to construct Appli
cation instance: class fxdemo.FXDemo
        at com.sun.javafx.application.LauncherImpl.launchApplication1(javafx.gra
phics@9-ea/LauncherImpl.java:926)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$138(
javafx.graphics@9-ea/LauncherImpl.java:220)
        at java.lang.Thread.run(java.base@9-ea/Thread.java:747)
Caused by: java.lang.IllegalAccessException: class com.sun.javafx.application.La
uncherImpl (in module javafx.graphics) cannot access class fxdemo.FXDemo (in mod
ule fxdemo) because module fxdemo does not export fxdemo to module javafx.graphi
cs
        at sun.reflect.Reflection.throwIllegalAccessException(java.base@9-ea/Ref
lection.java:452)
        at sun.reflect.Reflection.ensureMemberAccess(java.base@9-ea/Reflection.j
ava:135)
        at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(java.base@9-
ea/AccessibleObject.java:370)
        at java.lang.reflect.AccessibleObject.checkAccess(java.base@9-ea/Accessi
bleObject.java:362)
        at java.lang.reflect.Constructor.newInstance(java.base@9-ea/Constructor.
java:435)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$144
(javafx.graphics@9-ea/LauncherImpl.java:838)
        at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$158(javafx.
graphics@9-ea/PlatformImpl.java:351)
        at com.sun.javafx.application.PlatformImpl.lambda$null$156(javafx.graphi
cs@9-ea/PlatformImpl.java:320)
        at java.security.AccessController.doPrivileged(java.base@9-ea/Native Met
hod)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$157(javafx.gr
aphics@9-ea/PlatformImpl.java:319)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(javafx.graphics@9-e
a/InvokeLaterDispatcher.java:96)
        at com.sun.glass.ui.win.WinApplication._runLoop(javafx.graphics@9-ea/Nat
ive Method)
        at com.sun.glass.ui.win.WinApplication.lambda$null$130(javafx.graphics@9
-ea/WinApplication.java:191)
        ... 1 more

C:\fxdemo>

あれ、動かない。

まぁ、理由は簡単で、module-info.java に exports の項を書かなかったためです。このサンプルは外部から使うわけではないと思ったわけですが、実行するということは main メソッドを外部から呼ぶことになるため、exports が書いてないと実行できないのです。

ということで、module-info.java を次のように書きかえました。

module fxdemo {
    requires javafx.controls;
    requires javafx.graphics;

    exports fxdemo;
}

これで、同じようにコンパイルして、モジュールを作ったら、無事に実行できました。

Jigsaw で実行する場合は、-modulepath もしくは -mp でモジュールがおいてあるディレクトリを指定し、-m でメインクラスを指定します。この時、[モジュール名]/[クラス名] のようにモジュールとクラス名を / で区切って併記するようにします。

さて、これでモジュールができたので、次にこのサンプルのモジュールと最小限のモジュールを含む JRE を作ってみましょう。

それをやるには jlink コマンドを使用します。

C:\fxdemo>jlink --modulepath mods;"c:\Program Files\Java\jdk-9\jmods" --addmods
fxdemo --output fxdemo

オプションはだいたい分かると思いますが、--modulepath でモジュールのディレクトリを指定します。サンプルのモジュールだけでなく、JDK のモジュールの場所も指定しておきます。--addmods が追加するモジュールです。javafx.controls モジュールなどを追加しないのは、依存性の記述から勝手にやってくれるからです。

そして、fxdemo ディレクトリにイメージを作成します。このディレクトリには bin、conf、lib のディレクトリを作成します。

bin ディレクトリには java コマンドがあるので、どういうモジュールがあるか調べてみましょう。

C:\fxdemo\fxdemo\bin>java -listmods
fxdemo@1.0
java.base@9-ea
java.datatransfer@9-ea
java.desktop@9-ea
java.instrument@9-ea
java.logging@9-ea
java.management@9-ea
java.naming@9-ea
java.prefs@9-ea
java.rmi@9-ea
java.security.sasl@9-ea
java.xml@9-ea
javafx.base@9-ea
javafx.controls@9-ea
javafx.graphics@9-ea
jdk.jfr@9-ea
jdk.vm.ci@9-ea

javafx.controls モジュールなどの依存性も解決することで、必要最低限のモジュールを導入した JRE を作成することができました!