今まで何となくで使ってた Jigsaw (モジュールシステム)をしっかり学ぶ。
環境
OS
Windows 10
Java
java 10.0.1
モジュールの解決
モジュールの解決 | java.lang.module (Java SE 10 & JDK 10 )
Java 9 で追加されたモジュールシステムでは、複数のパッケージやリソースをモジュールと呼ばれる単位でまとめることができるようになった。
モジュールでは、次のようなことを定義できる1。
- どのパッケージを外部から見えるようにするか
- 他のどのモジュールに依存するか
これらは module-info.java
という専用のファイルを使い、次のように記述する。
module foo {
exports foo.api;
requires bar;
}
この例では、 foo
というモジュールを定義している。
そして、 exports foo.api
で foo.api
パッケージを外部に公開し、 requires bar
で bar
モジュールに依存していることを宣言している。
コンパイルしたりプログラムを起動すると、この定義情報がまず最初に読み取られる2。
そして、各モジュールがどのような関係になっているかが分析される。
これを**モジュールの解決 (Resolution)**と呼び、非常に重要なステップとなっている。
このモジュールの解決によって得られた情報をもとに、コードの妥当性のチェックやクラスの検索といった処理が行われる。
つまり、モジュールの解決がどのように行われるかを理解しておかないと、モジュール絡みのエラーが発生したときに何が原因なのかあたりをつけることが困難になる。
モジュールグラフ
例えば、次のようなモジュール達が定義されていたとする。
module foo {
requires bar;
}
module bar {
requires fizz;
requires buzz;
}
foo
は、 bar
モジュールに依存し、 bar
モジュールは fizz
, buzz
モジュールに依存している。
これらのモジュールの関係を図にすると、下のような有向グラフができあがる3。
このようにモジュール同士の依存関係を表現したものを、モジュールグラフと呼ぶ。
矢印は requires
の関係を表現している。
あるモジュールが他のモジュールを参照するためには、この矢印が直接つながっていなければならない4。
つまり、 foo
モジュールは bar
モジュールと直接つながっているので参照できるが、 fizz
, buzz
モジュールとはつながっていないので参照はできないということになる。
モジュールの解決周りの問題に遭遇した場合は、このモジュールグラフの構築が意図通りになっているかを調べることで原因が特定しやすくなると思う。
間接エクスポート
foo
モジュールは fizz
, buzz
モジュールを直接 requires
していないため、参照することはできない。
しかし、もし bar
が fizz
, buzz
を**間接エクスポート(Indirect Exports)**している場合は、 foo
も fizz
, buzz
を参照できる。
module bar {
requires transitive fizz;
requires transitive buzz;
}
requires
に transitive
をつけている。
これにより、bar
を requires
したモジュールは fizz
, buzz
モジュールも requires
したことになる。
つまり、
は、
と同じことになる。
java.base モジュール
標準 API の中にある java.base
モジュールは、少し特別扱いとなっている。
このモジュールには java.lang
など、 Java を実行するうえで基礎となるパッケージが含まれている。
このため、このモジュールに限っては requires
を明示していなくても常に requires
している扱いとなる。
つまり、上述のモジュールグラフの図は、 java.base
も含めると次のようになる。
ルート・モジュールの決定
ルート・モジュール | パッケージ java.lang.module
モジュールの解決でモジュールグラフを構築するには、まずはじめにグラフの先頭となるモジュールを決定する。
これをルート・モジュールと呼ぶ。
ルート・モジュールは、コンパイル時と実行時でそれぞれ次のように決まる。
コンパイル時
コンパイル対象のモジュール
実行時
--module
(-m
) オプションで指定したモジュール
--module の意味
Jigsaw を触りたての頃、 --module
は「メインクラスを指定するためのオプション」くらいの認識だった5。
しかし、モジュールの解決について勉強しなおすなかで、 java
コマンドのリファレンスで次のように説明されていることに気付いた。
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
ルート・モジュールが決定したら、次はそのルート・モジュールの module-info.java
を見て6依存するモジュールを辿っていく。
このとき、依存するモジュールは次の4つの場所から順番に検索されていく。
-
--module-source-path
で指定されたモジュール(コンパイル時のみ) -
--upgrade-module-path
で指定されたモジュール(詳細後述) - システムモジュール
- ランタイムが用意している組み込みのモジュール
-
java.*
とかjdk.*
のモジュール- 固定で決まっているものではない
- jlink で作られたカスタムのランタイムなら標準の状態よりも少なくなることがありえる
- アプリケーションモジュール
-
--module-path
で指定した場所に存在するモジュール
-
これら検索可能な場所に存在するモジュールを総称して、**観測可能なモジュール (Observable modules)**と呼ぶ。
requires
で指定するモジュールは、この観測可能な範囲に存在していなければならない。
もし観測可能でないモジュールを requires
しようとすると、コンパイルまたは実行時にエラーになる。
システムモジュールを調べる
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);
}
}
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() メソッドを使えば、システムモジュールを検索する
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。
java.base
は他のモジュールを requires
することはないが、 uses
の宣言は大量に存在している(ServiceLoader は使っている)。
...
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
モジュールで提供されている。
provides java.net.ContentHandlerFactory with sun.awt.www.content.MultimediaContentHandlers;
この結果、
-
java.base
がuses
しているサービスのプロバイダーとなるモジュールを探してロードする - プロバイダーとなったモジュールが別のモジュールを
requires
しているのでそちらもロードする
という処理が再帰的に繰り返されて、上記のような長いログ出力になっている。
ちなみに、 java.desktop
モジュールは、ロードはされているがモジュールグラフ上には存在しない。
なので、 java.desktop
内にあるクラスを foo
モジュールなどで import
しようとするとエラーになる。
import
できるのは、あくまで requires
で依存関係を定義し、モジュールグラフ上で依存関係が直接つながっているモジュールに限られる。
後方互換の話が入るとややこしくなっていく
ここまでは、コンパイルや実行の対象が全てモジュール化されている場合の話。
正直これだけなら、割と簡単な話だと思う。
要はルート・モジュールを決めて、そこから requires
を辿ってグラフを作るというだけの話。
ここに後方互換性のために存在する無名モジュールや自動モジュールが関わってくると、特別ルールが追加されて話がややこしくなっていく。
クラスパスの問題点とモジュールシステムの Reliable configuration
モジュールシステムが導入された背景の1つとして、クラスパスの持つ脆さがある。
クラスパスには、アーティファクトを区別する仕組みはなく、型の検索は全てのアーティファクトが入ったクラスパス内から行われるようになっていた。
このため、アーティファクトの単位で事前に不足があるかどうかを判定できなかったり、異なるアーティファクトに同じパッケージが含まれていても検知する仕組みはなかった8。
モジュールシステムでは、プログラムの集合をモジュールという単位で定義し、それぞれの依存関係を管理できる。これにより、事前にモジュール単位で不足を検知できるようになった。
また、あるモジュールが別のモジュールのパッケージを読み込むときは、そのパッケージを含むモジュールは1つに定まることが保証されるようになった(requires
している複数の異なるモジュールに同じパッケージがあるとエラーになる)。
このようなモジュールシステムが持つ高い信頼性を Reliable configuration (信頼できる構成) と言う。
これは、モジュールシステムの重要な特徴の1つとなっている。
モジュールグラフは、この Reliable configuration を実現するための基礎となっている。
モジュールのバージョン管理は範囲外
同じモジュールのバージョン違いがモジュールパスに含まれていた場合、モジュールシステムはそれを検知しない(最初に見つかったモジュールが優先される)。
Non-Requirements | Java Platform Module System: Requirements
バージョンの選択については Maven のような既存のツールに任せることになっている。
そしてモジュールシステムは、バージョン選択が済んでいる前提で、残ったモジュールたちの検証に注力する方針となっている。
クラスパスは残っているが全く同じではない
クラスパスを捨てて全てをモジュール化できれば、この Reliable configuration を享受できる。
ところが、実際は後方互換を捨てるわけにはいかないので、クラスパスは Java 8 以前と同じ様な感じで使えるようになっている。
しかし、 -classpath
オプションは残っているものの、裏ではクラスパスで指定されたクラス達もモジュールシステム上で動くことになる。
従って、クラスパスを利用していればモジュールシステムは知らなくていいというわけではない。
クラスパスを利用していてもモジュール絡みのエラーが出る可能性は十分ある。
そういうときにモジュールシステムのことを知らないと、問題の解決に苦労するかもしれない。
無名モジュール
クラスパスから読み込まれたパッケージや型は、無名モジュール (Unnamed module) という特別なモジュールに属するようになる。
無名モジュールはモジュール定義 (module-info.java
) を持つことができないため、 requires
や exports
に関する定義を指定することはできない。
代わりに、無名モジュールはデフォルトで次のように振る舞う。
- 無名モジュール内の全てのパッケージは
exports
されている扱いになる - 無名モジュールは、モジュールグラフ内の全てのモジュールを
requires
している扱いになる
名前付きモジュールから無名モジュールは参照できない
無名モジュールは全てのパッケージを exports
していることになるが、それを名前付きモジュール9から参照することはできない。
これは、前述の Reliable configuration を維持するため、意図的にそういう仕様になっている。
もし名前付きモジュールから無名モジュール(クラスパス上のクラス)を参照できるようにすると、クラスパスの問題が再発することになり、せっかくモジュールシステムを導入した意味がなくなってしまう。
同じパッケージが、名前付きモジュールと無名モジュールの両方に存在した場合
同じパッケージが名前付きモジュールと無名モジュールの両方に存在した場合、名前付きモジュールに存在するパッケージが優先される。
このとき、エラーや警告は出ない。
例えば上図のように foo
パッケージが名前付きモジュールと無名モジュールの両方に存在したとする。
このとき、 foo
パッケージ内のクラスにアクセスしようとすると、名前付きモジュールの方が優先される。このため、無名モジュール側に存在する foo.FooService
にはアクセスすることができない。無理にアクセスしようとすると、コンパイル時なら型が見つからずエラーになり、実行時なら NoClassDefFoundError
が発生する。
意図せずこのような状態になっても警告は出ないため、モジュールグラフとクラスパスがどうなっているかを把握できていないと原因が分からず困ることになる10。
無名モジュールが起点となった場合のルート・モジュールの決定(2018-11-04 更新)
通常のモジュールをコンパイルした場合は対象のモジュールが、実行時は --module
で指定したモジュールがルート・モジュールとなった。
一方、コンパイル対象のソースに module-info.java
が含まれていなかったり、実行時のメインクラスがクラスパスからロードされた場合――すなわち無名モジュールを起点としてコンパイルやプログラムの起動が行われた場合、ルート・モジュールの決定方法は大きく変化する。
Java 11 以降
Java 11 で動作を確認したら、 Java 10 までとは違う動きになっていたので追記。
先に結論を書くと、 Java 11 からは次のルールでルートモジュールが決まる。
-
--upgrade-module-path
で指定した場所に存在する全てのモジュール - システムモジュール内に存在し、
to
による公開先の制限なしで最低1つはパッケージをexports
しているモジュール
java.se
どうのこうのの話はなくなっている。
OpenJDK 11 で、無名モジュールを起点とし --show-module-resolution
オプションを指定して起動すると、次のように出力される。
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
対応する Issue が次になる。
[JDK-8197532] Re-examine policy for the default set of modules when compiling or running code on the class path - Java Bug System
Java 11 で Java EE 系のモジュールが削除されたことで、ルートモジュールの解決が単純になったことが理由となっている。
java.se.ee
モジュールが削除されたことで、単純にシステムモジュール内のモジュールを全てルートにすればよくなったと思われる。
Java 10 まで
ルート・モジュールには、大きく次の2つが読み込まれる。
- ランタイムに
java.se
モジュールが存在する場合は、java.se
モジュールがルートとなる。
ランタイムにjava.se
モジュールが存在しない場合は、次の条件をすべて満たすモジュールがルート・モジュールとして読み込まれる- システムモジュール、または
--upgrade-module-path
で指定した場所に存在する - モジュール名が
java.*
にマッチする -
to
で公開先を限定していないパッケージが、 最低1つはexports
されている
- システムモジュール、または
- さらに、次の条件をすべて満たすモジュールもルート・モジュールとして読み込まれる
- システムモジュール、または
--upgrade-module-path
で指定した場所に存在する - モジュール名が
java.*
にマッチしない -
to
で公開先を限定していないパッケージが、 最低1つはexports
されている
- システムモジュール、または
つまり、ランタイムが提供するモジュールをできる限り根こそぎルートとして登録するようになっている。
これによって作成されるモジュールグラフは、ルート・モジュールだらけになる。
ここに、無名モジュールはモジュールグラフ上のすべてのモジュールを requires
している扱いになるという前述したルールが適用されることで、無名モジュールはランタイムが提供するほとんどのモジュールにアクセスできるようになる。
結果、 Java 8 以前に作られたプログラムでも、クラスパスに入れて実行すれば(ほぼ)問題なく動作するようになっている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
を確認してみると、次のようになっている。
module jdk.aot {
requires jdk.internal.vm.ci;
requires jdk.internal.vm.compiler;
requires jdk.management;
}
exports
されているパッケージが存在しない。
よって、前述の条件にあった「to
で公開先を限定していないパッケージが、 最低1つは 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 が非常に参考になる。
--add-modules に指定できる特殊な値
普通、 --add-modules
にはモジュール名を指定する。
しかし、モジュール名とは別に次の3つの特殊な値を指定することもできる。
ALL-DEFAULT
- 無名モジュールが起点となった場合と同じ要領でルート・モジュールが追加される
ALL-SYSTEM
- システムモジュールに存在するすべてのモジュールがルートになる
- テストのときとかに使うと便利らしい(よくわかってない)
- 当然多くのモジュールが読み込まれてしまうので、通常は
ALL-DEFAULT
の利用が推奨される
ALL-MODULE-PATH
- モジュールパスに存在するすべてのモジュールがルートになる
- Maven のような依存関係を解決できるツールを使っている場合に利用することが想定されているっぽい
- Maven は依存するライブラリを過不足なく集めてくるので、モジュールパスに存在する = 全部必要なモジュールということになる
- こっちのほうが、クラスの検索とかが早くなったりするのだろうか?(よくわかってない)
- 自動モジュールをまとめてルートに設定したいときにも便利らしい(よくわかってない)
これらは、例えば --add-modules ALL-MODULE-PATH
のような感じで指定する。
自動モジュール
無名モジュールは、主に自分のアプリケーションがモジュールに対応していない場合に利用することになる。
一方、自分のアプリケーションはモジュール化しているが、使用しているサードパーティのライブラリがモジュールに対応していないような場合に関係してくるのが、**自動モジュール (Automatic modules)**になる。
モジュール化されていない(module-info.class
が含まれていない)アーティファクトをモジュールパスに入れて利用した場合、そのアーティファクトは自動モジュールとして認識される。
自動モジュールの名前
自動モジュールのモジュール名は、次の優先順位で自動的に決定される。
-
MANIFEST.MF
のAutomatic-Module-Name
で指定された名前 - jar ファイル名から自動的に解決された名前12
自分のアプリケーションで自動モジュールを requires
する場合は、ここで決定されたモジュール名を指定する。
自動モジュールの requires と exports の扱い
自動モジュールも、無名モジュールと同じで明示的に requires
や exports
を定義することはできない。
そのため、自動モジュールは次のように振る舞うようになっている。
- すべてのパッケージを
exports
している扱いになる - モジュールグラフ上のすべてのモジュールを
requires
している扱いになる
つまり、 requires
と exports
については無名モジュールと同じ扱いになっている。
それ以外は、普通のモジュールと同じように requires
の対象として指定できるし、パッケージの重複を検出することもできる。
自動モジュールを含めたモジュールグラフのイメージ
例えば、自動モジュールなしの状態でグラフが下図のようになっていたとする。
ここに、仮に automatic
という名前の自動モジュールを追加すると、グラフは次のようになる。
さらに、自動モジュールは他の普通の名前付きモジュールから requires
できるので、次のように依存関係を追加することができる。
--upgrade-module-path でモジュールを差し替える
例えば、次のようなソースが存在したとする。
`-src/
|-module-info.java
`-sample/
`-Main.java
module sample {
requires java.transaction;
}
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 )
例外クラス3つだけを含み、 Status
クラスは存在しない。
一方、本家 Java EE の方の javax.transaction
パッケージは次のようになっている。
javax.transaction (Java(TM) EE 8 Specification APIs)
Status
クラスもちゃんと存在している。
コンパイルを通すためには、本家 Java EE の jar を持ってくる必要がある。
ということで、 Java EE 版の jar を落としてきて再度コンパイルしてみる。
>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。
この場合、システムモジュールの java.transaction
をなんとかしなければならない。
1つの解決方法として、システムモジュールの 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 の Javadoc を見ても java.naming
の記述は存在しない。
試しに module-info.java
を見てみたが、次のようになっていた。
module java.management.rmi {
requires java.naming;
requires transitive java.management;
requires transitive java.rmi;
...
}
ちゃんと java.naming
が requires
されている。
Javadoc に記載される情報に、 transitive
がついていないモジュールの情報は載らないということだろうか?
モジュールレイヤー
モジュールレイヤーは、モジュールグラフ内の各モジュールとクラスローダーとのマッピングを定義している。
JVM を起動すると、必ず1つのモジュールレイヤーが作成される。
これをブートレイヤーと呼び、 ModuleLayer.boot()
で取得できる。
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());
}
}
java.base module ClassLoader => null
java.sql module ClassLoader => platform
sample module ClassLoader => app
-
ModuleLayer.findLoader(String)
で、指定したモジュールにマッピングされたClassLoader
を取得できる -
ブートストラップクラスローダーは通常 null で表されるので、
java.base
のクラスローダーもnull
になる -
platform
はプラットフォームクラスローダー、app
はシステムクラスローダー(アプリケーションクラスローダー)を表している
いつ使うものか?
- 普通に Java アプリケーションを作る場合は、このモジュールレイヤーを意識することはほぼない
- モジュールレイヤーが関わってくるのは、複数のアプリケーションをホストするソフトウェアを作るような場合になる
- もしくは、 IDE やテストフレームワークのプラグイン機構とかにも使えるとかなんとか(よくわかってない)
- モジュールレイヤーは、動的に作成したり階層構造にすることができる
- これは、ちょうど Tomcat のようなコンテナ機能を持つアプリケーションが、デプロイされたアプリケーションごとにクラスローダーを分けるような感じと似ている
- つまり、ホストするアプリケーションごとにモジュールレイヤーを分けるような使い方ができる
- モジュールレイヤーを分けて利用するようなアプリケーションを今後使うようになったときに、この辺の話が関わってくるのかもしれない
モジュールレイヤーを作成する
実際にモジュールレイヤーを作成してみる。
|-foo/
| |-classes/ <---- foo モジュールのコンパイル結果
| `-src/
| |-module-info.java
| `-foo/
| `-Foo.java
|
|-classes/ <---- sampleモジュールのコンパイル結果
`-src/
|-module-info.java
`-sample/
`-Main.java
module foo {
exports foo;
}
package foo;
public class Foo {
public void hello() {
System.out.println("foooooooo!!!!");
}
}
module sample {
}
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);
}
}
> java -p classes -m sample/sample.Main
foooooooo!!!!
Configuration を作成する
// 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 を作成する
-
Configuration
はモジュールの解決の結果――――すなわちモジュールグラフを保持している -
Configuration
を作成するには、大きく次の3つを用意する- 親となる
Configuration
- モジュールを検索する
ModuleFinder
- ルート・モジュール名のコレクション
- 親となる
-
Configuration
は階層構造を取るようになっており、新しく作成するときは親のConfiguration
が必要となる- 何もないときはブートレイヤーの
Configuration
を使用する(ModuleLayer.boot().configuration()
で取得できる)
- 何もないときはブートレイヤーの
-
ModuleFinder はモジュールを検索する機能を提供する
-
ModuleFinder.of(Path...)
で作成したModuleFinder
は、Path
で指定した場所からモジュールを検索する -
Configuration.resolve(ModuleFinder, ModuleFinder, Set<String>) では、
before
とafter
の2つを指定する- モジュールを検索するときに、まず
before
のModuleFinder
でモジュールが検索される -
before
で見つからなければ、次は親のConfiguration
が検索される - それでも見つからない場合は
after
のModuleFinder
が検索される
- モジュールを検索するときに、まず
-
- ルート・モジュール名のコレクションは、モジュールグラフを作成するときのルート・モジュール名を指定するものになる
ModuleLayer を作成する
// 新しい ModuleLayer を作成
ModuleLayer newLayer = bootLayer.defineModulesWithOneLoader(newConfiguration, ClassLoader.getSystemClassLoader());
-
ModuleLayer
に用意されたメソッドに上で作成したConfiguration
を渡すことで、新しいModuleLayer
を作成できる -
ModuleLayer
も親のレイヤーを指定する必要があり、特にない場合はブートレイヤーを利用する -
ModuleLayer
を作成するためのメソッドは、大きく以下の3種類が存在する -
defineModules(Configuration, Function<String, ClassLoader>)
- 第二引数の
Function
は、モジュール名を受け取って、そのモジュールに対応するClassLoader
を返す実装を渡す
- 第二引数の
-
defineModulesWithOneLoader(Configuration, ClassLoader)
- これは、モジュールグラフ内の全てのモジュールに対して、1つの新しいクラスローダーが割り当てられる
- クラスローダーは
ModuleLayer
が内部で独自のものを作成する - 第二引数で渡す
ClassLoader
は、新規クラスローダーの親として利用される
-
defineModulesWithManyLoaders(Configuration, ClassLoader)
- これは、モジュールグラフ内のモジュール1つ1つに対して、クラスローダーが作成されて割り当てられる
- 第二引数で渡す
ClassLoader
は、新規クラスローダーの親として利用される
参考
- JEP 261: Module System
- java - Which system modules are on the module path by default? - Stack Overflow
- java.lang.module (Java SE 10 & JDK 10 )
- Java SE 9の紹介: モジュール・システムを中心に
- 実行時におけるJava SE 9モジュールとクラスローダーの関連 - 映画は中劇
- Java 9 - Modules · JVM. Blog.
- The State of the Module System
-
他にも定義できることはあるが、ここでは話を単純にするため基本となるこの2つの情報に絞る ↩
-
厳密には module-info.java だけでなく、実行時のオプションで定義を追加したり書き換えたりすることも可能 ↩
-
java.base
の話は今は割愛 ↩ -
パッケージにアクセスするためには、さらにそのパッケージが
exports
されている必要がある ↩ -
だからこそ、「なんで
--main
とかじゃなくて--module
なんだろう」みたいなモヤモヤがあった ↩ -
実行時なら
module-info.class
↩ -
ServiceLoaderについてはJigsaw 勉強メモ - Qiitaを参照 ↩
-
そして実行時に意図しない方のクラスがロードされNoSuchMethodErrorとかが発生する ↩
-
module-info.java
を持つ通常のモジュール、および後述する自動モジュールのこと ↩ -
実際遭遇して困った。 ↩
-
ルート・モジュールの決定ルールをよく読めばわかるが、完全にすべてのモジュールが対象になるわけではないので、そういうモジュールを使っていた場合に問題が発生する ↩
-
観測可能なモジュール内に同じ名前のモジュールが複数存在した場合、最初に見つかったモジュールが優先されるっぽい(システムモジュールはモジュールパスより先に検索される) ↩