Jigsaw 勉強メモ

Java 9 の Project Jigsaw を勉強したときのメモ。

# 環境
## OS
Windows 10

## Java
```
>java --version
java 9
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
```

# Hello World
## フォルダ構成
```
|-main/
| |-class/
| `-src/
|   |-module-info.java
|   `-sample/main/
|     `-Main.java
|
`-sub/
  |-class/
  `-src/
    |-module-info.java
    `-sample/sub/
      |-api/
      | `-Foo.java
      |-internal/
      | `-FooImpl.java
      |
      `-FooFactory.java
```

## ソースコード
```java:sub/src/sample/sub/api/Foo.java
package sample.sub.api;

public interface Foo {
    void hello();
}
```

```java:sub/src/sample/sub/internal/FooImpl.java
package sample.sub.internal;

import sample.sub.api.Foo;

public class FooImpl implements Foo {
    @Override
    public void hello() {
        System.out.println("FooImpl.hello()");
    }
}
```

```java:sub/src/sample/sub/FooFactory.java
package sample.sub;

import sample.sub.api.Foo;
import sample.sub.internal.FooImpl;

public class FooFactory {
    public static Foo newInstance() {
        return new FooImpl();
    }
}
```

```java:sub/src/module-info.java
module sample_module.sub {
    exports sample.sub;
    exports sample.sub.api;
}
```

```java:main/src/sample/main/Main.java
package sample.main;

import sample.sub.FooFactory;
import sample.sub.api.Foo;

public class Main {
    public static void main(String... args) {
        Foo foo = FooFactory.newInstance();
        foo.hello();
    }
}
```

```java:main/src/module-info.java
module sample_module.main {
    requires sample_module.sub;
}
```

## コンパイル
```
# sub モジュールをコンパイルする
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\FooFactory.java sub\src\sample\sub\api\Foo.java sub\src\sample\sub\internal\FooImpl.java

# main モジュールをコンパイルする
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java
```

## 実行する
```bat
> java -p main\class;sub\class -m sample_module.main/sample.main.Main
FooImpl.hello()
```

## 説明
### 依存される側のモジュール宣言
```java:sub/src/module-info.java
module sample_module.sub {
    exports sample.sub;
    exports sample.sub.api;
}
```

- モジュールの宣言は、ルートパッケージに置く `module-info.java` という専用のファイルに記述する
    - 名前は `module-info.java` 固定
- モジュール宣言は、 `module <モジュール名> {<モジュール定義>}` という形式で記述する
- モジュール名はパッケージ名と同じようにドット `.` 区切りで記述できる
    - 通常はパッケージ名と同じような名前になるようにつけるようだが、それだとサンプル実装上パッケージ名とモジュール名が区別しづらいので、ここではあえて `sample_module.sub` とパッケージとは異なる名前を付けている
- `{}` の中ではこのモジュールに関する定義を記述できる
- `exports` は、外部に公開するパッケージを指定する
- ここでは `sample.sub` と `sample.sub.api` の２パッケージを外部に公開している
    - `sample.sub.internal` は内部 API のつもりなので、公開しないようにしている

### 参照する側のモジュール宣言
```java:main/src/module-info.java
module sample_module.main {
    requires sample_module.sub;
}
```

- 他のモジュールを参照する側も、参照される側と同様に `module-info.java` を使ってモジュール宣言を記述する
- `requries` で、このモジュールが依存するモジュールを定義する
- ここでは、さきほどの `sample_module.sub` モジュールを参照することを定義している
- メインクラスのあるパッケージ（`sample.main`）を `exports` しないと起動できない、という説明を見たが、実際やってみると `exports` していなくても動いた
    - 途中で仕様が変わった？

### コンパイル時のモジュール指定
```
# sub モジュールのコンパイル
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\FooFactory.java sub\src\sample\sub\api\Foo.java sub\src\sample\sub\internal\FooImpl.java

# main モジュールのコンパイル
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java
```

- コンパイルはこれまで通り `javac` コマンドを使用する
- 依存するモジュールが存在しない場合は、従来通りの指定でコンパイルできる（sub モジュールのコンパイル）
    - `module-info.java` をコンパイル対象のソースに含めることを忘れないように！
- 依存するモジュールが存在する場合は、 `-p` （もしくは `--module-path`）オプションで依存するモジュールが存在するフォルダのパスを指定する
    - 古い記事だと、このオプションが `-mp` で紹介されているものがあるが、どうやら変わったもよう

### 起動時のモジュール指定とメインクラスの指定
```bat
> java -p main\class;sub\class -m sample_module.main/sample.main.Main
```

- 起動時も、 `-p` （`--module-path`）オプションで使用するモジュールの場所を指定する
    - パスが複数ある場合は、 Windows ならセミコロン `;`、 Linux などならコロン `:` で連結して指定する
- また、メインクラスの指定は `-m` （`--module`）オプションで指定するように変わった（モジュールを使用している場合）
- メインクラス名の指定も変わっていて、 `<module名>/<メインクラス名>` と指定する
    - ここでは、 `sample_module.main` モジュールの `sample.main.Main` クラスを指定するため、 `sample_module.main/sample.main.Main` としている


# exports していないパッケージを参照した場合の挙動
```java:Main.java
package sample.main;

import sample.sub.FooFactory;
import sample.sub.api.Foo;
import sample.sub.internal.FooImpl;

public class Main {
    public static void main(String... args) {
        FooImpl foo = (FooImpl)FooFactory.newInstance();
        foo.hello();
    }
}
```

```text:コンパイルエラーの様子
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java
main\src\sample\main\Main.java:5: エラー: パッケージsample.sub.internalは表示不可です
import sample.sub.internal.FooImpl;
                 ^
  (パッケージsample.sub.internalはモジュールsample_module.subで宣言されていますが、エクスポートされていません)
エラー1個
```

こんな感じになって、 `exports` していないパッケージは参照できない。

# jar にパッケージして利用する
Hello World のプログラムを、 main, sub ともに jar にパッケージして使ってみる。

`javac` でコンパイルするところまでは同じ。

## jar にパッケージする
```
# sub モジュールの jar を作成
> jar -c -f sub\jar\sub-module.jar -C sub\class .

# main モジュールの jar を作成
> jar -c -f main\jar\main-module.jar -C main\class .
```

- モジュールの jar ファイルを作成するには、これまで通り `jar` コマンドを使用する
- `-c` は jar を作成するという意味のコマンド(`--create` の短縮形)
- `-f <jar の出力先パス>` で、出力先を指定する
- `-C <パッケージするディレクトリ> .` で、指定したディレクトリを jar にパッケージする

## jar のモジュール情報を参照する
```
# sub モジュールの情報を出力
> jar -d -f sub\jar\sub-module.jar
sample_module.sub jar:file:///.../sub/jar/sub-module.jar/!module-info.class
exports sample.sub
exports sample.sub.api
requires java.base mandated
contains sample.sub.internal

# main モジュールの情報を出力
> jar -d -f main\jar\main-module.jar
sample_module.main jar:file:///.../main/jar/main-module.jar/!module-info.class
requires java.base mandated
requires sample_module.sub
contains sample.main
```

- `-d` (`--describe-module`) で、 jar のモジュール情報を出力できる
- `exports` は公開されているパッケージ
- `requres` は依存しているモジュール
    - `java.base` は `java.lang` パッケージとかが含まれるモジュールで、指定していなくても必ずついてくる
- `contains` は、おそらく非公開のパッケージ

## 実行する
```
> java -p sub\jar;main\jar -m sample_module.main/sample.main.Main
FooImpl.hello()
```

- `-p` で、モジュール（`jar`）のあるフォルダを指定すれば、そこにある jar を読み込んでくれるっぽい
    - jar ファイルまでのパスを直接しても良い



# モジュールの種類
モジュールには、大きく次の３つの種類がある。

1. named module
2. automatic module
3. unnamed module

## named module
モジュールパス（`--module-path`）で読み込まれた定義情報（`module-info.class`）を持つモジュール。
単にモジュールと言ったら、これのことを指すことが多い。

## automatic module
モジュールパスで読み込まれた定義情報の無い jar。

全てのパッケージが `exports` されている扱いになる。
また、モジュール名は jar ファイル名から自動的に補完される。

Java 9 より前に作成された jar ファイルを `--module-path` で指定した場合などがこれになる。

明確な定義は見つけられていないが、ファイル名からモジュール名が補完されたりするので、たぶん jar ファイルでないといけないっぽい。
Javadoc にも以下のような記述があるので、最低限 jar である必要がある気がする。

> A JAR file that does not have a module-info.class in its top-level directory defines an automatic module
> （訳）
> トップレベルに `module-info.class` を持たない Jar ファイルは automatic module を定義します

http://download.java.net/java/jigsaw/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path...-

## unnamed module
クラスパス（`--class-path`）で読み込まれたモジュール。
全てのパッケージが `exports` されている扱いになる。

# 各モジュールを組み合わせる
各モジュール（named module, automatic module, unnamed module）をいろいろ組み合わせみる。

先に結果を表にすると、

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/a8d01910-4990-450f-2530-c563eddb0229.jpeg)

こんな感じになった。

## 実装（共通）
```java:sub/src/sample/sub/Sub.java
package sample.sub;

public class Sub {
    public void method() {
        System.out.println("Sub.method()");
    }
}
```

```java:main/src/sample/main/Main.java
package sample.main;

import sample.sub.Sub;

public class Main {
    public static void main(String... args) {
        Sub sub = new Sub();
        sub.method();
    }
}
```

## named module -> named module
Hello World がこれなので割愛。


## named module -> automatic module
```text:フォルダ構成
|-main/
| |-class/
| `-src/
|   |-module-info.java
|   `-sample/main/
|     `-Main.java
|
`-sub/
  |-class/
  `-src/
    `-sample/sub/
      `-Sub.java
```

```text:subモジュールのコンパイル
# コンパイル
> javac -d sub\class sub\src\sample\sub\Sub.java

# jar 作成
> jar -c -f sub\jar\no_module-sub.jar -C sub\class .
```

```java:main\src\module-info.java
module sample_module.main {
    requires no.module.sub;
}
```

- `requires` に指定するsub モジュールの名前は、 jar ファイル名から補完される名前を指定する
- `no_module-sub.jar` の場合は、 `no.module.sub` というモジュール名になる
- 詳しい変換ルールは後述

```text:mainモジュールのコンパイルと起動
# main モジュールのコンパイル
> javac -p sub\jar\no_module-sub.jar -d main\class main\src\module-info.java main\src\sample\main\Main.java

# 起動
> java -p sub\jar\no_module-sub.jar;main\class -m sample_module.main/sample.main.Main
Sub.method()
```

- sub モジュールを `-p` (`--module-path`) に設定して起動する

### automatic module のモジュール名の自動決定ロジック
1. マニフェストファイルの `Automatic-Module-Name` 属性
2. マニフェストファイルで指定されていない場合はファイル名から自動決定
    - 末尾の `.jar` を除く
    - 正規表現 `-(\d+(\.|$))` より前だけを抽出する
        - この正規表現から後ろはバージョン文字列として扱われる
        - `xxx-1.1.2.jar` は `xxx` がモジュール名として抽出される
    - 英数字（`[^A-Za-z0-9]`）以外の文字はドット (`.`) に置き換える
        - ドット (`.`) が繰り返された場合は１つのドットに置き換える

## named module -> unnamed module
できないっぽい。

```java:main/src/module-info.java
module sample_module.main {
    // requires no.module.sub;
}
```

```
> javac -cp sub\jar\no_module-sub.jar -d main\class main\src\module-info.java main\src\sample\main\Main.java
main\src\sample\main\Main.java:3: エラー: パッケージsample.subは存在しません
import sample.sub.Sub;
                 ^
main\src\sample\main\Main.java:7: エラー: シンボルを見つけられません
        Sub sub = new Sub();
        ^
  シンボル:   クラス Sub
  場所: クラス Main
main\src\sample\main\Main.java:7: エラー: シンボルを見つけられません
        Sub sub = new Sub();
                      ^
  シンボル:   クラス Sub
  場所: クラス Main
エラー3個
```

クラスパス(`-cp`)に jar を設定しても、クラスが読み込めない。

## automatic module -> named module
```text:フォルダ構成
|-main/
| |-class/
| `-src/
|   `-sample/main/
|     `-Main.java
|
`-sub/
  |-class/
  `-src/
    |-module-info.java
    `-sample/sub/
      `-Sub.java
```

```java:sub/src/module-info.java
module sample_module.sub {
    exports sample.sub;
}
```

```text:subモジュールのコンパイル
# コンパイル
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\Sub.java

# jar 作成
> jar -c -f sub\jar\module-sub.jar -C sub\class .
```

```text:mainモジュールのコンパイル
# コンパイル
> javac -d main\class --add-modules sample_module.sub -p sub\jar\module-sub.jar main\src\sample\main\Main.java

# jar 作成
> jar -c -f main\jar\no_module-main.jar -C main\class .
```

```text:実行
> java -p sub\jar\module-sub.jar;main\jar\no_module-main.jar --add-modules sample_module.sub -m no.module.main/sample.main.Main
Sub.method()
```

- main モジュールは `module-info.java` がない状態で `-p` (`--module-path`) に指定しているため、 automatic module として動く
- しかし、 `module-info.java` が無いので、 `sample_module.sub` は `requires` されておらず、ロードされていない状態となっている、たぶん（モジュールグラフ上に存在しない、と表現すればいいのかな？）
- `--add-modules` を使うと、任意のモジュールをルート・モジュールというものに追加することができる
- ルート・モジュールが何なのか厳密には分かっていないが、たぶん依存関係のグラフの起点としてモジュールを追加することなんだと思う
- これで `sample_module.sub` モジュールがロードされて、そこにあるクラスが使えるようになる
- automatic module は、他の全てのモジュールを `requires` しているものとして扱われるらしいので、ルート・モジュールとして追加されたモジュールは automatic module から参照できるようになるっぽい
    - つまり、 main モジュール（automatic module）から、 `--add-modules` でルート・モジュールに追加した sub モジュールが参照できるようになる、ということらしい

このへん、[神々の Togetter](https://togetter.com/li/1147805) を眺めての想像なので、違ってるかもしれないかもしれなくもなくもないかもしれない。

## automatic module -> automatic module
```text:フォルダ構成
|-main/
| |-class/
| `-src/
|   `-sample/main/
|     `-Main.java
|
`-sub/
  |-class/
  `-src/
    `-sample/sub/
      `-Sub.java
```

```text:subモジュールのコンパイル
# コンパイル
> javac -d sub\class sub\src\sample\sub\Sub.java

# jar 作成
> jar -c -f sub\jar\no_module-sub.jar -C sub\class .
```

```text:mainモジュールのコンパイル
# コンパイル
> javac -d main\class -p sub\jar\no_module-sub.jar --add-modules no.module.sub main\src\sample\main\Main.java

# jar 
> jar -c -f main\jar\no_module-main.jar -C main\class .
```

```text:起動
> java -p sub\jar\no_module-sub.jar;main\jar\no_module-main.jar --add-modules no.module.sub -m no.module.main/sample.main.Main
Sub.method()
```

- こちらも、 `--add-modules` を指定することで実行できる
- main モジュールの方はメインクラスとして読み込んでいるから、 `--add-modules` への指定は不要ということ？

## automatic module -> unnamed module
```text:subモジュールのコンパイル
# コンパイル
> javac -d sub\class sub\src\sample\sub\Sub.java

# jar 作成
> jar -c -f sub\jar\no_module-sub.jar -C sub\class .
```

```text:mainモジュールのコンパイル
# コンパイル
> javac -d main\class -cp sub\jar\no_module-sub.jar main\src\sample\main\Main.java

# jar 
> jar -c -f main\jar\no_module-main.jar -C main\class .
```

```text:実行
> java -p main\jar\no_module-main.jar -cp sub\jar\no_module-sub.jar -m no.module.main/sample.main.Main
Sub.method()
```

- 非モジュール jar を `-cp` でクラスパスに設定することで unnamed module として扱える

## unnamed module -> named module
```text:フォルダ構成
|-main/
| |-class/
| `-src/
|   `-sample/main/
|     `-Main.java
|
`-sub/
  |-class/
  `-src/
    |-module-info.java
    `-sample/sub/
      `-Sub.java
```

```java:sub/src/module-info.java
module sample_module.sub {
    exports sample.sub;
}
```

```text:subモジュールのコンパイル
# コンパイル
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\Sub.java

# jar 
> jar -c -f sub\jar\module-sub.jar -C sub\class .
```

```text:mainモジュールのコンパイル
# コンパイル
> javac -p sub\jar\module-sub.jar --add-modules sample_module.sub -d main\class main\src\sample\main\Main.java

# jar 作成
> jar -c -f main\jar\no_module-main.jar -C main\class .
```

```text:実行
> java -p sub\jar\module-sub.jar -cp main\jar\no_module-main.jar --add-modules sample_module.sub sample.main.Main
Sub.method()
```

- sub モジュールの名前を `--add-modules` で指定すれば動く
- main 側は unnamed module なので、メインクラスの指定が `-m` を使わない旧来の方法になっている


## unnamed module -> automatic module
```text:フォルダ構成
|-main/
| |-class/
| `-src/
|   `-sample/main/
|     `-Main.java
|
`-sub/
  |-class/
  `-src/
    `-sample/sub/
      `-Sub.java
```

```text:subモジュールのコンパイル
# コンパイル
> javac -d sub\class sub\src\sample\sub\Sub.java

# jar 作成
> jar -c -f sub\jar\no_module-sub.jar -C sub\class .
```

```text:mainモジュールのコンパイル
# コンパイル
> javac -p sub\jar\no_module-sub.jar -d main\class --add-modules no.module.sub main\src\sample\main\Main.java

# jar 作成
> jar -c -f main\jar\no_module-main.jar -C main\class .
```

```text:実行
> java -p sub\jar\no_module-sub.jar -cp main\jar\no_module-main.jar --add-modules no.module.sub sample.main.Main
Sub.method()
```

- ファイル名から補完されるモジュール名を `--add-modules` で指定すれば動く


## unnamed module -> unnamed module
```text:subモジュールのコンパイル
# コンパイル
> javac -d sub\class sub\src\sample\sub\Sub.java

# jar 作成
> jar -c -f sub\jar\no_module-sub.jar -C sub\class .
```

```text:mainモジュールのコンパイル
# コンパイル
> javac -d main\class -cp sub\jar\no_module-sub.jar main\src\sample\main\Main.java

# jar 作成
> jar -c -f main\jar\no_module-main.jar -C main\class .
```

```text:実行
> java -cp sub\jar\no_module-sub.jar;main\jar\no_module-main.jar sample.main.Main
Sub.method()
```

- これは、要はモジュールを使わない方法、つまり Java 8 以下で普通にコンパイルして実行しているのと同じ状態になっている

# requires の修飾子
`requires` には次の２つの修飾子のいずれかを設定することができる。

- `transitive`
- `static`

## transitive
```text:モジュールの依存関係
A -> B -> C
```

あるモジュール `B` が `C` に依存しているとする。
別のモジュール `A` が `B` を `requires` で読み込んだときに、 `C` も自動的に `requires` したことにできるようにするのが、この `transitive` 修飾子になる。

### まずは transitive 無しの場合
```text:フォルダ構成
|-A/
| `-src/
|   |-module-info.java
|   `-a/
|     `-A.java
|-B/
| `-src/
|   |-module-info.java
|   `-b/
|     `-B.java
`-C/
  `-src/
    |-module-info.java
    `-c/
      `-C.java
```

**C モジュール**

```java:C.java
package c;

public class C {}
```

```java:C/src/module-info.java
module module_c {
    exports c;
}
```

**B モジュール**

```java:B.java
package b;

import c.C;

public class B {}
```

```java:B/src/module-info.java
module module_b {
    requires module_c;
    exports b;
}
```

**A モジュール**

```java:A.java
package a;

import b.B;
import c.C;

public class A {}
```

```java:A/src/module-info.java
module module_a {
    requires module_b;
    // requires module_c; C モジュールは requires しない状態で試す
    exports a;
}
```

**コンパイル**

```
# C モジュールのコンパイル
> javac -d c\class C\src\module-info.java C\src\c\C.java

# B モジュールのコンパイル
> javac -p c\class -d b\class B\src\module-info.java B\src\b\B.java

# A モジュールのコンパイル
> javac -p c\class;b\class -d a\class A\src\module-info.java A\src\a\A.java
A\src\a\A.java:4: エラー: パッケージcは表示不可です
import c.C;
       ^
  (パッケージcはモジュールmodule_cで宣言されていますが、モジュールmodule_aに読み込まれていません)
エラー1個
```

A モジュールでは C モジュールを明示的に `requires` していないので、パッケージ `c` は参照できない。

### transitive を使う
```java:B\src\module-info.java
module module_b {
    requires transitive module_c;
    exports b;
}
```

- B モジュールの `module-info.java` で `requires` していた C モジュールに `transitive` を追加

```text
# B モジュールのコンパイル
> javac -p c\class -d b\class B\src\module-info.java B\src\b\B.java

# A モジュールのコンパイル
> javac -p c\class;b\class -d a\class A\src\module-info.java A\src\a\A.java
```

- 今度はエラー無くコンパイルできた

## static
`static` は、「コンパイル時には必要だが実行時は任意」なモジュールに対して使用する。

### static が無い場合
```text:フォルダ構成
|-main/
| `-src/
|   |-module-info.java
|   `-sample/main/
|     |-SubFactory.java
|     `-Main.java
`-sub/
  `-src/
    |-module-info.java
    `-sample/sub/
      `-Sub.java
```

```java:Sub.java
package sample.sub;

public class Sub {}
```

```java:sub\src\module-info.java
module sample_module.sub {
    exports sample.sub;
}
```

```java:SubFactory.java
package sample.main;

import sample.sub.Sub;

public class SubFactory {
    public static Sub newSub() {
        return new Sub();
    }
}
```

```java:Main.java
package sample.main;

public class Main {
    public static void main(String... args) {
        System.out.println("Hello World!!");
    }
}
```

```java:main/src/module-info.java
module sample_module.main {
    requires sample_module.sub;
}
```

- main モジュールには sub モジュールのクラスを参照している `SubFactory` というクラスがいる
- このため、コンパイル時には sub モジュールが必要になる
- しかし、 `Main` クラスは `SubFactory` を使用していないため、起動時には sub モジュールは不要ということになる

コンパイルしてみる。

```text:コンパイル
# sub モジュールのコンパイル
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\Sub.java

# main モジュールのコンパイル
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java main\src\sample\main\SubFactory.java
```

コンパイルは通る。
続いて実行してみる。

```
> java -p main\class -m sample_module.main/sample.main.Main
Error occurred during initialization of boot layer
java.lang.module.FindException: Module sample_module.sub not found, required by sample_module.main
```

sub モジュールが見つからなくてエラーになった。

### static をつけた場合
```java:main\src\module-info.java
module sample_module.main {
    requires static sample_module.sub;
}
```

`static` をつけて同じようにコンパイル、実行してみる。

```
# main モジュールのコンパイル
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java main\src\sample\main\SubFactory.java

# 実行
> java -p main\class -m sample_module.main/sample.main.Main
Hello World!!
```

実行できた。

# open
コンパイル時は参照できないが、実行時は参照できるように制御することができる。
これは、リフレクションを使ってコードを参照しているような場合に利用することになる。

パッケージ単位で設定する `opens` と、モジュール全体を設定する `open module` の２つが存在する。

## opens
```text:フォルダ構成
|-main/
| `-src/
|   |-module-info.java
|   `-sample/main/
|     `-Main.java
`-sub/
  `-src/
    |-module-info.java
    `-sample/sub/
      `-Sub.java
```

```java:Sub.java
package sample.sub;

public class Sub {
    public void sub() {
        System.out.println("Sub.sub()");
    }
}
```

```java:sub\src\module-info.java
module sample_module.sub {
    // まずは一切公開せずに作成する
}
```

```java:Main.java
package sample.main;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Main {
    public static void main(String... args) throws Exception {
        Class<?> subClass = Class.forName("sample.sub.Sub");
        Constructor<?> constructor = subClass.getConstructor();
        Object sub = constructor.newInstance();

        Method method = subClass.getMethod("sub");
        method.invoke(sub);
    }
}
```

`sample.sub` パッケージは直接参照せず、リフレクションを使って `Sub.sub()` メソッドを実行している。

```java:main/src/module-info.java
module sample_module.main {
    requires sample_module.sub;
}
```

コンパイルしてみる。

```
# sub モジュールのコンパイル
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\Sub.java

# main モジュールのコンパイル
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java
```

`sample.sub` は、静的には参照されていないため、コンパイルは通る。
しかし、これを実行すると。

```
> java -p sub\class;main\class -m sample_module.main/sample.main.Main
Exception in thread "main" java.lang.IllegalAccessException: class sample.main.Main (in module sample_module.main) cannot access class sample.sub.Sub (in module sample_module.sub) because module sample_module.sub does not export sample.sub to module sample_module.main
        at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
        at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:589)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:479)
        at sample_module.main/sample.main.Main.main(Main.java:10)
```

`IllegalAccessException` がスローされる。

`sample.sub` を `opens` で公開してやり直してみる。

```java:sub\src\module-info.java
module sample_module.sub {
    opens sample.sub;
}
```

```text:コンパイル、実行
# sub モジュールのコンパイル
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\Sub.java

# main モジュールのコンパイル
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java

# 実行
> java -p sub\class;main\class -m sample_module.main/sample.main.Main
Sub.sub()
```

今度はうまく動いた。

ところで、この状態(`opens sample.sub;`)で `Main.java` で `sample.sub` パッケージを参照するようにすると、次のようになる。

```java:Main.java
package sample.main;

import sample.sub.Sub;

public class Main {
    public static void main(String... args) throws Exception {
        new Sub().sub();
    }
}
```

普通に `Sub` を `new` してメソッドを実行している。

```text:コンパイル
# main モジュールのコンパイル
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java
main\src\sample\main\Main.java:3: エラー: パッケージsample.subは表示不可です
import sample.sub.Sub;
             ^
  (パッケージsample.subはモジュールsample_module.subで宣言されていますが、エクスポートされていません)
エラー1個
```

`sample.sub` パッケージはあくまで `opens` であり、 `exports` はされていない。
そのため、コンパイル時に静的に参照しようとするとエラーになる。

それぞれのタイミングでの参照の可否をまとめると、

|参照タイミング|`opens`|`exports`|
|---|---|---|
|コンパイル時|×|○|
|実行時|○|○|

こんな感じ。

## open module
`opens` は特定のパッケージに対する設定だが、 `open module` を指定するとモジュール全体に `opens` を適用した状態になる。

```text:フォルダ構成
|-main/
| `-src/
|   |-module-info.java
|   `-sample/main/
|     `-Main.java
`-sub/
  `-src/
    |-module-info.java
    `-sample/sub/
      |-foo/
      | `-Foo.java
      `-Sub.java
```

```java:Foo.java
package sample.sub.foo;

public class Foo {
    public void foo() {
        System.out.println("Foo.foo()");
    }
}
```

```java:sub/src/module-info.java
open module sample_module.sub {
    exports sample.sub;
}
```

- モジュールの前に `open` を追加している
- `sample.sub` のみ `exports` して、 `sample.sub.foo` パッケージについては何も設定していない
- `Sub.java` は変更なし

```java:Main.java
package sample.main;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import sample.sub.Sub;

public class Main {
    public static void main(String... args) throws Exception {
        new Sub().sub();

        Class<?> clazz = Class.forName("sample.sub.foo.Foo");
        Constructor<?> constructor = clazz.getConstructor();
        Object obj = constructor.newInstance();

        Method method = clazz.getMethod("foo");
        method.invoke(obj);
    }
}
```

- `Sub` クラスは静的に参照してメソッドを実行している
- `Foo` クラスはリフレクションを使ってメソッドを実行している

```text:コンパイルと実行
# sub モジュールのコンパイル
> javac -d sub\class sub\src\module-info.java sub\src\sample\sub\Sub.java sub\src\sample\sub\foo\Foo.java

# main モジュールのコンパイル
> javac -p sub\class -d main\class main\src\module-info.java main\src\sample\main\Main.java

# 実行
> java -p sub\class;main\class -m sample_module.main/sample.main.Main
Sub.sub()
Foo.foo()
```

- `sample.sub.foo.Foo` は `opens` も `exports` も宣言していなかったが、リフレクションから参照できている
- `module` を `open` で修飾することで、そのモジュールに含まれるパッケージが全て `opens` で修飾された状態になる

# use, provides
ServiceLoader の定義が Java 9 からは `module-info.java` でできるようになった。

## 実装
```text:フォルダ構成
|-user/
| `-src/
|   |-module-info.java
|   `-sample/user/
|     `-Main.java
|
`-provider/
  `-src/
    |-module-info.java
    `-sample/provider/
      |-api/
      | `-Foo.java
      `-impl/
        `-FooImpl.java
```

```java:Foo.java
package sample.provider.api;

public interface Foo {
    void foo();
}
```

```java:FooImpl.java
package sample.provider.impl;

import sample.provider.api.Foo;

public class FooImpl implements Foo {
    @Override
    public void foo() {
        System.out.println("FooImpl.foo()");
    }
}
```

```java:provider/src/module-info.java
module sample_module.provider {
    exports sample.provider.api;
    provides sample.provider.api.Foo with sample.provider.impl.FooImpl;
}
```

```java:Main.java
package sample.user;

import java.util.ServiceLoader;
import sample.provider.api.Foo;

public class Main {
    public static void main(String... args) {
        for (Foo foo : ServiceLoader.load(Foo.class)) {
            foo.foo();
        }
    }
}
```

```java:user/src/module-info.java
module sample_module.user {
    requires sample_module.provider;
    uses sample.provider.api.Foo;
}
```

## 動作確認
```text:コンパイル
# provider モジュールのコンパイル
> javac -d provider\class provider\src\module-info.java provider\src\sample\provider\api\Foo.java provider\src\sample\provider\impl\FooImpl.java

# user モジュールのコンパイル
> javac -d user\class -p provider\class user\src\module-info.java user\src\sample\user\Main.java
```

```text:実行
> java -p provider\class;user\class -m sample_module.user/sample.user.Main
FooImpl.foo()
```

## 説明
```java:provider/src/module-info.java
module sample_module.provider {
    exports sample.provider.api;
    provides sample.provider.api.Foo with sample.provider.impl.FooImpl;
}
```

```java:user/src/module-info.java
module sample_module.user {
    requires sample_module.provider;
    uses sample.provider.api.Foo;
}
```

- 「サービスプロバイダ[^1]」を提供する側は、 `provides` を使って「サービス[^2]」に対する「サービスプロバイダ」を指定する
    - `provides <サービス> with <サービスプロバイダ>;`
- 「サービスプロバイダ」が複数ある場合は、 `with` の後ろの「サービスプロバイダ」をカンマ区切りで列挙できる
- `ServiceLoader` を使う側は、 `uses` を使って、利用する「サービス」を指定する
    - `uses <サービス>;`
- あとは、通常の `ServiceLoader` と同じ方法で利用する

OSS のフレームワークのコードを読むときとかに、宣言方法が増えていることに注意しないといけなさそう。


[^1]: 「サービス」を実装したクラス
[^2]: インターフェースや抽象クラスなど

# jlink
jlink コマンドを使用すると、必要最低限のモジュールだけを集めた Java ランタイム（サブセット）を作成することができる。
配布先に JRE がインストールされていなくても、 jlink で作ったサブセットを入れればアプリケーションを起動できるようになる。

## 実装
```text:フォルダ構成
`-src/
  |-module-info.java
  `-sample/jlink/
    `-Main.java
```

```java:Main.java
package sample.jlink;

public class Main {
    public static void main(String... args) {
        System.out.println("Hello jlink!!");
    }
}
```

```java:module-info.java
module sample_module.jlink {
}
```

## サブセットを作成する
```
# コンパイル
> javac -d class src\module-info.java src\sample\jlink\Main.java

# jar 作成
> jar -c -f jar\module-jlink.jar -C class .

# jlink でランタイムを作成
> jlink -p .\jar;%JAVA_HOME%\jmods --add-modules sample_module.jlink --output .\output
```

- `jlink` コマンドでは、最低限以下を指定する
    - `-p` (`--module-path`)
        - 組み込むモジュールが存在するパスを指定する
        - 標準ライブラリのモジュールも必要になるので、 JDK9 のインストールフォルダ(`%JAVA_HOME%`)の下の `jmods` フォルダもモジュールパスに追加する
    - `--add-modules`
        - ルート・モジュールを指定するらしい
        - ルート・モジュールが何なのかよくわかっていないが、たぶんモジュールの依存関係のグラフを構築するときの起点となるモジュールな気がする
        - ここを指定すれば、あとは芋づる式に必要なモジュールが検索されて、作成するランタイムに組み込まれることになるのだと思う
    - `--output`
        - 出力先のフォルダのパスを指定する
        - 既にフォルダが存在するとエラーになるので、存在しないパスを指定する


以下のように、 `output` フォルダの下にランタイム入りのアプリケーションが出力される。

```
`-output/
  |-bin/
  |-conf/
  |-include/
  |-legal/
  |-lib/
  `-release
```

`bin` の下には `java.exe` ファイルが配置されており、これがサブセット用の `java` コマンドとなっている。

```
# サブセットの bin に移動し
> cd output\bin

# 含まれているモジュールを確認
> java --list-modules
java.base@9
sample_module.jlink
```

サブセットには、必要最小限のモジュール（`java.base` と `sample_module.jlink`）しか含まれていない。

プログラムを起動してみる。

```
> java -m sample_module.jlink/sample.jlink.Main
Hello jlink!!
```

`sample_module.jlink` はランタイムの中に組み込まれているので、 `-p` で指定する必要はない。

ちなみに、作成されたランタイムのサイズは Windows のエクスプローラで確認したところ 35.9 MB だった。

## 圧縮する
オプションである程度圧縮が可能。

```
> jlink --compress=2 -p .\jar;%JAVA_HOME%\jmods --add-modules sample_module.jlink --output .\output
```

- `--compress=N` で圧縮指定が可能。
- `N` には `0`, `1`, `2` のいずれかを指定できる
- ヘルプでは次のように説明されている（Level 1 はよくわからん）

```
-c, --compress=<0|1|2> Enable compression of resources:
                       Level 0: No compression
                       Level 1: Constant string sharing
                       Level 2: ZIP
```

- ちなみに、サイズは 24.2 MB になった

## 起動ファイルを生成する
起動の際に `-m` でメインクラスを指定する必要があるのは、 Java に疎い人に配布するときのことを考えるとあまりよろしくない。
その場合は、起動用のスクリプトファイルを生成しておくと便利。

```
> jlink --launcher jlink-hello=sample_module.jlink/sample.jlink.Main  -p .\jar;%JAVA_HOME%\jmods --add-modules sample_module.jlink --output .\output
```

- `--launcher <コマンド名>=<メインクラス>` と指定する
- `output\bin` の下に `<コマンド名>` で指定した起動スクリプトが出力されるので、それを実行すればプログラムが起動する

```
# bin の下に移動して
> cd output\bin

# 起動スクリプトを実行
> jlink-hello.bat
Hello jlink!!
```

生成されたスクリプトファイルは、以下のような内容になっている。

```bat:jlink-hello.bat
@echo off
set JLINK_VM_OPTIONS=
set DIR=%~dp0
"%DIR%\java" %JLINK_VM_OPTIONS% -m sample_module.jlink/sample.jlink.Main %*
```



一応 Linux 用のシェルスクリプトも出力されていたけど、起動できるのだろうか？

# 依存モジュールを調べる
`jdeps` コマンドを使うと、 jar ファイルがどのモジュールに依存しているかを調べることができる。
既存の非モジュール jar を追加するときに、標準ライブラリのどのモジュールを追加すればいいかが分かるようになる。

試しに、 Apache Commons lang-3 を調べてみる。

```
> jdeps -s commons-lang3-3.6.jar
commons-lang3-3.6.jar -> java.base
commons-lang3-3.6.jar -> java.desktop
```

`-s` オプションをつけて jar を食わせると、依存するモジュールを表示してくれる。

[java.desktop](https://docs.oracle.com/javase/9/docs/api/java.desktop-summary.html) は、 awt や swing が含まれるモジュールだけど、 beans とかがあるのでそれかなぁ。

jar が named module の場合は、依存するモジュールを `--module-path` で指定する必要がある（まぁ、当然なんだろうけど、なんか依存モジュールを調べるために依存モジュールを用意するところが変な感じがする）。


# 参考
- [最新Java情報局 - Java SE 9を先取り、Project Jigsawでモジュールを作成する：ITpro](http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/042000011/?itp_leaf_index)
- [最新Java情報局 - Java SE 9、Project Jigsawの標準モジュールと依存性の記述：ITpro](http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/050900012/)
- [最新Java情報局 - Project JigsawによるJREのカスタマイズ：ITpro](http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/062200015/?itp_leaf_index&rt=nocnt)
- [The Java® Language Specification](https://docs.oracle.com/javase/specs/jls/se9/html/index.html)
- [Getting Started with Java 9 Modules - ConSol Labs](https://labs.consol.de/development/2017/02/13/getting-started-with-java9-modules.html)
- [ModuleFinder (Java SE 9 & JDK 9 [build 176])](http://download.java.net/java/jigsaw/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path...-)
- [Project JigawにおけるUnnamed Moduleと通常のModuleの共存 - Togetterまとめ](https://togetter.com/li/1147805)
- [ykubota/jigsaw-sample-accessibility: Example program to run Modular JAR with Java 8's JAR.](https://github.com/ykubota/jigsaw-sample-accessibility)
- [ServiceLoaderでプラグインの仕組みをさくっと作る。 - うなの日記](http://unageanu.hatenablog.com/entry/20090326/1238073098)
- [JDK9のモジュールとjlinkでアプリ配布向けのJVMを作る - Qiita](https://qiita.com/koduki/items/5a1b5e5da95a21935d18)