このドキュメントではいくつかのシンプルな例を開発者がmoduleを始められるように提供します。
このサンプルの中でファイルパスはforward slashesを使い、でpath separatorはcolonsです。
Windos Userはback slashedとpath separatorはsemicolonを使ってください。
この最初の例では com.greetings
と名付けられたモジュールで それは シンプルに Greetings!
と表示するものです。
このモジュールは2つのソースから成り立っており、モジュールの宣言である、module-info.java
と main classです。
習慣に則って、モジュールのソースコードはモジュールの名前のディレクトリに配置しています。
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 --module-path mods -m com.greetings/com.greetings.Main
--module-pathはモジュールのパスです。この値はモジュールを含んだ1つもしくは複数のディレクトリです。
-m オプショnで指定するのはmain moduleで、この値のslash以降はそのmain モジュール内のメインクラスのクラス名です。
次のサンプルではモジュールの宣言と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回で1つのモジュールをコンパイルします。
javac コマンドでcom.greetings
モジュールをコンパイルするために
モジュールのパスを指定します。org.astro
モジュールへの参照とそのモジュールのexportされたパッケージにあるTypeを解決するために。
$ mkdir -p 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 --module-path mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
この例では最初の例と同じ方法で実行することが可能です。
$ java --module-path mods -m com.greetings/com.greetings.Main
Greetings world!
前回の例ではcom.greetings
とorg.astro
のモジュールを分離してコンパイルしました。
1回のコンパイルで複数モジュールをコンパイルすることも可能です。
$ mkdir mods
$ javac -d mods --module-source-path 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
これまでの例では、ファイルシステム上にコンパイルされたモジュールが散らばっていました。
転送やデプロイのためにmodular JARとしてモジュールをpackageすることが一般に便利でしょう。
modular JARは通常のJAR fileでトップレベルにmodule-info.classを持った物です。
次の例ではmlibディレクトリに [email protected] とcom.greeting.jarを作成します。
$ mkdir mlib
$ jar --create --file=mlib/[email protected] \
--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 [email protected]
この例では
モジュールorg.astro
はパッケージされており、1.0のバージョンを示しています。
また、com.greetings
モジュールはパッケージされており、Main classがcom.greetings.Mainを示しています。そのため、com.greetingsはメインクラスを指定することなく実行することが可能です。
$ java -p mlib -m com.greetings
Greetings world!
上記コマンドラインの-p ショートハンドによって--module-pathの代わりになります。
jar toolにはたくさんのオプションがありますが、次の例では
modular JARとしてパッケージされたjarのmodule宣言を表示する例です。
$ jar --describe-module --file=mlib/[email protected]
[email protected] jar:file:///d/mlib/[email protected] /!module-info.class
exports org.astro
requires java.base mandated
Missing requires or missing exports
この例では、前回の例で間違えて、requiresディレクティブを外してしまった場合に
どうなるかを見てみましょう。
$ cat src/com.greetings/module-info.java
module com.greetings {
// requires org.astro;
}
$ javac --module-path 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 is not visible
import org.astro.World;
^
(package org.astro is declared in module org.astro, but module com.greetings does not read it)
1 error
このモジュール定義を修正して、別のミスを入れたときにどうなるかみてみましょう。
ここでは 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 --module-path mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
$ javac --module-path 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 is not visible
import org.astro.World;
^
(package org.astro is declared in module org.astro, which does not export it)
1 error
Serviceはプロバイダとコンシューマに緩い結合を許します。
この例ではサービスプロバイダとサービスコンシューマの例を示します。
com.socket モジュールは network socketに関係するAPIを公開しています。このAPIはcom.socket
パッケージを持っており、そのパッケージを公開しています。このAPIはpluggableで、代替の実装を許します。com.socket.spi.NetworkSocketProvider
が同じモジュールに入っており、com.socket.spi
もexportされています。
com.fastsocket
モジュールはサービスプロバイダです。これはcom.socket.spi.NetworkSocketProvider
の実装を提供しています。
しかし、何もpackageは公開していません。
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();
}
com.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() { }
}
簡単にこれらのモジュールを一緒にコンパイル可能です。
ほとんどの場合、実用ではコンシューマとプロバイダのモジュールは別でコンパイルされるでしょう。
$ mkdir mods
$ javac -d mods --module-source-path src $(find src -name "*.java")
最後に、com.greetings
モジュールを変更してこれらのAPIを使ってみます。
$ 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/ -p mods $(find src/com.greetings/ -name "*.java")
実行してみます。
$ java -p mods -m com.greetings/com.greetings.Main
class org.fastsocket.FastNetworkSocket
この出力でサービスプロバイダによって提供されているNetworkSocket
のファクトリが使われていることが分かります。
jlinkはlinker toolでモジュールのセットをリンクして利用することを可能にします、推移的な依存とともに。そして独自の modular 実行イメージを作ることができます。 (See JEP220)
このツールは現在、モジュールパスにパッケージされたmodular JARかJMODフォーマットでモジュールが配置されていることを必要とします。
次の例ではサンプルの実行イメージを作ります。 com.greetings
とそれに依存するモジュールを含みます。
jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.greetings --output greetingsapp
--module-pathの値はパッケージされたモジュールを含むディレクトリです。Windowの場合はセミコロンに置き換えてください。
$JAVA_HOME/jmods はjava.base.jmodやその他の標準やJDKのモジュールを含んだディレクトリです。
mlib ディレクトリは com.greetings
モジュールの成果物を含むディレクトリです。
jlinkは生成されるイメージをカスタマイズするために複数のオプションをサポートしています。 詳しくはjlink --help
を見てください。
DDoug LeaのCVSからモジュールを置き換えたい場合、ソースをコンパイルして -Xbootclasspath/p
オプションを利用するでしょう。
-Xbootclasspath/p
は削除され、モジュールの置き換えは --patch-moduleを利用します。
モジュールの中身を強化するために利用することが可能です。
--patch-moduleはjavacもサポートしています。モジュールの一部であるかのようにコードをコンパイル可能です。
javac --patch-module java.base=src -d mypatches/java.base \
src/java.base/java/util/concurrent/ConcurrentHashMap.java
java --patch-module java.base=mypatches/java.base ...
https://www.youtube.com/watch?v=p7jCvbzqnS0を見た。
jdeps --generate-module-info hogehoge
きつねさんによるモジュールのディレクティブ解説
http://d.hatena.ne.jp/bitter_fox/20160921/1474476977
SPI (ServiceLoader)がMETA-INF/servicesに書かなくても使えるようになるみたい。
http://d.hatena.ne.jp/bitter_fox/20160720/1469042323
ModuleFinderのjavadoc
http://cr.openjdk.java.net/~mr/jigsaw/spec/api/java/lang/module/ModuleFinder.html#automatic-modules