Javaには膨大な数のライブラリがあり、我々開発者としてはそれらを使うことで既存の枯れた機能を簡単(?)に利用できてウハウハな訳ですが、ライブラリのJarファイルの管理はどうにも大変ですね。
Jar同士の依存性管理とか、手動でがんばるとありえないし。
というわけで、今まではMavenやIvyなどの完全に外部のプロダクトとしてその辺りのソリューションが提供されてきてたわけですが、ついに本家による対策がなされるときがきたのです、というお話。
経緯とかあまり詳しくないんですけど、
既に組み込みではデファクトのモジュラリティソリューションであるOSGiをそのまま採用しよう
↓
やっぱりやめよう
↓
やっぱりOSGiでいこう
↓
(以下ループ)
みたいなやりとりが過去にあったようです。結局はOSGiとは全然別物になって今回のお披露目となりました。
というわけで、Project Jigsawのセッションです。
名前だけみると、なんかコスプレした人によく分からない場所に監禁されて致死確定のゲームを強いられるふいんきが漂いますが、もちろんそんなことはありません。
さて、Project Jigsawによる新しいモジュールシステムの特徴をぱらぱらと紹介していきます。
間違いがありましたらツッコミお願いします。
Install-time optimizations
新しいモジュールシステムでは、実行時ではなく、Jarファイルを実行環境上にインストールするときに、色々と最適化しておくことで、実行時のロード時間を劇的に改善します。
- 通常は実行時にクラスファイルのベリファイが行われますが、インストール時に実行しておくことで省略できるようになり、10〜20%程度高速になります。
- アプリケーション起動時にクラスのリストを記録して、それらを1つのファイルにコピーしておくことで、次回以降の起動が10〜30%程度高速になります。
- クラスファイルをより効率的なフォーマットに変換して、共有することで10〜20%程度高速化します。
なんか数字だけ見るとめっちゃ速くなりそうなんですけど、実際どうなんですかね。それとも誤読?
依存関係はmodule-info.javaに設定する
Modularityの構成要素は、
Modularity = Grouping
+ Dependence
+ Versioning
+ Encapsulation
+ Optional modules
+ Virtual modules
ってことらしいです。
Grouping
複数のパッケージを1つのモジュールとして扱うための方法です。
といっても、ごくシンプルなもので、たとえば、
// com/foo/Main.java package com.foo; import java.io.*; import java.util.*; public class Main { ... } // com/foo/Other.java package com.foo; import java.io.*; import java.util.*; public class Other { ... } // com/foo/ui/Shell.java package com.foo.ui; import java.io.*; import java.util.*; public class Shell { ... }
という3つのクラスがあるときに(パッケージ名に注目)、
// module-info.java
module com.foo { }
のように、module-info.javaというファイルに、見慣れぬmodule記法でパッケージ名の共通部分までを記述すると、com.foo.Mainとcom.foo.Otherとcom.foo.ui.Shellが同一のcom.fooモジュールに含まれる、ということになります。
JarのときはMETA-INF/MANIFEST.MFにMain-Classというエントリを書いて、実行可能Jarのエントリポイントを指定しましたが、Jigsawでは
// module-info.java module com.foo { class com.foo.Main; }
と書きます。
Dependence
次はモジュール内のクラスが外部ライブラリに依存してる場合です。
// com/foo/ui/Shell.java package com.foo.ui; import java.io.*; import java.util.*; import org.bar.lib.*; // ←これと import edu.baz.util.*; // ←これ。 public class Shell { ... }
のように、Shellクラスがorg.barライブラリとedu.bazライブラリに依存している場合、今までだと、
$ java -cp app.jar:bar.jar:baz.jar ....
などと実行時にクラスパスリストをがんばって書かなければなりませんでしたが、Jigsaw以降はmodule-info.javaで宣言しておくだけOKになります。
つまり「クラスパスが許されるのなんて、Java7までだよねーー」 ということです。*1
さて、module-info.javaは↓こう書きます。
// module-info.java
module com.foo {
requires org.bar.lib;
requires edu.baz.util;
}
パッケージ名を書くだけ。jarファイル名とかは気にしません*2。あくまでAPIとしてのパッケージ名に対する依存性を記述します。
Versioning
依存先のAPIはバージョンごとに変化してしまう可能性がありますので、依存しているバージョンを明記するのは良い作法です。
module com.foo @ 1.0.0 { requires org.bar.lib @ 2.1-alpha; requires edu.baz.util @ 5.2_11; }
このように@の後にバージョンを記述します。
Encapsulation
全部のモジュールがpublic扱いだと、見えすぎて何かとよくありません。
Javaコードでも全部publicだとちょっとアレですね。
適度に隠蔽した方が見通しもよくシンプルな構造が保てます。
というわけで、依存されるモジュール側でアクセスを許可するモジュールを指定して、限定公開することもできます。
module com.foo.secret { permits com.foo.lib; }
と書くと、com.foo.libからのrequiresのみに限定することができます。
module com.foo.lib { requires com.foo.secret; }
は許可されているのでOKですが、
module com.foo.app { requires com.foo.secret; }
はそれ以外のモジュールからの依存になるのでNGです。なんかエラーになるんですかね。
Optional modules
もしあったら使うけど、無くても代替手段があるから別にいいよ、というオプション扱いのモジュールも指定できます。
module com.foo.app {
requires com.foo.lib;
requires optional com.foo.extra; // ←これ
}
Virtual modules
異なるモジュールを集約(Aggregation)した仮想モジュールを作ることもできます。
module com.foo.app {
requires com.foo.lib.db;
requires com.foo.lib.ui;
requires com.foo.lib.util;
}
// Virtual modules
module com.foo.lib {
provides com.foo.lib.db;
provides com.foo.lib.ui;
provides com.foo.lib.util;
}
上記の設定の場合、実際のモジュール依存関係としては、com.foo.app→com.foo.lib となります。
モジュールの実体とかインストールとか
Packagingの構成要素は、
Packaging = Module files
+ Libraries
+ Repositories
+ Native packages
ってことらしいです。
Module files
$ ls src/ mods/ $ javac -modulepath mods src/com.foo.app/... $ ls mods com.foo.app/ com.foo.extra/ com.foo.lib/ $ ls -R mods/com.foo.app mods/com.foo.app/com/foo/app/Main.class mods/com.foo.app/com/foo/app/Other.class mods/com.foo.app/com/foo/ui/Shell.class
-classpath の代わりに、-modulepathオプションが提供されます。
とはいえ、一応-classpathオプションも継続で提供されるようです。一安心。
資料にあったコード例をみると、ここに指定したディレクトリにモジュールごとのクラスファイルが格納されるようです。この辺、実際に手を動かしてないので細かいところは不明です。
次に、モジュールごとに、クラスファイルをjmodという形式でパッケージングします。
jpkgというコマンドが追加されています。
$ jpkg -modulepath mods jmod com.foo.app com.foo.extra com.foo.lib $ ls *.jmod com.foo.app@1.0.0.jmod com.foo.extra@0.9a.jmod com.foo.lib@1.0.2.jmod
というわけで、直下にjmodファイルができました。
Libraries
サードパーティから提供されているモジュールファイルをローカルのリポジトリにインストールして、自分のアプリからのモジュール依存性解決パスにいれる、などのようにモジュールをライブラリとして管理・利用するためにjmodコマンドが提供されています。
jmodのコマンド例が資料にのってますが、説明文がないので解釈に全然自信がないです。
おそらくは、↓のような話かと思われます・・・。
- アプリ専用のモジュールライブラリ(?)*3の格納用に、カレントディレクトリ配下のmlibディレクトリを生成します。
$ jmod -L mlib create
- あらかじめとあるディレクトリにダウンロードしておいた、サードパーティのモジュールをインストールします。
$ jmod -L mlib install \ $EXT/edu.baz.util@*.jmod \ $EXT/org.bar.lib@*.jmod$
- 前述の例において、自前で生成しておいたモジュールも同じくインストールします。
$ ls *.jmod com.foo.app@1.0.0.jmod com.foo.extra@0.9a.jmod com.foo.lib@1.0.2.jmod $ jmod -L mlib install *.jmod
- モジュールライブラリの中身を確認します。
$ jmod -L mlib ls om.foo.app @ 1.0.0 com.foo.extra @ 0.9a com.foo.lib @ 1.0.2 edu.baz.util @ 5.2_11 org.bar.lib @ 2.1-alpha
- -Lオプションでモジュールライブラリを指定しつつ、-mで実行したいmainメソッドを含むモジュールを指定して、アプリを起動します。
$ java -L mlib -m com.foo.app Welcome to Foo, v1.0.0 ...
Repositories
Mavenリポジトリのように外部サーバに置いてあるjmodファイルをhttp経由で取得してインストールすることもできるよー、という話。
こんな感じ。
$ jmod add-repo http://jig.sfbay $ jmod install -n jdk.tools Modules needed: jdk.tools@7-ea Bytes to download: 1.2M Bytes to install: 2.3M $ jmod install jdk.tools Downloading jdk.tools@7-ea ... Configuring jdk.tools@7-ea ...
Native packages
ざっくり言うと、jmod形式だけじゃなくて、rpmとかdebとかOS依存のネイティブなパッケージ形式にも対応しているんだよー、という話。