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.よくわからん