torutkのブログ

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

Project Jigsaw ことはじめ

注)Jigsawは、Java SE 8には間に合わず、その後も仕様検討・開発が継続され、Java SE 9に導入されることになりました。すでに本記事は完全に古い内容となっています。


Java SE 8で導入予定のモジュールシステムが、Project Jigsawです。JavaでJigsawという名称を聞くとw3cのHTTPサーバーを連想してしまう人もいるかと思いますが*1、それとは無関係のモジュール管理の仕組みです。ここでいうモジュールとはJARファイルの類です。

JavaOneなどでこのProject Jigsawの解説はされているのですが、目で見ているだけではピンと来なく、実際に手で動かしてみないとと思っていたら、先月にJigsawのプレビュー版が公開されました。

以下からJigsaw機能入りJDK8プレビュー版*2がダウンロードできます。

ダウンロードの種類に、OSとは別に

  • JDK module image
  • jdk base image + jmod packages

と2種類が選べます。

とりあえず、自分で作成するプログラムをProject Jigsawの機能でモジュール化したい、というときは前者を、JDKの機能を取捨選択して組み合わせて実行してみたい、というときは後者を入手して動かすとよいかと思います。

ここでは、前者を使って簡単なプログラム(伝統にのっとりHello world)をProject Jigsawの機能でモジュール化して実行するところまでを試してみます。

JDK 1.8 Jigsaw Previewのインストール(Windows

上述のURLから、"JDK module image"のzipファイルをダウンロードします。本日時点でダウンロードしたファイル名(Windows 64bit版)は次のとおりです。

任意の場所に展開します。
試した例では、C:\Program Files\Java\の下に展開し、ディレクトリ名をjdk-module-imageからjdk1.8.0_Jigsawに変更しました。

続いて、環境変数PATHをC:\Program Files\Java\jdk1.8.0_Jigsaw\binに通しておきます。

プログラムを作る

モジュール名の決定

まず、モジュール構成を決めます。今回作るHelloプログラムは1つのモジュールとします。モジュール名は通常代表パッケージ名に合わせます。今回は、パッケージ名を

package org.example.alfa;

とし、よってモジュール名もorg.example.alfaとします。

ソースディレクトリ構成の作成

ソースファイルのディレクトリは、今までのやり方では次のようになります(一例)。

1stJigsaw
 +-- src
      +-- org
           +-- example
                +-- alfa
                     +-- HelloAlfa.java

Jigsawでは、モジュール毎にディレクトリを分けるので、

1stJigsaw
 +-- src
      +-- org.example.alfa
           +-- module-info.java
           +-- org
                +-- example
                     +-- alfa
                          +-- HelloAlfa.java

のように、ソースディレクトリの基点の下にモジュール名のディレクトリを設け、その下にmodule-info.javaというモジュール情報を定義するファイルと、従来どおりのパッケージ名にちなんだディレクトリを用意します。

Helloプログラム

エントリポイントとなるmainメソッドを持つクラス(HelloAlfa)を作成します。

package org.example.alfa;

public class HelloAlfa {

    public static String greet() {
	return "Hello, Alfa";
    }

    public static void main(String[] args) {
	System.out.println(HelloAlfa.greet());
    }
}
モジュール定義ファイル

モジュールを定義するため、module-info.javaというファイル名規定のソースファイルを作成します。なお、このファイルはパッケージのルートにあたるディレクトリに置く必要があります。

ということで、複数のモジュールを1つのパッケージツリー上に作ることができないため、モジュール毎にディレクトリを分けるという構成になるのだと思います。

module org.example.alfa @ 1.0 {
    class org.example.alfa.HelloAlfa;
}
  • モジュール名org.example.alfa
  • モジュールのバージョンを1.0
  • 実行可能なモジュールとして定義し、エントリポイント(mainメソッド)を持つクラスorg.example.alfa.HelloAlfaを指定

という内容です。バージョンは省略できます。省略した場合は次のようになります。

module org.example.alfa {
    class org.example.alfa.HelloAlfa;
}
  • 注)現在バージョン番号を省略したモジュールでエントリポイントを指定しているものはインストール時にエラーとなるようです。

ビルドする

ビルドの流れは

  1. コンパイルする(.javaファイルから.classファイルを生成する)
  2. モジュールリポジトリを作成する
  3. モジュールリポジトリにモジュールを追加する
  4. (オプション)再配布用のモジュールファイルを作成する

となります。
コンパイルした場所で実行するならオプションは不要です。

コンパイルする

シェル環境(bash)では次のように実行します。

~$ cd 1stJigsaw
1stJigsaw$ ls
src
1stJigsaw$ mkdir build
1stJigsaw$ ls
build   src
1stJigsaw$ javac -d ./build -modulepath ./build -sourcepath src `find ./src -name "*.java"`
1stJigsaw$ 

Windowsコマンドプロンプトでは次のように実行します。

C:\Users\torutk> cd 1stJigsaw
C:\Users\torutk\1stJigsaw> dir /b
src
C:\Users\torutk\1stJigsaw> mkdir build
C:\Users\torutk\1stJigsaw> dir /b
build
src
C:\Users\torutk\1stJigsaw> dir /s /b src\*.java > sources.txt
C:\Users\torutk\1stJigsaw> javac -d .\build -modulepath .\build -sourcepath .\src @sources.txt
C:\Users\torutk\1stJigsaw>

この結果コンパイルされた結果は次のようになります。

1stJigsaw
 +-- build
      +-- org.example.alfa
           +-- module-info.class
           +-- org
                +-- example
                     +-- alfa
                          +-- HelloAlfa.java
  • -dオプションを指定しないと、-modulepathによらずソースファイルと同じ場所にclassファイルが生成されてしまいます。
  • -modulepathオプションを指定しないと、-dオプション指定下にモジュール名のディレクトリができず、パッケージに対応したディレクトリが生成されます。
モジュールリポジトリを作成する

モジュールを収める場所(リポジトリ)を作成します。

1stJigsaw> jmod create -L lib
1stJigsaw> ls
build  lib   src
1stJigsaw> 

これでできました。libのなかには正体不明の管理ファイルが生成されています。

1stJigsaw> ls lib
%jigsaw-library  %lock  %mids
1stJigsaw>
モジュールリポジトリにモジュールを追加する

先ほどコンパイルしたorg.example.alfaモジュールをこのリポジトリに追加します。

1stJigsaw> jmod install ./build org.example.alfa -L lib
1stJigsaw>

モジュールリポジトリの下は次のようになります。

1stJigsaw
 :
 +-- lib
      +-- %jigsaw-library
      +-- %lock
      +-- %mids
      +-- org.example.alfa
           +-- 1.0
                +-- classes
                +-- config
                +-- index
                +-- info

メモ

  • classesファイルはZIP形式で、この中にコンパイルしたクラスが入っています。
  • ディレクトリ'1.0'の名前は、module-info.javaで指定したバージョン番号になります。バージョン番号を省略したときのディレクトリは'default'となります。
(オプション)再配布用のモジュールファイルを作成する

プログラムを配布するためにモジュールファイルを作成します。モジュールファイルは、jpkgコマンドで作成します。モジュールファイルのソースは、モジュールリポジトリではなく、コンパイル結果を生成したディレクトリとなります。

1stJigsaw> jpkg -m ./build/org.example.alfa jmod org.example.alfa
1stJigsaw> ls
build   lib     org.example.alfa@1.0.jmod  src
1stJigsaw>

jpkgでモジュールリポジトリからモジュールファイルを生成できた方がいいと思いましたが、そういう思想ではない模様です。jpkgで-mオプションではなく-Lオプションでモジュールリポジトリを指定してモジュールファイルを生成しようとしたらエラーとなってしまいました。

モジュールの実行

モジュールリポジトリにインストールしたモジュールを実行します。

1stJigsaw> java -L lib -m org.example.alfa
Warning: CLASSPATH environment variable ignored when -m specified
Hello, Alfa

1stJigsaw>

なお、ビルド結果のディレクトリから直接実行する方法(javaコマンドの-modulepathオプション)はエラーとなってしまいました。Preview版だからかもしれません。

1stJigsaw> java -modulepath build -m org.example.alfa
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Unrecognized option: -modulepath

モジュールファイルのインストール

モジュールファイルを受け取ったら、モジュールリポジトリにインストールして実行します。

1stJigsaw> jmod install org.example.alfa@1.0.jmod -L lib
  : (既に存在しているモジュールをinstallしようとするとエラーになる)

同じモジュール(+バージョン)を再度インストールしようとするとエラーとなります。現時点のプレビュー版*3では回避方法がありません。
参考になる情報

そこで、いったんモジュールリポジトリを丸ごと削除し、インストールします。

1stJigsaw> rm -r lib
1stJigsaw> jmod create -L lib
1stJigsaw> 

モジュールファイルからインストールします。

1stJigsaw> jmod install org.example.alfa@1.0.jmod -L lib
1stJigsaw> 

Project Jigsawで何が変わるのか

今、Javaでライブラリを開発して公開するときは、JARファイルの形式で提供しています。バージョンは、ファイル名に付けるか(foo-1.2.3.jar)、JARファイル内のマニフェストファイルに記述するか(MANIFEST.MFのSpecification-VersionやImplementation-Version)でした。JARファイル内のマニフェストは任意なので、記述のないライブラリが大半です。また記述があっても読み取る手段が乏しく活用されることはあまりありません。

複数のJARファイルを組み合わせるときに、不足するJARがあったとき、JARのバージョンが不整合で不足するクラス・メソッドがあったとき、どちらも実際に実行してからのエラーとなります。

Javaのpackageのアクセス制御では、ライブラリ外には公開したくないクラスであってもライブラリ内で複数のpackageから利用する場合publicにせざるを得ず、publicとしたらライブラリ外からも見えてしまいます。

このようなJARの管理の課題を解決し、もっと今流の管理ができるように改善したのがProject Jigsawです。モジュールという仕組みを導入することで、プログラムの管理を容易にするのですが、今まで実行時のファイル構成など考えなかった人たちには、プログラム開発時にモジュールに合わせたディレクトリ構成を取る必要があるので、覚えておくべき項目が増えて厄介に感じるかもしれません。

参考文献

JavaOne Tokyo 2012のセッション資料

Project Jigsawのドキュメント"Jigsaw Quick Start"

さいごに

今回は、Helloレベルの最低限のプログラムを実行するための手順を確認しました。
次は、複数のモジュールを扱うことや、隠蔽するパッケージ、バージョンアップといったことに取り組む予定です。(時期未定)

*1:http://www.w3.org/Jigsaw/ いや、ほとんどいないでしょうけれど

*2:ラムダは含まれてません

*3:2012/6/22のbuild 1.8.0-ea-jigsaw-nightly-h458-20120622-b42-b00ではjmod removeが追加され、モジュールを削除する手段が提供されるようになりました。