モジュールグラフが作られる様子を学ぼう

## 自己紹介
<img src="https://qiita-image-store.s3.amazonaws.com/0/28302/63d28421-104b-3066-2712-768e6aded5f7.jpeg" width="200px" />

- opengl-8080
- 主に Qiita で技術メモを書いたり
- 関西の SIer 勤務

---
## 現場はまだまだ Java 8 が多い？
- Java 9 以降の変化に警戒
    - リリースサイクルの変更
    - サポートポリシーの変更
- Java 8 でしのぐ
- その Java 8 も、来年１月で無償のサポートが終了
- Java 11 からは LTS 付きの JDK がいくつか
    - Oracle JDK
    - AdoptOpenJDK
    - Amazon Correto
    - etc ?

---
## そろそろ本格的に Java 11 へ移行？

---
## 立ちはだかるモジュールシステム

---
## ビルド設定の不備に起因するエラーが増えた
- Java 8 以前
    - 「パッケージ...は存在しません」
    - 「シンボルを見つけられません」
    - `java.lang.ClassNotFoundException`
    - `java.lang.NoClassDefFoundError`
    - `java.lang.NoSuchMethodError`
- Java 9 以降（上記に加え。。。）
    - 「モジュールが見つかりません: ...」
    - 「パッケージ...は表示不可です」
        - 「パッケージ...はモジュール...で宣言されていますが、エクスポートされていません」
        - 「パッケージ...はモジュール...で宣言されていますが、モジュールfooに読み込まれていません」
        - 「パッケージ...はモジュール...で宣言されていますが、モジュール・グラフにありません」
    - `java.lang.module.FindException`
    - `java.lang.IllegalAccessException`

---
## Java 8 以前のビルドエラーの原因
- クラスパスに必要な jar がない
- クラスパスに入れている jar のバージョンが間違っている

だいたい、クラスパスの設定を調べれば解決した（要出典）。

---
## Java 9 で追加されたモジュールシステム
- クラスをロードするときの仕組みが変わった
- コンパイラや JVM は、アプリケーションが利用するモジュールの情報を集めて、各モジュールの関係を解析する
- このステップを **モジュールの解決** と呼び、モジュール間の関係を表したものを **モジュールグラフ** と呼ぶ

---
## Java 9 以降のビルドエラーの原因
- 設定漏れなどにより、モジュールの解決が正常に完了できなかった
- 解決されたモジュールグラフが必要な形を満たしていない


---
## モジュールグラフの作られ方を理解していないと、<br>エラーの原因が特定しづらい（気がする）

---
## モジュールグラフが作られる様子を学ぼう

---
## 前提（詳しくは話さないこと、知ってる前提）
- モジュールシステムが導入された背景やメリット、使い方など
- Java 8 以前のクラスパスとかの説明
- Oracle JDK と OpenJDK の違いや、ここ半年ほどの変遷
- Java 9 以降の新規機能（jlink とか）

---
## モジュールシステムでできること
- 複数のパッケージを **モジュール** というカタマリにまとめられる
- モジュール間の依存関係を定義できる
- パッケージ単位で外部への公開・非公開を制御できる

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/c543c815-e46e-8cc4-118e-f81cc34cad49.jpeg)

---
## モジュールの定義
```java:module-info.java
module foo {      // foo モジュールの定義
    exports foo;  // foo パッケージの公開
    requires bar; // bar モジュールへの依存を宣言
}
```

- `module-info.java(class)` ファイルをモジュール内のルートに配置
- モジュール名、公開するパッケージ、依存するモジュールなどを宣言できる

---
## モジュールを使った場合の大前提
あるモジュール内のクラスが別のモジュールのクラスを使用するためには、次の条件を満たしている必要がある。

- 対象のクラスが存在するモジュールを**直接** `requires` している
- 対象のクラスが存在するパッケージが `exports` されている

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/af9d7be0-b0eb-0752-59c2-9f36701f6782.jpeg)

※`java.base` モジュールは自動的に `requires` されるので明示的な宣言が不要

---
## 推移的な requires
```java:module-info.java
module bar {
    requires transitive fuga; // transitive 修飾子をつける
}
```

- `requires` に `transitive` 修飾子をつけると、 `requires` が伝播するようになる
- この場合、 `bar` を `requires` すると、自動的に `fuga` も `requires` したことになる

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/7300e6d6-4324-b428-4e16-6ebce457b6f6.jpeg)


---
## モジュールグラフ
- モジュール間の関係を表したもの
- コンパイルや JVM 起動時に、このモジュールグラフが作られる

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/0ecdb0ba-4333-e46d-e35e-71e4085b44f1.jpeg)

---
## ルートモジュールを決める
- モジュールグラフの起点となるモジュール = **ルートモジュール** を決める

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/6833af03-7e99-6c79-ddcb-116b5cccf059.jpeg)

---
## コンパイル時と JVM 起動時でルートモジュールは異なる
### コンパイル時
- コンパイル対象のソース（モジュール）

### JVM 起動時
- `--module` (`-m`) オプションで指定したモジュール
- メインクラスの指定というよりも、先頭のモジュールを指定するためのオプション

> module[/mainclass]
> 
> **解決する初期moduleの名前を指定します。**
> また、moduleで指定されていない場合は、実行するmainclassの名前を指定します。

[java | Java Platform, Standard Editionツール・リファレンス](https://docs.oracle.com/javase/jp/10/tools/java.htm)

---
## ルートモジュールから依存関係を辿る
- ルートモジュールが決まったら、 `module-info` の内容を確認する
- 依存している(`requires` している) モジュールを調べ、モジュールグラフを構築していく

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/66701de6-73d0-887a-4865-c0970e3b1d13.jpeg)

---
## 観測可能なモジュール
- モジュールを検索してくる場所
- ルートモジュールおよび `requires` しているモジュールは、 **観測可能なモジュール** でなければならない
- 大きく２つ存在する
    - システムモジュール
    - モジュールパス
- 厳密には他にも観測可能なモジュールの候補は存在するが、ここでは割愛（詳細は [Javadoc などを参照](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/package-summary.html#package-summary.10Q8q2) ）

---
## システムモジュール
- 実行環境に組み込まれているモジュール
- `java.base`, `java.desktop`, `java.sql` などなど
- 固定ではない
    - jlink で作られたカスタム JRE
    - Oracle Java 9, 10 では JavaFX 系のモジュールが含まれていた
- [`ModuleFinder.ofSystem()`](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/ModuleFinder.html#ofSystem()) で一覧を取得可能

---
## モジュールパス
- `--module-path` (`-p`) で指定
- システムモジュールに存在しないモジュールは、モジュールパスに追加しなければ `requires` できない
    - アプリケーション自身
    - サードパーティのライブラリ

---
## モジュールグラフの話を整理
![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/35d1af62-bc3b-4cf7-b95b-6502aba85959.jpeg)

- モジュールグラフとは、モジュール間の関係を表したもの
- ルートモジュールを起点にして、 `requires` しているモジュールを紐づけていく
- ルートモジュールは、コンパイル時は対象のソースが、実行時は `--module` で指定したモジュールがなる
- ルートモジュールおよび `requires` するモジュールは、観測可能なモジュールでなければならない
- 代表的な観測可能なモジュールとして、システムモジュールとモジュールパスに含まれるモジュールがある

---
## モジュールの解決の様子を確認する
- `java` コマンドのオプションに `--show-module-resolution` を追加すると、モジュールの解決の様子を確認できる

```bash
$ java --show-module-resolution \ # モジュールの解決の様子を出力するオプション
       --module-path ... \
       --module ...
```

```bash:実行結果
root foo file:///.../foo/classes/ # ルートモジュール
foo requires bar file:///.../bar/classes/ # requires をたどっている
java.base binds java.desktop jrt:/java.desktop # サービスプロバイダの解決
java.base binds jdk.jartool jrt:/jdk.jartool
java.base binds jdk.jlink jrt:/jdk.jlink
java.base binds jdk.compiler jrt:/jdk.compiler
java.base binds jdk.javadoc jrt:/jdk.javadoc
java.base binds jdk.jdeps jrt:/jdk.jdeps
java.base binds jdk.localedata jrt:/jdk.localedata
java.base binds java.security.sasl jrt:/java.security.sasl
java.base binds jdk.crypto.mscapi jrt:/jdk.crypto.mscapi
java.base binds java.security.jgss jrt:/java.security.jgss
java.base binds java.smartcardio jrt:/java.smartcardio
java.base binds java.xml.crypto jrt:/java.xml.crypto
java.base binds jdk.crypto.cryptoki jrt:/jdk.crypto.cryptoki
java.base binds jdk.security.jgss jrt:/jdk.security.jgss
java.base binds java.naming jrt:/java.naming
java.base binds jdk.crypto.ec jrt:/jdk.crypto.ec
java.base binds jdk.zipfs jrt:/jdk.zipfs
java.base binds jdk.charsets jrt:/jdk.charsets
java.base binds java.logging jrt:/java.logging
java.base binds java.management jrt:/java.management
java.base binds jdk.security.auth jrt:/jdk.security.auth
jdk.security.auth requires java.naming jrt:/java.naming # サービスプロバイダとして読み込まれたモジュールの requires をたどっている
jdk.security.auth requires java.security.jgss jrt:/java.security.jgss
java.naming requires java.security.sasl jrt:/java.security.sasl
jdk.security.jgss requires java.security.jgss jrt:/java.security.jgss
jdk.security.jgss requires java.logging jrt:/java.logging
jdk.security.jgss requires java.security.sasl jrt:/java.security.sasl
jdk.crypto.cryptoki requires jdk.crypto.ec jrt:/jdk.crypto.ec
java.xml.crypto requires java.logging jrt:/java.logging
java.xml.crypto requires java.xml jrt:/java.xml
java.security.jgss requires java.naming jrt:/java.naming
java.security.sasl requires java.logging jrt:/java.logging
jdk.jdeps requires jdk.compiler jrt:/jdk.compiler
jdk.jdeps requires java.compiler jrt:/java.compiler
jdk.javadoc requires jdk.compiler jrt:/jdk.compiler
jdk.javadoc requires java.xml jrt:/java.xml
jdk.javadoc requires java.compiler jrt:/java.compiler
jdk.compiler requires java.compiler jrt:/java.compiler
jdk.jlink requires jdk.internal.opt jrt:/jdk.internal.opt
jdk.jlink requires jdk.jdeps jrt:/jdk.jdeps
java.desktop requires java.datatransfer jrt:/java.datatransfer
java.desktop requires java.xml jrt:/java.xml
java.desktop requires java.prefs jrt:/java.prefs
java.prefs requires java.xml jrt:/java.xml
java.datatransfer binds java.desktop jrt:/java.desktop
java.management binds jdk.management.jfr jrt:/jdk.management.jfr
java.management binds jdk.management jrt:/jdk.management
java.management binds java.management.rmi jrt:/java.management.rmi
java.desktop binds jdk.accessibility jrt:/jdk.accessibility
java.desktop binds jdk.unsupported.desktop jrt:/jdk.unsupported.desktop
java.compiler binds jdk.javadoc jrt:/jdk.javadoc
java.compiler binds jdk.compiler jrt:/jdk.compiler
java.naming binds jdk.naming.rmi jrt:/jdk.naming.rmi
java.naming binds jdk.naming.dns jrt:/jdk.naming.dns
jdk.naming.dns requires java.naming jrt:/java.naming
jdk.naming.rmi requires java.rmi jrt:/java.rmi
jdk.naming.rmi requires java.naming jrt:/java.naming
jdk.unsupported.desktop requires java.desktop jrt:/java.desktop
jdk.accessibility requires java.desktop jrt:/java.desktop
java.management.rmi requires java.management jrt:/java.management
java.management.rmi requires java.rmi jrt:/java.rmi
java.management.rmi requires java.naming jrt:/java.naming
jdk.management requires java.management jrt:/java.management
jdk.management.jfr requires java.management jrt:/java.management
jdk.management.jfr requires jdk.management jrt:/jdk.management
jdk.management.jfr requires jdk.jfr jrt:/jdk.jfr
java.rmi requires java.logging jrt:/java.logging
```

---
## 下位互換の話が入ってくるとややこしくなる

---
## Java 8 以前のコードを Java 9 以上で動かす
```bash
$ java --classpath path/to/classes foo.bar.MainClassName
```

- Java 8 以前同様、 jar ファイルなどをクラスパスを追加して起動する
- クラスパスは引き続き存在している
- 表面上は変わっていないが、内部の動きは大きく変わっている

---
## 無名モジュール（unnamed module）
- クラスパスから読み込まれたクラスは、**無名モジュール** に所属するようになる
    - モジュール定義（module-info）を持つ通常のモジュールは、**名前付きモジュール**（named module）と呼ぶ
- 無名モジュールはモジュール定義を持っていない（持てない）
    - 全てのパッケージを `exports` している扱いになる
    - モジュールグラフ上の全てのモジュールを `requires` している扱いになる

---
## 無名モジュールが起点となった場合
無名モジュールが起点（コンパイル対象, Main クラス）になった場合、**ルートモジュールの決定方法が変わる**。
ザックリ言うと、**システムモジュール内のほぼ全てのモジュールがルートになる。**

厳密なルールは以下。

- `--upgrade-module-path` で指定した場所に存在するモジュール
    - 任意のモジュールの内容を上書きするオプション
- システムモジュール内に存在し、 `to` による公開先の制限なしで最低１つはパッケージを `exports` しているモジュール
    - `exports` は `to` で公開先のモジュールを制限できる
    - ルートになるのは、 `to` による制限のないモジュールが１つ以上存在するモノのみ

※Java 10 まではもっと複雑だった。詳細は [モジュールシステムを学ぶ - Qiita](https://qiita.com/opengl-8080/items/93c8e0cf58654d5f73cb#%E7%84%A1%E5%90%8D%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%8C%E8%B5%B7%E7%82%B9%E3%81%A8%E3%81%AA%E3%81%A3%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%88%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E6%B1%BA%E5%AE%9A2018-11-04-%E6%9B%B4%E6%96%B0) を参照。

---
## 無名モジュールで起動したときのモジュールの解決を見る
```bash
$ java --show-module-resolution \
       --classpath ... \
       ...
```

```text:実行結果
root java.sql jrt:/java.sql
root jdk.management.jfr jrt:/jdk.management.jfr
root java.rmi jrt:/java.rmi
root jdk.jdi jrt:/jdk.jdi
root java.transaction.xa jrt:/java.transaction.xa
root java.logging jrt:/java.logging
root java.xml.crypto jrt:/java.xml.crypto
root java.xml jrt:/java.xml
root jdk.xml.dom jrt:/jdk.xml.dom
root jdk.jfr jrt:/jdk.jfr
root java.datatransfer jrt:/java.datatransfer
root jdk.httpserver jrt:/jdk.httpserver
root jdk.net jrt:/jdk.net
root java.desktop jrt:/java.desktop
root java.naming jrt:/java.naming
root java.prefs jrt:/java.prefs
root java.net.http jrt:/java.net.http
root jdk.compiler jrt:/jdk.compiler
root java.security.sasl jrt:/java.security.sasl
root jdk.jconsole jrt:/jdk.jconsole
root jdk.attach jrt:/jdk.attach
root java.base jrt:/java.base
root jdk.javadoc jrt:/jdk.javadoc
root jdk.jshell jrt:/jdk.jshell
root java.sql.rowset jrt:/java.sql.rowset
root jdk.sctp jrt:/jdk.sctp
root jdk.jsobject jrt:/jdk.jsobject
root java.management jrt:/java.management
root jdk.unsupported jrt:/jdk.unsupported
root java.smartcardio jrt:/java.smartcardio
root jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
root java.instrument jrt:/java.instrument
root java.security.jgss jrt:/java.security.jgss
root jdk.management jrt:/jdk.management
root jdk.security.auth jrt:/jdk.security.auth
root java.compiler jrt:/java.compiler
root java.scripting jrt:/java.scripting
root jdk.dynalink jrt:/jdk.dynalink
root jdk.unsupported.desktop jrt:/jdk.unsupported.desktop
root jdk.accessibility jrt:/jdk.accessibility
root jdk.jartool jrt:/jdk.jartool
root java.management.rmi jrt:/java.management.rmi
root jdk.security.jgss jrt:/jdk.security.jgss
jdk.security.jgss requires java.security.sasl jrt:/java.security.sasl
（以下略）
```

システムモジュール内の様々なモジュールがルートとして読み込まれているのがわかる。

---
## 互換性が保たれる仕組み
- システムモジュール内のモジュールは、ほぼ全てルートモジュールになる（モジュールグラフに追加される）
- 無名モジュールは、モジュールグラフ内の全てのモジュールを `requires` する

結果、無名モジュールは基本的に全てのモジュールを利用できるようになり、互換性が保たれる。

---
## --add-modules で任意のモジュールをルートに追加する
- Java 9, 10 の頃は、システムモジュールにあってもルートにならないモジュールがあった
    - Java EE 関係のモジュール
    - `java.transaction`, `java.xml.bind` など
    - Java 11 で削除された
- これらのモジュールに依存しているアプリケーションをクラスパスを使ってビルドすると、コンパイルが通らない
    - 無名モジュールはモジュールグラフ上のモジュールを自動的に `requires` する
    - 言い替えると、モジュールグラフに存在しないモジュールは `requires` されない（参照できない）
    - Java EE 関係のモジュールはルートにならなかったので、そのままだと `requires` されない

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/7f7da961-c280-ac40-f378-34678204b3a0.jpeg)

- `--add-modules` オプションを使うと、観測可能なモジュール内の任意のモジュールをルートモジュールに追加できる

```bash
$ javac --add-modules java.xml.bind \
        --classpath ...
```

![jjug-ccc-2018.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/45c349d0-4ab0-8374-df10-3cc2e7bc0f3c.jpeg)

- Java EE 関係のモジュールは Java 11 で削除されたので、 11 以降は普通にサードパーティのライブラリ扱いで利用すればいい
- クラスパスに jar を追加すれば、アプリケーションのコードからは参照できるようになる（`--add-modules` は不要）

---
## まとめ
### モジュールグラフの作られ方
1. **ルートモジュール**を決める
    - コンパイル対象 or `--module` で指定したモジュール
    - `--add-modules` で指定したモジュール
2. ルートモジュールから `requires` をたどってモジュールグラフを構築する

### 観測可能なモジュール
- ルートモジュールおよび `requires` するモジュールは、**観測可能なモジュール**でなければならない
    - システムモジュール
    - モジュールパス

### 他のモジュールのクラスを利用できる条件
- そのモジュールを `requires` している
- そのクラスが所属するパッケージが `exports` されている

### 互換の話が入るとちょっとややこしい
- 無名モジュール
    - クラスパスから読み込んだクラスが所属するモジュール
    - 無名モジュールが起点になると、ルートモジュールの決定方法が変わる
    - 全てのパッケージを `exports` し、モジュールグラフ上の全てのモジュールを `requires`

---
## エラーとの関係
前述のルールから外れた設定があると、モジュール絡みのエラーが発生する。

### モジュールが見つかりません
- 観測可能でないモジュールを利用しようとした（コンパイル時）
    - `--add-modules` で指定したモジュールが存在しない
    - `--module` で指定したモジュールが存在しない

### パッケージ...は表示不可です
#### パッケージ...はモジュール...で宣言されていますが、エクスポートされていません
- `import` しているパッケージが `exports` されていない

#### パッケージ...はモジュール...で宣言されていますが、モジュール...に読み込まれていません
- `import` しているパッケージが所属するモジュールを `requires` で読み込んでいない

#### パッケージ...はモジュール...で宣言されていますが、モジュール・グラフにありません
- 無名モジュール内で `import` しているパッケージを含むモジュールが、モジュールグラフ上に存在しない
- 観測可能なモジュールには存在している

### java.lang.module.FindException
- 観測可能でないモジュールを利用しようとした（実行時）

### java.lang.IllegalAccessException
- 参照できないクラスにリフレクションでアクセスしようとした
    - `requires` していないモジュール
    - `exports` されていないパッケージのクラス

---
## より詳細な話
- [Jigsaw 勉強メモ - Qiita](https://qiita.com/opengl-8080/items/1007c2b2543c2fe0d7d5)
- [モジュールシステムを学ぶ - Qiita](https://qiita.com/opengl-8080/items/93c8e0cf58654d5f73cb)