torutkのブログ

ソフトウェア・エンジニアのブログ

Project Jigsaw ことはじめ(その2)

Project Jigsaw ことはじめ - torutkのブログ の続きです。
今日は、JDK8 Jigsawプレビューが更新された件、および、2つの依存関係のあるモジュールを作成し、ライブラリ化、実行する件を紹介します。

JDK8 Jigsawプレビューの更新

JDK8 Jigsawプレビューが1か月ちょっとぶりに更新されました(2012/6/22付でb36→b42ベースに)。

展開しJavaのバージョンを表示させると次のとおりです。

$ java -version
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-jigsaw-nightly-h458-20120622-b42-b00)
Java HotSpot(TM) 64-Bit Server VM (build 24.0-b13, mixed mode)

モジュール・ライブラリにインストールしたモジュールを削除するためのサブコマンドremove(rm)がjmodに追加されています。その他の修正は把握していません(Changes一覧がないので・・・)。

$ jmod list -L lib
org.example.bravo@1.0
org.example.delta@1.0

$ jmod remove -L lib org.example.bravo@1.0

$ $ jmod list -L lib
org.example.delta@1.0

$

2つのモジュール(依存関係あり)の作成

今回は、依存関係を持つJigsawのモジュールを2つ作成し、モジュール・ライブラリにインストールして実行するところまで動かしてみます。

作成するモジュールは、

 org.example.bravo@1.0
         ↓
 org.example.delta@1.0

です。

今回作成するディレクトリ構造は次のとおりです。

2ndJigsaw
  +-- build
  +-- lib
  +-- src
        +-- org.example.bravo
        |     +-- module-info.java
        |     +-- org
        |           +-- example
        |                 +-- bravo
        |                       +-- HelloBravo.java
        +-- org.example.delta
              +-- module-info.java
              +-- org
                    +-- example
                          +-- delta
                                +-- HelloDelta.java
                                +-- impl
                                      + HelloDeltaImpl.java
org.example.deltaモジュールの作成

まずは依存される側から作成します。

src/org.example.delta/module-info.java

module org.example.delta @ 1.0 {
  exports org.example.delta;
}

他のモジュールに公開するのは、org.example.deltaパッケージのHelloDeltaで、org.example.delta.implパッケージ(HelloDeltaImpl)は他のモジュールには非公開となります。

src/org.example.delta/org/exaple/delta/HelloDelta.java

package org.example.delta;

import org.example.delta.impl.HelloDeltaImpl;

public class HelloDelta {
    public static String greet() {
	return HelloDeltaImpl.greet();
    }
}

src/org.example.delta/org/exaple/delta/impl/HelloDeltaImpl.java

package org.example.delta.impl;

public class HelloDeltaImpl {
    public static String greet() {
	return "Hello Delta";
    }
}

HelloDeltaクラスからHelloDeltaImplクラスを利用するためには、パッケージが異なるのでpublicなクラスである必要がありました。しかし、publicなクラスは他のどこからも参照できてしまいます。implパッケージはこのorg.example.deltaモジュールの中では自由に使用し、モジュールの外には隠蔽することが可能になります。

次に、このorg.example.deltaモジュールのコンパイルとモジュール・ライブラリへのインストールを行います。

$ javac -d ./build -modulepath ./build -sourcepath src \
`find ./src/org.example.delta -name "*.java"`
$
$ jmod create -L lib
$ jmod install -L lib ./build org.example.delta
$ jmod ls -L lib
org.example.delta@1.0
$

プログラムを修正して再度インストールするとき、同じモジュール名かつバージョン番号のものがモジュール・ライブラリに存在するとエラーになります。

$ jmod install -L lib ./build org.example.delta
I/O error: org.openjdk.jigsaw.ConfigurationException: module view org.example.delta@1.0 already installed
org.openjdk.jigsaw.cli.Command$Exception: I/O error: org.openjdk.jigsaw.ConfigurationException: module view org.example.delta@1.0
 already installed
        at org.openjdk.jigsaw.cli.Librarian$Install.go(Librarian.java:211)

このときは、いったん削除してからインストールします。

$ jmod rm -L lib org.example.delta@1.0
org.example.bravoモジュールの作成

先に作ったorg.example.deltaを使用するモジュールを作成します。

src/org.example.bravo/module-info.java

module org.example.bravo @ 1.0 {
    requires org.example.delta;
    class org.example.bravo.HelloBravo;
}

requires文で、使用する(依存する)モジュールを指定します。なお、バージョン番号を指定することもできます。この例では省略していますが、省略時は最新バージョンを使用するとあります。

また、プログラムの実行にあたってエントリポイントとなるmainを持つクラスを指定しています。

src/org.example.bravo/org/example/bravo/HelloBravo.java

package org.example.bravo;

import org.example.delta.HelloDelta;

public class HelloBravo {
    public static String greet() {
	return "hello Bravo, " + HelloDelta.greet();
    }

    public static void main(String[] args) {
	System.out.println(HelloBravo.greet());
    }
}

このorg.example.bravoモジュールのコンパイルとモジュール・ライブラリへのインストールを行います。

$ javac -d ./build -modulepath ./build -sourcepath src \
`find ./src/org.example.bravo -name "*.java"`
$
$ jmod install -L lib ./build org.example.bravo
$ jmod ls -L lib
org.example.bravo@1.0
org.example.delta@1.0
プログラムの実行
$ java -L lib -m org.example.bravo
hello Bravo, Hello Delta
Warning: CLASSPATH environment variable ignored when -m specified

$

モジュールで公開されないクラスを使用

さきのHelloBravoクラスの中からHelloDeltaImplを直接呼出し

src/org.example.bravo/org/example/bravo/HelloBravo.java

package org.example.bravo;

import org.example.delta.HelloDelta;
import org.example.delta.impl.HelloDeltaImpl;

public class HelloBravo {
    public static String greet() {
	return "hello Bravo, " + HelloDelta.greet();
    }

    public static void main(String[] args) {
	System.out.println(HelloBravo.greet());
	System.out.println(HelloDeltaImpl.greet());
    }
}

と、org.example.deltaモジュールで公開されていないクラスを使用するコードに修正します。

org.example.bravoを再度コンパイルしモジュール・ライブラリにインストールします。

$ javac -d ./build -modulepath ./build -sourcepath src \
`find ./src/org.example.bravo -name "*.java"`
$ jmod rm -L lib org.example.bravo@1.0
$ jmod install -L lib org.example.bravo
$

モジュールのインストールまでは特にエラーもなく進んでしまいました。

では、実行してみると、エラーが出ます。が、実行時エラーとなっています。

$ java -L lib -m org.example.bravo
hello Bravo, Hello Delta
Exception in thread "main" java.lang.NoClassDefFoundError: org/example/delta/impl/HelloDeltaImpl
        at org.example.bravo.HelloBravo.main(HelloBravo.java:13)
Caused by: java.lang.ClassNotFoundException: org.example.delta.impl.HelloDeltaImpl : requested by +org.example.bravo
        at org.openjdk.jigsaw.Loader.loadClass(Loader.java:116)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 1 more
Warning: CLASSPATH environment variable ignored when -m specified
$
公開されていないパッケージをrequire

次のパターンとして、org.example.bravoのモジュール定義でorg.example.delta.implを指定してみます。

src/org.example.bravo/module-info.java

module org.example.bravo @ 1.0 {
    requires org.example.delta;
    requires org.example.delta.impl;
    class org.example.bravo.HelloBravo;
}

コンパイルします。

$ javac -d ./build -modulepath ./build -sourcepath src \
`find ./src/org.example.bravo -name "*.java"`
エラー: Cannot resolve module dependencies using Jigsaw module resolver
  org.example.bravo@=1.0: Cannot resolve
エラー1個

今度は、コンパイル時に未解決の依存関係があるとのエラーとなりました。

補足事項

バージョン番号を指定しないmoduleでmainのエントリポイントを定義しているものがjmod installでエラーになる
$ jmod install -L lib build org.example.alfa
$ jmod install -L lib build org.example.alfa
Exception in thread "main" java.lang.NullPointerException
        at java.lang.module.ModuleId.toQuery(ModuleId.java:102)
        at org.openjdk.jigsaw.SimpleLibrary.configureWhileModuleDirectoryLocked(SimpleLibrary.java:1691)
        at org.openjdk.jigsaw.SimpleLibrary.installFromManifests(SimpleLibrary.java:1125)
        at org.openjdk.jigsaw.cli.Librarian$Install.go(Librarian.java:207)
        at org.openjdk.jigsaw.cli.Librarian$Install.go(Librarian.java:186)
        at org.openjdk.jigsaw.cli.Command.run(Command.java:100)
        at org.openjdk.jigsaw.cli.Librarian.exec(Librarian.java:693)
        at org.openjdk.jigsaw.cli.Librarian.run(Librarian.java:591)
        at org.openjdk.jigsaw.cli.Librarian.main(Librarian.java:717)

エントリポイントを指定していないものについてはエラーになりません。

依存関係上、削除すると影響のあるモジュールがある場合、jmod removeはエラーになる
$ jmod ls -L lib
org.example.bravo@1.0
org.example.delta@1.0

$ jmod rm -L lib org.example.delta@1.0
org.openjdk.jigsaw.ConfigurationException: org.example.delta@1.0: being used
 by org.example.bravo@1.0
   :

と、他のモジュールから使用されているためエラーとなりました。