http://openjdk.java.net/projects/jigsaw/quick-start をテキトーに訳した
#java9 #javac #jigsaw quick start
https://t.co/xNhn9xmcJs
— A. Sundararajan (@sundararajan_a) 2015, 10月 20
Project Jigsaw: Module System Quick-Start Guide
このドキュメントでは、モジュール(module)を初めて使う開発者向けにいくつかの例を示します。
例におけるファイルパスはスラッシュでパスセパレータはコロンです。Microsoft Windows上での開発の場合、ファイルパスはバックスラッシュでパスセパレータはセミコロンになります。
- Greetings
- Greetings world
- Multi-module compilation
- Packaging
- Missing requires or missing exports
- Services
- The linker
- javac -Xmodule and java -Xpatch
Greetings
最初の例は"Greetings!"と表示するだけのcom.greetingsという名前のモジュールです。このモジュールには2つのソースファイルが含まれており、それぞれモジュール定義(module-info.java)とメインクラスです。
慣例により、モジュールのソースコードはモジュール名のディレクトリに配置します。
src/com.greetings/com/greetings/Main.java
src/com.greetings/module-info.java
$ cat src/com.greetings/module-info.java
module com.greetings { }
$ cat src/com.greetings/com/greetings/Main.java
package com.greetings;
public class Main {
public static void main(String[] args) {
System.out.println("Greetings!");
}
}
ソースコードは以下のコマンドによりmods/com.greetingsディレクトリにコンパイルされます。
$ mkdir -p mods/com.greetings
$ javac -d mods/com.greetings \
src/com.greetings/module-info.java \
src/com.greetings/com/greetings/Main.java
上記の例は以下のコマンドで実行します。
$ java -modulepath mods -m com.greetings/com.greetings.Main
-modulepathはモジュールパスで、モジュールを含むディレクトリを値として1つ以上指定します。-mオプションでメインモジュールを指定し、スラッシュより後ろがモジュールのメインクラス名です。
Greetings world
2つ目の例はモジュールorg.astroへの依存を宣言するようにモジュール宣言を書き換えます。モジュールorg.astroはorg.astroパッケージのAPIをエクスポートします。
src/org.astro/module-info.java
src/org.astro/org/astro/World.java
src/com.greetings/com/greetings/Main.java
src/com.greetings/module-info.java
$ cat src/org.astro/module-info.java
module org.astro {
exports org.astro;
}
$ cat src/org.astro/org/astro/World.java
package org.astro;
public class World {
public static String name() {
return "world";
}
}
$ cat src/com.greetings/module-info.java
module com.greetings {
requires org.astro;
}
$ cat src/com.greetings/com/greetings/Main.java
package com.greetings;
import org.astro.World;
public class Main {
public static void main(String[] args) {
System.out.format("Greetings %s!%n", World.name());
}
}
モジュールは1つずつコンパイルされます。モジュールcom.greetingsをコンパイルするためのjavacコマンドではモジュールパスを指定していますが、これはモジュールorg.astroへの参照とorg.astroがエクスポートするパッケージの型を解決可能にするためです。
$ mkdir mods/org.astro mods/com.greetings
$ javac -d mods/org.astro \
src/org.astro/module-info.java src/org.astro/org/astro/World.java
$ javac -modulepath mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
この例は最初の例と全く同じコマンドで動作します。
$ java -modulepath mods -m com.greetings/com.greetings.Main
Greetings world!
Multi-module compilation
前の例ではモジュールcom.greetingsとorg.astroは別々にコンパイルしていました。複数モジュールを1回のjavadcコマンドでコンパイルすることも可能です。
$ mkdir mods
$ javac -d mods -modulesourcepath src $(find src -name "*.java")
$ find mods -type f
mods/com.greetings/com/greetings/Main.class
mods/com.greetings/module-info.class
mods/org.astro/module-info.class
mods/org.astro/org/astro/World.class
Packaging
これまでの例ではコンパイルしたモジュールはファイルシステム上にexploded*1されています。配布やデプロイ目的には、通常、扱いやすくするためにmodular JARとしてモジュールをパッケージします。modular JARは普通のJARファイルでトップレベルディレクトリにmodule-info.classを持ちます。以下の例はmlibディレクトリにorg.astro@1.0.jarとcom.greetings.jarを作成します。
$ mkdir mlib
$ jar --create --file=mlib/org.astro@1.0.jar \
--module-version=1.0 -C mods/org.astro .
$ jar --create --file=mlib/com.greetings.jar \
--main-class=com.greetings.Main -C mods/com.greetings .
$ ls mlib
com.greetings.jar org.astro@1.0.jar
この例では、モジュールorg.astroにはバージョン1.0を示すためのパッケージ化が行われています。モジュールcom.greetingsはメインクラスがcom.greetings.Mainと示すためのパッケージ化が行われています。これにより、メインクラスを指定することなくモジュールcom.greetingsを実行可能です。
$ java -mp mlib -m com.greetings
Greetings world!
また、-modulepathの代わりに-mpを使うことでコマンドラインを短縮しています。
jarツールには新規オプション(jar -help参照)が多数追加され、その内の1つに、modular JARとしてパッケージ化されたモジュールの宣言を表示するものがあります。
$ jar --print-module-descriptor --file=mlib/org.astro@1.0.jar Name: org.astro@1.0 Requires: java.base [ MANDATED ] Exports: org.astro
Missing requires or missing exports
次に、前述の例においてcom.greetingsモジュール宣言からrequiresを誤って削除した場合に何が起きるのかについて見ていきます。
$ cat src/com.greetings/module-info.java
module com.greetings {
// requires org.astro;
}
$ javac -modulepath mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
src/com.greetings/com/greetings/Main.java:2: error: package org.astro does not exist
import org.astro.World;
^
src/com.greetings/com/greetings/Main.java:5: error: cannot find symbol
System.out.format("Greetings %s!%n", World.name());
^
symbol: variable World
location: class Main
2 errors
モジュール宣言を修正したとして、それとは別のミスとしてorg.astroモジュール宣言からexportsを削除してみます。
$ cat src/com.greetings/module-info.java
module com.greetings {
requires org.astro;
}
$ cat src/org.astro/module-info.java
module org.astro {
// exports org.astro;
}
$ javac -modulepath mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
src/com.greetings/com/greetings/Main.java:2: error: package org.astro does not exist
import org.astro.World;
^
src/com.greetings/com/greetings/Main.java:5: error: cannot find symbol
System.out.format("Greetings %s!%n", World.name());
^
symbol: variable World
location: class Main
2 errors
Services
サービスによりservice consumersモジュールとservice providersモジュール間に疎結合が実現できます。
以下の例にはservice consumerモジュールとservice providerモジュールがあります。
- モジュール
com.socketはネットワークソケット用のAPIをエクスポートします。このAPIはcom.socketパッケージにあり、エクスポートされます。このAPIは別の実装を許容するためにpluggableです。サービスの型は同モジュール内のcom.socket.spi.NetworkSocketProviderで、com.socket.spiもエクスポートされます。 - モジュール
org.fastsocketはservice providerモジュールです。com.socket.spi.NetworkSocketProviderの実装を提供します。エクスポートするパッケージはありません。
以下はモジュールcom.socketのソースコードです。
$ cat src/com.socket/module-info.java
module com.socket {
exports com.socket;
exports com.socket.spi;
uses com.socket.spi.NetworkSocketProvider;
}
$ cat src/com.socket/com/socket/NetworkSocket.java
package com.socket;
import java.io.Closeable;
import java.util.Iterator;
import java.util.ServiceLoader;
import com.socket.spi.NetworkSocketProvider;
public abstract class NetworkSocket implements Closeable {
protected NetworkSocket() { }
public static NetworkSocket open() {
ServiceLoader<NetworkSocketProvider> sl
= ServiceLoader.load(NetworkSocketProvider.class);
Iterator<NetworkSocketProvider> iter = sl.iterator();
if (!iter.hasNext())
throw new RuntimeException("No service providers found!");
NetworkSocketProvider provider = iter.next();
return provider.openNetworkSocket();
}
}
$ cat src/com.socket/com/socket/spi/NetworkSocketProvider.java
package com.socket.spi;
import com.socket.NetworkSocket;
public abstract class NetworkSocketProvider {
protected NetworkSocketProvider() { }
public abstract NetworkSocket openNetworkSocket();
}
以下はモジュールorg.fastsocketのソースコードです。
$ cat src/org.fastsocket/module-info.java
module org.fastsocket {
requires com.socket;
provides com.socket.spi.NetworkSocketProvider
with org.fastsocket.FastNetworkSocketProvider;
}
$ cat src/org.fastsocket/org/fastsocket/FastNetworkSocketProvider.java
package org.fastsocket;
import com.socket.NetworkSocket;
import com.socket.spi.NetworkSocketProvider;
public class FastNetworkSocketProvider extends NetworkSocketProvider {
public FastNetworkSocketProvider() { }
@Override
public NetworkSocket openNetworkSocket() {
return new FastNetworkSocket();
}
}
$ cat src/org.fastsocket/org/fastsocket/FastNetworkSocket.java
package org.fastsocket;
import com.socket.NetworkSocket;
class FastNetworkSocket extends NetworkSocket {
FastNetworkSocket() { }
public void close() { }
}
説明単純化のために、上記モジュールは同時にコンパイルしています。実際には、service consumerモジュールとservice providerモジュールは別々にコンパイルされることがほとんどだと思われます。
$ mkdir mods
$ javac -d mods -modulesourcepath src $(find src -name "*.java")
最後に、上記APIを使うためにモジュールcom.greetingsを修正します。
$ cat src/com.greetings/module-info.java
module com.greetings {
requires com.socket;
}
$ cat src/com.greetings/com/greetings/Main.java
package com.greetings;
import com.socket.NetworkSocket;
public class Main {
public static void main(String[] args) {
NetworkSocket s = NetworkSocket.open();
System.out.println(s.getClass());
}
}
$ javac -d mods/com.greetings/ -mp mods $(find src/com.greetings/ -name "*.java")
実行します。
$ java -mp mods -m com.greetings/com.greetings.Main
class org.fastsocket.FastNetworkSocket
出力結果として、service providerが配置されてNetworkSocketのファクトリーとしてそのservice providerが使われていることが確認できます。
The linker
jlinkはリンカーツールです。複数のモジュールをリンクし、推移依存(transitive dependences)に従ってカスタムのモジュール実行時イメージ(custom modular run-time image)(JEP 220を参照)を作成するのに使います。
このツールは今のところ、modular JARもしくはJMODフォーマットでパッケージ化されたモジュールパス上のモジュールを要求します。JDKのビルドはスタンダードおよびJMODフォーマットのJDK固有モジュールをパッケージします。
以下の例はモジュールcom.greetingsと推移依存を含む実行時イメージを作成します。
jlink --modulepath $JAVA_HOME/jmods:mlib --addmods com.greetings --output greetingsapp
$JAVA_HOME/jmodsディレクトリにはjava.base.jmodとその他の標準およびJDKモジュールが含まれます。OpenJDKの自前ビルドを使う場合、jmodファイルは$BUILDOUTPUT/images/jmodにあり、$BUILDOUTPUTはビルド出力ディレクトリです。
モジュールパス上のmlibディレクトリにはモジュールcom.greetings用のアーティファクトが含まれます。
jlinkツールはイメージ生成をカスタマイズするための高度なオプションを多数サポートしており、詳細はjlink --helpを参照してください。
現時点では、jlinkツールはデフォルトでservice providerモジュールのリンクを行うため、生成される実行時イメージには予期しないモジュールが含まれる可能性があります。
javac -Xmodule and java -Xpatch
Doug Lea's CVSからjava.util.concurrentクラスをチェックアウトする開発者はソースファイルのコンパイルとクラスのデプロイに-Xbootclasspath/pを使うことになると思われます。
-Xbootclasspath/pが無いと、モジュール置換はモジュール内のクラスのオーバーライドするための-Xpatchオプションとなります*2。また、既存モジュールのパッケージのクラスのコンパイル時にjavacは警告を出します。既存モジュールのクラスをコンパイルするにはjavacの-Xmoduleオプションが必要です。
以下の例はjava.util.concurrent.ConcurrentHashMapの新バージョンをコンパイルし、実行時にこれを使用しているものです。
javac -Xmodule:java.base -d mypatches/java.base \ src/java.base/java/util/concurrent/ConcurrentHashMap.java java -Xpatch:mypatches ...
More information
Feedback
使用法に関する質問や実際に使ってみた感触をjigsaw-devへフィードバックをお願い致します。モジュールシステムの設計に関する具体的な提案はJSR 376 Expert Group's comments listに送信してください。
*1:jarやwarと異なり中身のファイルがディレクトリ上に存在している様を示すのに使われる。なおexplodeには爆発するとか破裂させる、とかの意味がある。タブンだけど、warやjarのように固まっているのではなく、ファイルがディレクトリに散らばっているのをexplodeと表現しているのでは?と思われる。exploded deploymentとかでぐぐるとそんな気がする。
*2:-Xbootclasspath/p has been removed, its module replacement is the option -Xpatch to override classes in a module.よくわからん