モジュールシステムを学ぶ

今まで何となくで使ってた Jigsaw (モジュールシステム)をしっかり学ぶ。

# 環境
## OS
Windows 10

## Java
java 10.0.1


# モジュールの解決
[モジュールの解決 | java.lang.module (Java SE 10 & JDK 10 )](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/package-summary.html#ModuleResolution)

Java 9 で追加されたモジュールシステムでは、複数のパッケージやリソースを**モジュール**と呼ばれる単位でまとめることができるようになった。

モジュールでは、次のようなことを定義できる[^1]。

[^1]: 他にも定義できることはあるが、ここでは話を単純にするため基本となるこの２つの情報に絞る

- どのパッケージを外部から見えるようにするか
- 他のどのモジュールに依存するか

これらは `module-info.java` という専用のファイルを使い、次のように記述する。

```java:module-info.java
module foo {
    exports foo.api;
    requires bar;
}
```

この例では、 `foo` というモジュールを定義している。
そして、 `exports foo.api` で `foo.api` パッケージを外部に公開し、 `requires bar` で `bar` モジュールに依存していることを宣言している。

コンパイルしたりプログラムを起動すると、この定義情報がまず最初に読み取られる[^2]。
そして、各モジュールがどのような関係になっているかが分析される。

[^2]: 厳密には module-info.java だけでなく、実行時のオプションで定義を追加したり書き換えたりすることも可能

これを**モジュールの解決 (Resolution)**と呼び、非常に重要なステップとなっている。

このモジュールの解決によって得られた情報をもとに、コードの妥当性のチェックやクラスの検索といった処理が行われる。
つまり、モジュールの解決がどのように行われるかを理解しておかないと、モジュール絡みのエラーが発生したときに何が原因なのかあたりをつけることが困難になる。

# モジュールグラフ
例えば、次のようなモジュール達が定義されていたとする。

```java:fooモジュールのmodule-info.java
module foo {
    requires bar;
}
```

```java:barモジュールのmodule-info.java
module bar {
    requires fizz;
    requires buzz;
}
```

`foo` は、 `bar` モジュールに依存し、 `bar` モジュールは `fizz`, `buzz` モジュールに依存している。

これらのモジュールの関係を図にすると、下のような有向グラフができあがる[^3]。

[^3]: `java.base` の話は今は割愛

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/ff53d1df-7666-ab3d-ea6e-f2691c0ecb94.jpeg)

このようにモジュール同士の依存関係を表現したものを、**モジュールグラフ**と呼ぶ。

矢印は `requires` の関係を表現している。
あるモジュールが他のモジュールを参照するためには、この矢印が直接つながっていなければならない[^4]。

[^4]: パッケージにアクセスするためには、さらにそのパッケージが `exports` されている必要がある

つまり、 `foo` モジュールは `bar` モジュールと直接つながっているので参照できるが、 `fizz`, `buzz` モジュールとはつながっていないので参照はできないということになる。

モジュールの解決周りの問題に遭遇した場合は、このモジュールグラフの構築が意図通りになっているかを調べることで原因が特定しやすくなると思う。

# 間接エクスポート
`foo` モジュールは `fizz`, `buzz` モジュールを直接 `requires` していないため、参照することはできない。

しかし、もし `bar` が `fizz`, `buzz` を**間接エクスポート(Indirect Exports)**している場合は、 `foo` も `fizz`, `buzz` を参照できる。

```java:barが間接エクスポートしている場合のmodule-info.java
module bar {
    requires transitive fizz;
    requires transitive buzz;
}
```

`requires` に `transitive` をつけている。
これにより、`bar` を `requires` したモジュールは `fizz`, `buzz` モジュールも `requires` したことになる。

つまり、

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/fe155b42-ffee-4540-aefb-91527741bfdc.jpeg)

は、

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/888a47b7-459e-6f96-c2d9-8f0c1d4b6662.jpeg)

と同じことになる。


# java.base モジュール
標準 API の中にある `java.base` モジュールは、少し特別扱いとなっている。

このモジュールには `java.lang` など、 Java を実行するうえで基礎となるパッケージが含まれている。
このため、このモジュールに限っては `requires` を明示していなくても常に `requires` している扱いとなる。

つまり、上述のモジュールグラフの図は、 `java.base` も含めると次のようになる。

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/97c487f9-8d6c-b193-e378-8c53673ff35c.jpeg)



# ルート・モジュールの決定
[ルート・モジュール | パッケージ java.lang.module](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/package-summary.html#package-summary.3DVcaH)

モジュールの解決でモジュールグラフを構築するには、まずはじめにグラフの先頭となるモジュールを決定する。
これを**ルート・モジュール**と呼ぶ。

ルート・モジュールは、コンパイル時と実行時でそれぞれ次のように決まる。

**コンパイル時**

コンパイル対象のモジュール

**実行時**

`--module` (`-m`) オプションで指定したモジュール

## --module の意味
Jigsaw を触りたての頃、 `--module` は「メインクラスを指定するためのオプション」くらいの認識だった[^5]。

[^5]: だからこそ、「なんで `--main` とかじゃなくて `--module` なんだろう」みたいなモヤモヤがあった

しかし、モジュールの解決について勉強しなおすなかで、 `java` コマンドのリファレンスで次のように説明されていることに気付いた。

[java | Oracle Help Center](https://docs.oracle.com/javase/10/tools/java.htm#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE__GUID-5F97A247-EF3F-4381-84C4-AC88723DBDED)

> module[/mainclass]
> **Specifies the name of the initial module to resolve** and, if it isn’t specified by the module, then specifies the name of the mainclass to execute. 
> 
> 【訳】
> **（モジュールの）解決のために初期モジュールの名前を指定する**。そして、もしモジュールによって（メインクラスが）指定されていない場合は、実行するメインクラスの名前を指定する。

メインクラスを指定する目的ももちろんあるが、それ以上に初期モジュール（ルート・モジュール）を決めるという重要な役割を持っているのが、この `--module` ということを知った。

# 観測可能なモジュール
[観測可能なモジュール | パッケージ java.lang.module](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/package-summary.html#package-summary.10Q8q2)

ルート・モジュールが決定したら、次はそのルート・モジュールの `module-info.java` を見て[^6]依存するモジュールを辿っていく。

[^6]: 実行時なら `module-info.class`

このとき、依存するモジュールは次の４つの場所から順番に検索されていく。

1. `--module-source-path` で指定されたモジュール（コンパイル時のみ）
2. `--upgrade-module-path` で指定されたモジュール（詳細後述）
3. システムモジュール
    - ランタイムが用意している組み込みのモジュール
    - `java.*` とか `jdk.*` のモジュール
        - 固定で決まっているものではない
        - jlink で作られたカスタムのランタイムなら標準の状態よりも少なくなることがありえる
4. アプリケーションモジュール
    - `--module-path` で指定した場所に存在するモジュール

これら検索可能な場所に存在するモジュールを総称して、**観測可能なモジュール (Observable modules)**と呼ぶ。

`requires` で指定するモジュールは、**この観測可能な範囲に存在していなければならない**。
もし観測可能でないモジュールを `requires` しようとすると、コンパイルまたは実行時にエラーになる。

## システムモジュールを調べる
```java
package sample;

import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;

public class Main {
    public static void main(String[] args) {
        ModuleFinder
            .ofSystem()
            .findAll()
            .stream()
            .map(ModuleReference::descriptor)
            .map(ModuleDescriptor::name)
            .sorted()
            .forEach(System.out::println);
    }
}
```

```text:実行結果
java.activation
java.base
java.compiler
java.corba
java.datatransfer
java.desktop
java.instrument
java.jnlp
java.logging
java.management
java.management.rmi
java.naming
java.prefs
java.rmi
java.scripting
java.se
java.se.ee
java.security.jgss
java.security.sasl
java.smartcardio
java.sql
java.sql.rowset
java.transaction
java.xml
java.xml.bind
java.xml.crypto
java.xml.ws
java.xml.ws.annotation
javafx.base
javafx.controls
javafx.deploy
javafx.fxml
javafx.graphics
javafx.media
javafx.swing
javafx.web
jdk.accessibility
jdk.aot
jdk.attach
jdk.charsets
jdk.compiler
jdk.crypto.cryptoki
jdk.crypto.ec
jdk.crypto.mscapi
jdk.deploy
jdk.deploy.controlpanel
jdk.dynalink
jdk.editpad
jdk.hotspot.agent
jdk.httpserver
jdk.incubator.httpclient
jdk.internal.ed
jdk.internal.jvmstat
jdk.internal.le
jdk.internal.opt
jdk.internal.vm.ci
jdk.internal.vm.compiler
jdk.internal.vm.compiler.management
jdk.jartool
jdk.javadoc
jdk.javaws
jdk.jcmd
jdk.jconsole
jdk.jdeps
jdk.jdi
jdk.jdwp.agent
jdk.jfr
jdk.jlink
jdk.jshell
jdk.jsobject
jdk.jstatd
jdk.localedata
jdk.management
jdk.management.agent
jdk.management.cmm
jdk.management.jfr
jdk.management.resource
jdk.naming.dns
jdk.naming.rmi
jdk.net
jdk.pack
jdk.packager
jdk.packager.services
jdk.plugin
jdk.plugin.server
jdk.rmic
jdk.scripting.nashorn
jdk.scripting.nashorn.shell
jdk.sctp
jdk.security.auth
jdk.security.jgss
jdk.snmp
jdk.unsupported
jdk.xml.bind
jdk.xml.dom
jdk.xml.ws
jdk.zipfs
oracle.desktop
oracle.net
```

- [ModuleFinder.ofSystem()](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/ModuleFinder.html#ofSystem()) メソッドを使えば、システムモジュールを検索する `ModuleFinder` を取得できる
- これらのモジュールはランタイムに組み込まれており、 `--module-path` で指定していなくても `requires` できる

# モジュールの解決の様子を確認する
`java` コマンドのオプションで `--show-module-resolution` というものが存在する。
これを指定してプログラムを起動すると、モジュールの解決の様子が出力されるようになる。

```
> java --show-module-resolution 【中略】 -m foo/foo.Main

root foo file:///.../foo/classes/
foo requires bar file:///.../bar/classes/
bar requires buzz file:///.../buzz/classes/
bar requires fizz file:///.../fizz/classes/
java.base binds jdk.localedata jrt:/jdk.localedata
java.base binds java.desktop jrt:/java.desktop
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
java.base binds jdk.zipfs jrt:/jdk.zipfs
java.base binds jdk.crypto.cryptoki jrt:/jdk.crypto.cryptoki
java.base binds jdk.crypto.ec jrt:/jdk.crypto.ec
java.base binds java.naming jrt:/java.naming
java.base binds java.xml.crypto jrt:/java.xml.crypto
java.base binds jdk.crypto.mscapi jrt:/jdk.crypto.mscapi
java.base binds jdk.deploy jrt:/jdk.deploy
java.base binds java.security.sasl jrt:/java.security.sasl
java.base binds java.security.jgss jrt:/java.security.jgss
java.base binds jdk.security.jgss jrt:/jdk.security.jgss
java.base binds java.smartcardio jrt:/java.smartcardio
java.base binds jdk.jlink jrt:/jdk.jlink
java.base binds jdk.javadoc jrt:/jdk.javadoc
java.base binds jdk.jartool jrt:/jdk.jartool
java.base binds jdk.packager jrt:/jdk.packager
java.base binds jdk.jdeps jrt:/jdk.jdeps
java.base binds jdk.compiler jrt:/jdk.compiler
jdk.compiler requires java.compiler jrt:/java.compiler
jdk.jdeps requires jdk.compiler jrt:/jdk.compiler
jdk.jdeps requires java.compiler jrt:/java.compiler
jdk.packager requires java.logging jrt:/java.logging
jdk.packager requires jdk.jlink jrt:/jdk.jlink
jdk.packager requires java.xml jrt:/java.xml
jdk.packager requires java.desktop jrt:/java.desktop
jdk.javadoc requires java.xml jrt:/java.xml
jdk.javadoc requires java.compiler jrt:/java.compiler
jdk.javadoc requires jdk.compiler jrt:/jdk.compiler
jdk.jlink requires jdk.jdeps jrt:/jdk.jdeps
jdk.jlink requires jdk.internal.opt jrt:/jdk.internal.opt
jdk.security.jgss requires java.logging jrt:/java.logging
jdk.security.jgss requires java.security.sasl jrt:/java.security.sasl
jdk.security.jgss requires java.security.jgss jrt:/java.security.jgss
java.security.jgss requires java.naming jrt:/java.naming
java.security.sasl requires java.logging jrt:/java.logging
jdk.deploy requires java.scripting jrt:/java.scripting
jdk.deploy requires java.management jrt:/java.management
jdk.deploy requires jdk.unsupported jrt:/jdk.unsupported
jdk.deploy requires java.naming jrt:/java.naming
jdk.deploy requires java.xml jrt:/java.xml
jdk.deploy requires java.prefs jrt:/java.prefs
jdk.deploy requires java.desktop jrt:/java.desktop
jdk.deploy requires java.rmi jrt:/java.rmi
jdk.deploy requires java.logging jrt:/java.logging
java.xml.crypto requires java.xml jrt:/java.xml
java.xml.crypto requires java.logging jrt:/java.logging
java.naming requires java.security.sasl jrt:/java.security.sasl
jdk.crypto.cryptoki requires jdk.crypto.ec jrt:/jdk.crypto.ec
jdk.security.auth requires java.naming jrt:/java.naming
jdk.security.auth requires java.security.jgss jrt:/java.security.jgss
java.desktop requires java.xml jrt:/java.xml
java.desktop requires java.datatransfer jrt:/java.datatransfer
java.desktop requires java.prefs jrt:/java.prefs
java.prefs requires java.xml jrt:/java.xml
java.rmi requires java.logging jrt:/java.logging
java.naming binds jdk.naming.dns jrt:/jdk.naming.dns
java.naming binds jdk.naming.rmi jrt:/jdk.naming.rmi
java.scripting binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
java.management binds java.management.rmi jrt:/java.management.rmi
java.management binds jdk.management jrt:/jdk.management
java.management binds jdk.management.cmm jrt:/jdk.management.cmm
java.management binds jdk.internal.vm.compiler.management jrt:/jdk.internal.vm.compiler.management
java.management binds jdk.management.jfr jrt:/jdk.management.jfr
java.desktop binds jdk.accessibility jrt:/jdk.accessibility
java.compiler binds jdk.compiler jrt:/jdk.compiler
java.compiler binds jdk.javadoc jrt:/jdk.javadoc
java.datatransfer binds java.desktop jrt:/java.desktop
jdk.accessibility requires java.desktop jrt:/java.desktop
jdk.management.jfr requires jdk.management jrt:/jdk.management
jdk.management.jfr requires jdk.jfr jrt:/jdk.jfr
jdk.management.jfr requires java.management jrt:/java.management
jdk.internal.vm.compiler.management requires java.management jrt:/java.management
jdk.internal.vm.compiler.management requires jdk.internal.vm.compiler jrt:/jdk.internal.vm.compiler
jdk.internal.vm.compiler.management requires jdk.management jrt:/jdk.management
jdk.internal.vm.compiler.management requires jdk.internal.vm.ci jrt:/jdk.internal.vm.ci
jdk.management.cmm requires java.management jrt:/java.management
jdk.management.cmm requires jdk.management jrt:/jdk.management
jdk.management requires java.management jrt:/java.management
java.management.rmi requires java.rmi jrt:/java.rmi
java.management.rmi requires java.naming jrt:/java.naming
java.management.rmi requires java.management jrt:/java.management
jdk.scripting.nashorn requires java.logging jrt:/java.logging
jdk.scripting.nashorn requires java.scripting jrt:/java.scripting
jdk.scripting.nashorn requires jdk.dynalink jrt:/jdk.dynalink
jdk.naming.rmi requires java.rmi jrt:/java.rmi
jdk.naming.rmi requires java.naming jrt:/java.naming
jdk.naming.dns requires java.naming jrt:/java.naming
jdk.internal.vm.compiler requires jdk.management jrt:/jdk.management
jdk.internal.vm.compiler requires jdk.internal.vm.ci jrt:/jdk.internal.vm.ci
jdk.internal.vm.compiler requires jdk.unsupported jrt:/jdk.unsupported
jdk.internal.vm.compiler requires java.instrument jrt:/java.instrument
jdk.internal.vm.compiler requires java.management jrt:/java.management
jdk.dynalink requires java.logging jrt:/java.logging
jdk.dynalink binds jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
jdk.internal.vm.ci binds jdk.internal.vm.compiler jrt:/jdk.internal.vm.compiler
```

なにやら大量に出力されているが、とりあえず先頭の数行だけを確認してみる。

```
root foo file:///.../foo/classes/
foo requires bar file:///.../bar/classes/
bar requires buzz file:///.../buzz/classes/
bar requires fizz file:///.../fizz/classes/
```

`root foo` が、 `foo` モジュールがルート・モジュールとして処理されたことを意味している。
そして、 `foo` から順番に `requires` を辿って各モジュールが解決していることが読み取れる。

ちなみに、 `java.base` は必ず `requires` される扱いなので、 `foo requires java.base` というログは出力されないようになっている。

## その他の出力
`foo` から始まるモジュールグラフを作り終わったあとも、ログは大量に出力されている。

これらは `java.base` が読み込まれたことによって出力されている。

```
java.base binds jdk.localedata jrt:/jdk.localedata
java.base binds java.desktop jrt:/java.desktop
java.base binds jdk.charsets jrt:/jdk.charsets
java.base binds java.logging jrt:/java.logging
...
```

まず最初に `java.base binds ****` という記述が十数行出力されている。
この `binds` は、 ServiceLoader におけるサービスプロバイダが解決されたときに出力されている[^7]。

[^7]: ServiceLoaderについては[Jigsaw 勉強メモ - Qiita](https://qiita.com/opengl-8080/items/1007c2b2543c2fe0d7d5#use-provides#use-provides)を参照

`java.base` は他のモジュールを `requires` することはないが、 `uses` の宣言は大量に存在している（ServiceLoader は使っている）。

```java:java.baseのmodule-info.java
    ...
    uses java.lang.System.LoggerFinder;
    uses java.net.ContentHandlerFactory;
    uses java.net.spi.URLStreamHandlerProvider;
    uses java.nio.channels.spi.AsynchronousChannelProvider;
    ...
```

例えば、 `java.net.ContentHandlerFactory` の実装は `java.desktop` モジュールで提供されている。

```java:java.desktopモジュールのmodule-info.java
    provides java.net.ContentHandlerFactory with sun.awt.www.content.MultimediaContentHandlers;
```

この結果、

1. `java.base` が `uses` しているサービスのプロバイダーとなるモジュールを探してロードする
2. プロバイダーとなったモジュールが別のモジュールを `requires` しているのでそちらもロードする

という処理が再帰的に繰り返されて、上記のような長いログ出力になっている。

ちなみに、 `java.desktop` モジュールは、ロードはされているがモジュールグラフ上には存在しない。
なので、 `java.desktop` 内にあるクラスを `foo` モジュールなどで `import` しようとするとエラーになる。
`import` できるのは、あくまで `requires` で依存関係を定義し、モジュールグラフ上で依存関係が直接つながっているモジュールに限られる。


# 後方互換の話が入るとややこしくなっていく
ここまでは、コンパイルや実行の対象が全てモジュール化されている場合の話。

正直これだけなら、割と簡単な話だと思う。
要はルート・モジュールを決めて、そこから `requires`を辿ってグラフを作るというだけの話。

ここに後方互換性のために存在する無名モジュールや自動モジュールが関わってくると、特別ルールが追加されて話がややこしくなっていく。

# クラスパスの問題点とモジュールシステムの Reliable configuration
モジュールシステムが導入された背景の１つとして、クラスパスの持つ脆さがある。

クラスパスには、アーティファクトを区別する仕組みはなく、型の検索は全てのアーティファクトが入ったクラスパス内から行われるようになっていた。
このため、アーティファクトの単位で事前に不足があるかどうかを判定できなかったり、異なるアーティファクトに同じパッケージが含まれていても検知する仕組みはなかった[^8]。

[^8]: そして実行時に意図しない方のクラスがロードされNoSuchMethodErrorとかが発生する

モジュールシステムでは、プログラムの集合をモジュールという単位で定義し、それぞれの依存関係を管理できる。これにより、事前にモジュール単位で不足を検知できるようになった。
また、あるモジュールが別のモジュールのパッケージを読み込むときは、そのパッケージを含むモジュールは１つに定まることが保証されるようになった（`requires` している複数の異なるモジュールに同じパッケージがあるとエラーになる）。

このようなモジュールシステムが持つ高い信頼性を **Reliable configuration (信頼できる構成)** と言う。
これは、モジュールシステムの重要な特徴の１つとなっている。

モジュールグラフは、この Reliable configuration を実現するための基礎となっている。

## モジュールのバージョン管理は範囲外
同じモジュールのバージョン違いがモジュールパスに含まれていた場合、モジュールシステムはそれを検知しない（最初に見つかったモジュールが優先される）。

[Non-Requirements | Java Platform Module System: Requirements](http://openjdk.java.net/projects/jigsaw/spec/reqs/#non-requirements)

バージョンの選択については Maven のような既存のツールに任せることになっている。
そしてモジュールシステムは、バージョン選択が済んでいる前提で、残ったモジュールたちの検証に注力する方針となっている。

# クラスパスは残っているが全く同じではない
クラスパスを捨てて全てをモジュール化できれば、この Reliable configuration を享受できる。
ところが、実際は後方互換を捨てるわけにはいかないので、クラスパスは Java 8 以前と同じ様な感じで使えるようになっている。

しかし、 `-classpath` オプションは残っているものの、裏ではクラスパスで指定されたクラス達もモジュールシステム上で動くことになる。

従って、クラスパスを利用していればモジュールシステムは知らなくていいというわけではない。
クラスパスを利用していてもモジュール絡みのエラーが出る可能性は十分ある。
そういうときにモジュールシステムのことを知らないと、問題の解決に苦労するかもしれない。

# 無名モジュール
クラスパスから読み込まれたパッケージや型は、**無名モジュール (Unnamed module)** という特別なモジュールに属するようになる。

無名モジュールはモジュール定義 (`module-info.java`) を持つことができないため、 `requires` や `exports` に関する定義を指定することはできない。

代わりに、無名モジュールはデフォルトで次のように振る舞う。

- 無名モジュール内の全てのパッケージは `exports` されている扱いになる
- 無名モジュールは、モジュールグラフ内の全てのモジュールを `requires` している扱いになる

## 名前付きモジュールから無名モジュールは参照できない
無名モジュールは全てのパッケージを `exports` していることになるが、それを名前付きモジュール[^9]から参照することはできない。

[^9]: `module-info.java` を持つ通常のモジュール、および後述する自動モジュールのこと

これは、前述の Reliable configuration を維持するため、意図的にそういう仕様になっている。
もし名前付きモジュールから無名モジュール(クラスパス上のクラス)を参照できるようにすると、クラスパスの問題が再発することになり、せっかくモジュールシステムを導入した意味がなくなってしまう。

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/f06afd95-379e-99cb-7cf1-7e5d3c1cf237.jpeg)


## 同じパッケージが、名前付きモジュールと無名モジュールの両方に存在した場合
同じパッケージが名前付きモジュールと無名モジュールの両方に存在した場合、**名前付きモジュールに存在するパッケージが優先される**。
このとき、**エラーや警告は出ない**。

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/8a09f1b4-553c-2b59-3735-252e3cb2ff11.jpeg)

例えば上図のように `foo` パッケージが名前付きモジュールと無名モジュールの両方に存在したとする。

このとき、 `foo` パッケージ内のクラスにアクセスしようとすると、名前付きモジュールの方が優先される。このため、無名モジュール側に存在する `foo.FooService` にはアクセスすることができない。無理にアクセスしようとすると、コンパイル時なら型が見つからずエラーになり、実行時なら `NoClassDefFoundError` が発生する。

意図せずこのような状態になっても警告は出ないため、モジュールグラフとクラスパスがどうなっているかを把握できていないと原因が分からず困ることになる[^10]。

[^10]: 実際遭遇して困った。


## 無名モジュールが起点となった場合のルート・モジュールの決定（2018-11-04 更新）
通常のモジュールをコンパイルした場合は対象のモジュールが、実行時は `--module` で指定したモジュールがルート・モジュールとなった。

一方、コンパイル対象のソースに `module-info.java` が含まれていなかったり、実行時のメインクラスがクラスパスからロードされた場合――すなわち無名モジュールを起点としてコンパイルやプログラムの起動が行われた場合、ルート・モジュールの決定方法は大きく変化する。

### Java 11 以降
Java 11 で動作を確認したら、 Java 10 までとは違う動きになっていたので追記。

先に結論を書くと、 Java 11 からは次のルールでルートモジュールが決まる。

- `--upgrade-module-path` で指定した場所に存在する全てのモジュール
- システムモジュール内に存在し、 `to` による公開先の制限なしで最低１つはパッケージを `exports` しているモジュール

`java.se` どうのこうのの話はなくなっている。

OpenJDK 11 で、無名モジュールを起点とし `--show-module-resolution` オプションを指定して起動すると、次のように出力される。

```text
root jdk.management.jfr jrt:/jdk.management.jfr
root java.rmi jrt:/java.rmi
root java.sql jrt:/java.sql
root jdk.jdi jrt:/jdk.jdi
root java.xml.crypto jrt:/java.xml.crypto
root java.transaction.xa jrt:/java.transaction.xa
root java.logging jrt:/java.logging
root jdk.xml.dom jrt:/jdk.xml.dom
root java.xml jrt:/java.xml
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 jdk.sctp jrt:/jdk.sctp
root jdk.jsobject jrt:/jdk.jsobject
root java.management jrt:/java.management
root java.sql.rowset jrt:/java.sql.rowset
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 java.compiler jrt:/java.compiler
root jdk.security.auth jrt:/jdk.security.auth
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
（以下略）
```

よく見ると、 `java.se` モジュールが読み込まれていない。

修正が入ったコミットが次のコミットで（`DefaultRoots.java` の `compute(ModuleFinder, ModuleFinder)` メソッド）、
[jdk/jdk11: 6cc2dc161c64](https://hg.openjdk.java.net/jdk/jdk11/rev/6cc2dc161c64)

対応する Issue が次になる。
[[JDK-8197532] Re-examine policy for the default set of modules when compiling or running code on the class path - Java Bug System](https://bugs.openjdk.java.net/browse/JDK-8197532)

Java 11 で Java EE 系のモジュールが削除されたことで、ルートモジュールの解決が単純になったことが理由となっている。
`java.se.ee` モジュールが削除されたことで、単純にシステムモジュール内のモジュールを全てルートにすればよくなったと思われる。


### Java 10 まで
ルート・モジュールには、大きく次の２つが読み込まれる。

1. ランタイムに `java.se` モジュールが存在する場合は、 `java.se` モジュールがルートとなる。  
   ランタイムに `java.se` モジュールが存在しない場合は、次の条件をすべて満たすモジュールがルート・モジュールとして読み込まれる
   - システムモジュール、または `--upgrade-module-path` で指定した場所に存在する
   - モジュール名が `java.*` にマッチする
   - `to` で公開先を限定していないパッケージが、 最低１つは `exports` されている
2. さらに、次の条件をすべて満たすモジュールもルート・モジュールとして読み込まれる
   - システムモジュール、または `--upgrade-module-path` で指定した場所に存在する
   - モジュール名が `java.*` にマッチ**しない**
   - `to` で公開先を限定していないパッケージが、 最低１つは `exports` されている

つまり、ランタイムが提供するモジュールをできる限り根こそぎルートとして登録するようになっている。

これによって作成されるモジュールグラフは、ルート・モジュールだらけになる。
ここに、**無名モジュールはモジュールグラフ上のすべてのモジュールを `requires` している扱いになる**という前述したルールが適用されることで、無名モジュールはランタイムが提供するほとんどのモジュールにアクセスできるようになる。

結果、 Java 8 以前に作られたプログラムでも、クラスパスに入れて実行すれば（ほぼ）問題なく動作するようになっている[^11]。

[^11]: ルート・モジュールの決定ルールをよく読めばわかるが、完全にすべてのモジュールが対象になるわけではないので、そういうモジュールを使っていた場合に問題が発生する

#### 無名モジュールで起動したときのモジュールの解決を確認する
実際に、先程モジュールの解決を確認した実装を、今度はクラスパスに入れて起動してみる。

```
> java --show-module-resolution -cp 【中略】 foo.Main

root jdk.management.jfr jrt:/jdk.management.jfr
root jdk.jdi jrt:/jdk.jdi
root javafx.web jrt:/javafx.web
root jdk.xml.dom jrt:/jdk.xml.dom
root jdk.jfr jrt:/jdk.jfr
root jdk.packager.services jrt:/jdk.packager.services
root jdk.httpserver jrt:/jdk.httpserver
root javafx.base jrt:/javafx.base
root jdk.net jrt:/jdk.net
root javafx.controls jrt:/javafx.controls
root jdk.management.resource jrt:/jdk.management.resource
root java.se jrt:/java.se
root jdk.compiler jrt:/jdk.compiler
root jdk.jconsole jrt:/jdk.jconsole
root jdk.attach jrt:/jdk.attach
root jdk.management.cmm jrt:/jdk.management.cmm
root jdk.javadoc jrt:/jdk.javadoc
root jdk.jshell jrt:/jdk.jshell
root jdk.sctp jrt:/jdk.sctp
root jdk.jsobject jrt:/jdk.jsobject
root oracle.desktop jrt:/oracle.desktop
root javafx.swing jrt:/javafx.swing
root jdk.packager jrt:/jdk.packager
root jdk.unsupported jrt:/jdk.unsupported
root jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
root oracle.net jrt:/oracle.net
root jdk.management jrt:/jdk.management
root javafx.graphics jrt:/javafx.graphics
root jdk.security.auth jrt:/jdk.security.auth
root javafx.fxml jrt:/javafx.fxml
root jdk.dynalink jrt:/jdk.dynalink
root javafx.media jrt:/javafx.media
root jdk.accessibility jrt:/jdk.accessibility
root jdk.jartool jrt:/jdk.jartool
root jdk.security.jgss jrt:/jdk.security.jgss
...
```

`root` が大量に出力されていることから、多くのモジュールがルート・モジュールとして読み込まれていることがわかる。

実行した環境では `java.se` モジュールが存在したため、 `java.*` にマッチするモジュールは `java.se` のみがルートとなっている。
一方 `javafx.*` や `jdk.*` は、システムモジュールに存在したモジュールが軒並みルート・モジュールとして読み込まれている。

いくつか、システムモジュールにあったのにルートになっていないモジュールが存在する。
例えば `jdk.aot` がルートになっていない。このモジュールの `module-info.java` を確認してみると、次のようになっている。

```java:jdk.aotのmodule-info.java
module jdk.aot {
    requires jdk.internal.vm.ci;
    requires jdk.internal.vm.compiler;
    requires jdk.management;
}
```

`exports` されているパッケージが存在しない。

よって、前述の条件にあった「**`to` で公開先を限定していないパッケージが、 最低１つは `exports` されている**」を満たしていないため、対象からは除外されているものと思われる（確認はしていないが、おそらく他もそうなのだろう）。

## java.se が読み込まないモジュールたち
無名モジュールを起点とした場合、ランタイムに用意されているほとんどのモジュールが無名モジュールから参照できるようになる。
しかし、すべてのモジュールが参照できるようになっているかというと、例外がある。

以下のモジュールたちは、システムモジュールにはあるが `java.se` からは間接エクスポートされていない。

|モジュール               |説明                   |
|------------------------|-----------------------|
|`java.activation`       |非推奨(削除予定)        |
|`java.corba`            |非推奨(削除予定)        |
|`java.jnlp`             |Java Web Start 関係    |
|`java.se.ee`            |非推奨(削除予定)        |
|`java.smartcardio`      |スマート・カード入出力API|
|`java.transaction`      |非推奨(削除予定)        |
|`java.xml.bind`         |非推奨(削除予定)        |
|`java.xml.ws`           |非推奨(削除予定)        |
|`java.xml.ws.annotation`|非推奨(削除予定)        |

非推奨（削除予定）となっているモジュールは、全て Java EE から Java SE に一部が取り込まれたものの、結局 Java SE からは削除されることになった API たちになる。
これらは Java 11 で完全に削除されるので、 Java 9 の時点ですでに非推奨となっている。

これらのモジュールは、 `java.se` がルートになっただけではモジュールグラフには現れない。
そのため、もし無名モジュール内のクラスがこれらのモジュールに依存している場合、コンパイルや実行ができない。

## --add-modules で任意のモジュールをルートに追加する
コンパイルや実行時のときに `--add-modules` というオプションを指定できる。
このオプションを使うと、観測可能な範囲にある任意のモジュールをルート・モジュールとして指定できる。

このオプションを使えば、前述のデフォルトではモジュールグラフに読み込まれないモジュールも強制的にルート・モジュールにできるので、無名モジュールから参照できるようになる。

よく、Java 9 で非推奨となった前述のモジュールを使っている場合の対応方法として `--add-modules` を指定する方法が紹介されるが、こういう仕組になっている。

実際に、 `--add-modules` を追加してルート・モジュールがどう解決されるか確認してみる。

```
>java --show-module-resolution --add-modules java.transaction,java.xml.bind -cp 【中略】 foo.Main

root jdk.management.jfr jrt:/jdk.management.jfr
root jdk.jdi jrt:/jdk.jdi
root javafx.web jrt:/javafx.web
root jdk.xml.dom jrt:/jdk.xml.dom
root jdk.jfr jrt:/jdk.jfr
root jdk.packager.services jrt:/jdk.packager.services
root jdk.httpserver jrt:/jdk.httpserver
root javafx.base jrt:/javafx.base
root jdk.net jrt:/jdk.net
root javafx.controls jrt:/javafx.controls
root jdk.management.resource jrt:/jdk.management.resource
root java.se jrt:/java.se
root jdk.compiler jrt:/jdk.compiler
root jdk.jconsole jrt:/jdk.jconsole
root jdk.management.cmm jrt:/jdk.management.cmm
root jdk.attach jrt:/jdk.attach
root jdk.javadoc jrt:/jdk.javadoc
root java.transaction jrt:/java.transaction ★ここと
root jdk.jshell jrt:/jdk.jshell
root jdk.jsobject jrt:/jdk.jsobject
root jdk.sctp jrt:/jdk.sctp
root javafx.swing jrt:/javafx.swing
root oracle.desktop jrt:/oracle.desktop
root jdk.unsupported jrt:/jdk.unsupported
root jdk.packager jrt:/jdk.packager
root jdk.scripting.nashorn jrt:/jdk.scripting.nashorn
root java.xml.bind jrt:/java.xml.bind ★ここ
root oracle.net jrt:/oracle.net
root jdk.management jrt:/jdk.management
root jdk.security.auth jrt:/jdk.security.auth
root javafx.graphics jrt:/javafx.graphics
root javafx.fxml jrt:/javafx.fxml
root jdk.dynalink jrt:/jdk.dynalink
root jdk.accessibility jrt:/jdk.accessibility
root javafx.media jrt:/javafx.media
root jdk.jartool jrt:/jdk.jartool
root jdk.security.jgss jrt:/jdk.security.jgss
...
```

`--add-modules` で `java.transaction` と `java.xml.bind` を指定した結果、確かにルート・モジュールとして新たに読み込まれていることが確認できる。

## 削除予定のモジュールに関してはあくまで一時しのぎ
`--add-modules` を使用すると、 Java 9 で非推奨となったモジュールを利用していてもコンパイルや実行が引き続き可能となる。

しかし、この方法はあくまで一時しのぎの対処療法でしかない。
これらのモジュールは、 Java 11 で削除される予定なので、そうなるとこれらのモジュールは観測可能な範囲からも存在しなくなってしまう。

`--add-modules` は、あくまで観測可能な範囲に存在するモジュールを、強制的にルート・モジュールに追加するオプションでしかない。
モジュールがランタイムから削除されて観測可能な範囲からいなくなってしまうと、この方法では対処できなくなってしまう。

根本的に対処するには、ランタイムとは別にこれらのモジュールを含むアーティファクトを用意して、それをモジュールパスに追加する必要がある（もしくは依存を止めるか）。

詳しくは [Java 9 で deprecated になったモジュールによる例外発生の問題にちゃんと対処したい - k11i.biz](https://k11i.biz/blog/2018/06/26/maven-artifacts-for-java9-deprecated-modules/) が非常に参考になる。

## --add-modules に指定できる特殊な値
普通、 `--add-modules` にはモジュール名を指定する。
しかし、モジュール名とは別に次の３つの特殊な値を指定することもできる。

**ALL-DEFAULT**

- 無名モジュールが起点となった場合と同じ要領でルート・モジュールが追加される

**ALL-SYSTEM**

- システムモジュールに存在するすべてのモジュールがルートになる
- テストのときとかに使うと便利らしい（よくわかってない）
- 当然多くのモジュールが読み込まれてしまうので、通常は `ALL-DEFAULT` の利用が推奨される

**ALL-MODULE-PATH**

- モジュールパスに存在するすべてのモジュールがルートになる
- Maven のような依存関係を解決できるツールを使っている場合に利用することが想定されているっぽい
- Maven は依存するライブラリを過不足なく集めてくるので、モジュールパスに存在する = 全部必要なモジュールということになる
    - こっちのほうが、クラスの検索とかが早くなったりするのだろうか？（よくわかってない）
- 自動モジュールをまとめてルートに設定したいときにも便利らしい（よくわかってない）

これらは、例えば `--add-modules ALL-MODULE-PATH` のような感じで指定する。

# 自動モジュール
無名モジュールは、主に自分のアプリケーションがモジュールに対応していない場合に利用することになる。

一方、自分のアプリケーションはモジュール化しているが、使用しているサードパーティのライブラリがモジュールに対応していないような場合に関係してくるのが、**自動モジュール (Automatic modules)**になる。

モジュール化されていない（`module-info.class` が含まれていない）アーティファクトをモジュールパスに入れて利用した場合、そのアーティファクトは自動モジュールとして認識される。

## 自動モジュールの名前
自動モジュールのモジュール名は、次の優先順位で自動的に決定される。

1. `MANIFEST.MF` の `Automatic-Module-Name` で指定された名前
2. jar ファイル名から自動的に解決された名前[^12]

[^12]: 解決のされ方については [こちら](https://qiita.com/opengl-8080/items/1007c2b2543c2fe0d7d5#automatic-module-%E3%81%AE%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E5%90%8D%E3%81%AE%E8%87%AA%E5%8B%95%E6%B1%BA%E5%AE%9A%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF) を参照

自分のアプリケーションで自動モジュールを `requires` する場合は、ここで決定されたモジュール名を指定する。

## 自動モジュールの requires と exports の扱い
自動モジュールも、無名モジュールと同じで明示的に `requires` や `exports` を定義することはできない。

そのため、自動モジュールは次のように振る舞うようになっている。

- すべてのパッケージを `exports` している扱いになる
- モジュールグラフ上のすべてのモジュールを `requires` している扱いになる

つまり、 `requires` と `exports` については無名モジュールと同じ扱いになっている。

それ以外は、普通のモジュールと同じように `requires` の対象として指定できるし、パッケージの重複を検出することもできる。

## 自動モジュールを含めたモジュールグラフのイメージ
例えば、自動モジュールなしの状態でグラフが下図のようになっていたとする。

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/f1bd34dc-7729-8bb5-9c17-bdccdaeef0f5.jpeg)

ここに、仮に `automatic` という名前の自動モジュールを追加すると、グラフは次のようになる。

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/02dd4190-d904-20c7-a743-84a3b9f67cfc.jpeg)

さらに、自動モジュールは他の普通の名前付きモジュールから `requires` できるので、次のように依存関係を追加することができる。

![jigsaw.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/4ef5342e-5246-2823-68d7-e99606833e22.jpeg)

# --upgrade-module-path でモジュールを差し替える
例えば、次のようなソースが存在したとする。

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

```java:module-info.java
module sample {
    requires java.transaction;
}
```

```java:Main.java
package sample;

import javax.transaction.Status;

public class Main {
    public static void main(String[] args) {
        System.out.println("Status.STATUS_ACTIVE = " + Status.STATUS_ACTIVE);
    }
}
```

`java.transaction` モジュールを `requires` し、 `javax.transaction.Status` に宣言されている定数を出力している。

これをコンパイルしようとすると、次のようになる（警告メッセージは除外している）。

```
>javac -d classes src\module-info.java src\sample\Main.java
src\sample\Main.java:3: エラー: シンボルを見つけられません
import javax.transaction.Status;
                        ^
  シンボル:   クラス Status
  場所: パッケージ javax.transaction

    requires java.transaction;
                 ^
src\sample\Main.java:7: エラー: シンボルを見つけられません
        System.out.println("Status.STATUS_ACTIVE = " + Status.STATUS_ACTIVE);
                                                       ^
  シンボル:   変数 Status
  場所: クラス Main
エラー2個
```

`Status` が見つからずにエラーになっている。

これは、 Java SE 環境に存在する `java.transaction` モジュールが使用されているために発生している。

Java SE にある `java.transaction` モジュールが持つ `javax.transaction` パッケージは、次のようになっている。

[javax.transaction (Java SE 10 & JDK 10 )](https://docs.oracle.com/javase/jp/10/docs/api/javax/transaction/package-summary.html)

例外クラス３つだけを含み、 `Status` クラスは存在しない。
一方、本家 Java EE の方の `javax.transaction` パッケージは次のようになっている。

[javax.transaction (Java(TM) EE 8 Specification APIs)](https://javaee.github.io/javaee-spec/javadocs/javax/transaction/package-summary.html)

`Status` クラスもちゃんと存在している。

コンパイルを通すためには、本家 Java EE の jar を持ってくる必要がある。
ということで、 [Java EE 版の jar](https://mvnrepository.com/artifact/javax.transaction/javax.transaction-api/1.3) を落としてきて再度コンパイルしてみる。

```
>javac -d classes -p lib\javax.transaction-api-1.3.jar src\module-info.java src\sample\Main.java
src\sample\Main.java:3: エラー: シンボルを見つけられません
import javax.transaction.Status;
                        ^
  シンボル:   クラス Status
  場所: パッケージ javax.transaction

    requires java.transaction;
                 ^
src\sample\Main.java:7: エラー: シンボルを見つけられません
        System.out.println("Status.STATUS_ACTIVE = " + Status.STATUS_ACTIVE);
                                                       ^
  シンボル:   変数 Status
  場所: クラス Main
エラー2個
```

変わらない。

これは、おそらくモジュールパスよりもシステムモジュールの `java.transaction` モジュールが優先されているため、結局同じエラーになっているものと思われる[^13]。

[^13]: 観測可能なモジュール内に同じ名前のモジュールが複数存在した場合、最初に見つかったモジュールが優先されるっぽい（システムモジュールはモジュールパスより先に検索される）

この場合、システムモジュールの `java.transaction` をなんとかしなければならない。

１つの解決方法として、システムモジュールの `java.transaction` を別の内容で差し替える方法がある。
そのためのオプションが `--upgrade-module-path` で、次のように指定する。

```
>javac -d classes --upgrade-module-path lib\javax.transaction-api-1.3.jar src\module-info.java src\sample\Main.java
```

差し替えるモジュールのアーティファクトの場所を、モジュールパスの代わりに `--upgrade-module-path` に指定する。
`--upgrade-module-path` に指定したモジュールはシステムモジュールよりも優先されるため、システムモジュールの `java.transaction` ではなく、 `javax.transaction-api-1.3.jar` が持つ `java.transaction` モジュールが使用されるようになっている。


# --limit-modules でモジュールを制限する
無名モジュールを起点とした場合、多くのモジュールがロードされる。

必要ないモジュールのロードを止めたい場合や、デバッグ目的でロードするモジュールの数を絞りたい場合は、 `--limit-modules` オプションが利用できる。

```
>java --show-module-resolution --limit-modules java.sql,java.management.rmi -cp classes foo.Main

root java.rmi jrt:/java.rmi
root java.sql jrt:/java.sql
root java.naming jrt:/java.naming
root java.logging jrt:/java.logging
root java.xml jrt:/java.xml
root java.management jrt:/java.management
root java.security.sasl jrt:/java.security.sasl
root java.management.rmi jrt:/java.management.rmi
root java.base jrt:/java.base
java.management.rmi requires java.rmi jrt:/java.rmi
java.management.rmi requires java.management jrt:/java.management
java.management.rmi requires java.naming jrt:/java.naming
java.security.sasl requires java.logging jrt:/java.logging
java.naming requires java.security.sasl jrt:/java.security.sasl
java.sql requires java.logging jrt:/java.logging
java.sql requires java.xml jrt:/java.xml
java.rmi requires java.logging jrt:/java.logging
java.management binds java.management.rmi jrt:/java.management.rmi
java.base binds java.logging jrt:/java.logging
java.base binds java.management jrt:/java.management
java.base binds java.naming jrt:/java.naming
java.base binds java.security.sasl jrt:/java.security.sasl
```

`--limit-modules` を指定すると、そのモジュールと依存するモジュールだけが読み込まれるようになる。

上記例では `java.sql` と `java.management.rmi` だけに絞っている。

ところで `java.management.rmi requires java.naming` と出力されていて `java.management.rmi` は `java.naming` に依存していることになっている。
しかし、 [java.management.rmi](https://docs.oracle.com/javase/jp/10/docs/api/java.management.rmi-summary.html) の Javadoc を見ても `java.naming` の記述は存在しない。

試しに `module-info.java` を見てみたが、次のようになっていた。

```java:java.management.rmiのmodule-info.java
module java.management.rmi {
    requires java.naming;
    requires transitive java.management;
    requires transitive java.rmi;
    ...
}
```

ちゃんと `java.naming` が `requires` されている。
Javadoc に記載される情報に、 `transitive` がついていないモジュールの情報は載らないということだろうか？

# モジュールレイヤー
モジュールレイヤーは、モジュールグラフ内の各モジュールとクラスローダーとのマッピングを定義している。

JVM を起動すると、必ず１つのモジュールレイヤーが作成される。
これを**ブートレイヤー**と呼び、 `ModuleLayer.boot()` で取得できる。

```java:Main.java
package sample;

public class Main {
    public static void main(String[] args) {
        ModuleLayer bootLayer = ModuleLayer.boot();

        ClassLoader javaBaseModuleClassLoader = bootLayer.findLoader("java.base");
        System.out.println("java.base module ClassLoader => " + javaBaseModuleClassLoader);

        ClassLoader javaSqlModuleClassLoader = bootLayer.findLoader("java.compiler");
        System.out.println("java.sql module ClassLoader => " + javaSqlModuleClassLoader.getName());

        ClassLoader sampleModuleClassLoader = bootLayer.findLoader("sample");
        System.out.println("sample module ClassLoader => " + sampleModuleClassLoader.getName());
    }
}
```

```text:実行結果
java.base module ClassLoader => null
java.sql module ClassLoader => platform
sample module ClassLoader => app
```

- `ModuleLayer.findLoader(String)` で、指定したモジュールにマッピングされた `ClassLoader` を取得できる
- [ブートストラップクラスローダーは通常 null で表されるので](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/ClassLoader.html#ClassLoader.28Dwsq.spl1)、 `java.base` のクラスローダーも `null` になる
- `platform` はプラットフォームクラスローダー、 `app` はシステムクラスローダー（アプリケーションクラスローダー）を表している

## いつ使うものか？
- 普通に Java アプリケーションを作る場合は、このモジュールレイヤーを意識することはほぼない
- モジュールレイヤーが関わってくるのは、複数のアプリケーションをホストするソフトウェアを作るような場合になる
    - もしくは、 IDE やテストフレームワークのプラグイン機構とかにも使えるとかなんとか（よくわかってない）
- モジュールレイヤーは、動的に作成したり階層構造にすることができる
- これは、ちょうど Tomcat のようなコンテナ機能を持つアプリケーションが、デプロイされたアプリケーションごとにクラスローダーを分けるような感じと似ている
- つまり、ホストするアプリケーションごとにモジュールレイヤーを分けるような使い方ができる
- モジュールレイヤーを分けて利用するようなアプリケーションを今後使うようになったときに、この辺の話が関わってくるのかもしれない

## モジュールレイヤーを作成する
実際にモジュールレイヤーを作成してみる。

```text:フォルダ構成
|-foo/
| |-classes/ <---- foo モジュールのコンパイル結果
| `-src/
|   |-module-info.java
|   `-foo/
|     `-Foo.java
|
|-classes/   <---- sampleモジュールのコンパイル結果
`-src/
  |-module-info.java
  `-sample/
    `-Main.java
```

```java:fooモジュールのmodule-info.java
module foo {
    exports foo;
}
```

```java:Foo.java
package foo;

public class Foo {
    public void hello() {
        System.out.println("foooooooo!!!!");
    }
}
```

```java:sampleモジュールのmodule-info.java
module sample {
}
```

```java:Main.java
package sample;

import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.reflect.Method;
import java.nio.file.Paths;
import java.util.Set;

public class Main {
    public static void main(String[] args) throws Exception {
        ModuleLayer bootLayer = ModuleLayer.boot();

        // foo をルート・モジュールとしたモジュールグラフ(Configuration)を作成
        ModuleFinder moduleFinder = ModuleFinder.of(Paths.get("foo/classes"));
        ModuleFinder emptyFinder = ModuleFinder.of();
        Set<String> roots = Set.of("foo");

        Configuration bootConfiguration = bootLayer.configuration();
        Configuration newConfiguration = bootConfiguration.resolve(moduleFinder, emptyFinder, roots);

        // 新しい ModuleLayer を作成
        ModuleLayer newLayer = bootLayer.defineModulesWithOneLoader(newConfiguration, ClassLoader.getSystemClassLoader());

        // foo モジュールから Foo クラスをロードして hello() メソッドを実行
        ClassLoader fooClassLoader = newLayer.findLoader("foo");
        Class<?> fooClass = fooClassLoader.loadClass("foo.Foo");
        Object fooInstance = fooClass.getConstructor().newInstance();
        Method helloMethod = fooClass.getMethod("hello");
        helloMethod.invoke(fooInstance);
    }
}
```

```text:実行結果
> java -p classes -m sample/sample.Main
foooooooo!!!!
```

### Configuration を作成する
```java:Main.java
// foo をルート・モジュールとしたモジュールグラフ(Configuration)を作成
ModuleFinder moduleFinder = ModuleFinder.of(Paths.get("foo/classes"));
ModuleFinder emptyFinder = ModuleFinder.of();
Set<String> roots = Set.of("foo");

Configuration bootConfiguration = bootLayer.configuration();
Configuration newConfiguration = bootConfiguration.resolve(moduleFinder, emptyFinder, roots);
```

- モジュールレイヤーを作成するには、まずはじめに [Configuration](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/Configuration.html) を作成する
- `Configuration` はモジュールの解決の結果――――すなわちモジュールグラフを保持している
- `Configuration` を作成するには、大きく次の３つを用意する
    - 親となる `Configuration`
    - モジュールを検索する `ModuleFinder`
    - ルート・モジュール名のコレクション
- `Configuration` は階層構造を取るようになっており、新しく作成するときは親の `Configuration` が必要となる
    - 何もないときはブートレイヤーの `Configuration` を使用する（`ModuleLayer.boot().configuration()` で取得できる）
- [ModuleFinder](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/ModuleFinder.html) はモジュールを検索する機能を提供する
    - `ModuleFinder.of(Path...)` で作成した `ModuleFinder` は、 `Path` で指定した場所からモジュールを検索する
    - [Configuration.resolve(ModuleFinder, ModuleFinder, Set&lt;String&gt;)](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/Configuration.html#resolve(java.lang.module.ModuleFinder,java.lang.module.ModuleFinder,java.util.Collection)) では、 `before` と `after` の２つを指定する
        - モジュールを検索するときに、まず `before` の `ModuleFinder` でモジュールが検索される
        - `before` で見つからなければ、次は親の `Configuration` が検索される
        - それでも見つからない場合は `after` の `ModuleFinder` が検索される
- ルート・モジュール名のコレクションは、モジュールグラフを作成するときのルート・モジュール名を指定するものになる

### ModuleLayer を作成する
```java:Main.java
// 新しい ModuleLayer を作成
ModuleLayer newLayer = bootLayer.defineModulesWithOneLoader(newConfiguration, ClassLoader.getSystemClassLoader());
```

- `ModuleLayer` に用意されたメソッドに上で作成した `Configuration` を渡すことで、新しい `ModuleLayer` を作成できる
- `ModuleLayer` も親のレイヤーを指定する必要があり、特にない場合はブートレイヤーを利用する
- `ModuleLayer` を作成するためのメソッドは、大きく以下の３種類が存在する
- [defineModules(Configuration, Function&lt;String, ClassLoader&gt;)](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/ModuleLayer.html#defineModules(java.lang.module.Configuration,java.util.function.Function))
    - 第二引数の `Function` は、モジュール名を受け取って、そのモジュールに対応する `ClassLoader` を返す実装を渡す
- [defineModulesWithOneLoader(Configuration, ClassLoader)​](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/ModuleLayer.html#defineModulesWithOneLoader(java.lang.module.Configuration,java.lang.ClassLoader))
    - これは、モジュールグラフ内の全てのモジュールに対して、１つの新しいクラスローダーが割り当てられる
    - クラスローダーは `ModuleLayer` が内部で独自のものを作成する
    - 第二引数で渡す `ClassLoader` は、新規クラスローダーの親として利用される
- [defineModulesWithManyLoaders(Configuration, ClassLoader)](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/ModuleLayer.html#defineModulesWithManyLoaders(java.lang.module.Configuration,java.lang.ClassLoader))
    - これは、モジュールグラフ内のモジュール１つ１つに対して、クラスローダーが作成されて割り当てられる
    - 第二引数で渡す `ClassLoader` は、新規クラスローダーの親として利用される

# 参考
- [JEP 261: Module System](http://openjdk.java.net/jeps/261)
- [java - Which system modules are on the module path by default? - Stack Overflow](https://stackoverflow.com/questions/44051223/which-system-modules-are-on-the-module-path-by-default)
- [java.lang.module (Java SE 10 & JDK 10 )](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/module/package-summary.html)
- [Java SE 9の紹介: モジュール・システムを中心に](https://www.slideshare.net/miyakawataku/introduction-of-java-se-9-and-the-module-system)
- [実行時におけるJava SE 9モジュールとクラスローダーの関連 - 映画は中劇](http://d.hatena.ne.jp/miyakawa_taku/20171108/1510145001)
- [Java 9 - Modules · JVM. Blog.](https://blog.andresteingress.com/2017/09/29/java-9-modules.html)
- [The State of the Module System](http://openjdk.java.net/projects/jigsaw/spec/sotms/)