Java 9に導入されたモジュールシステムについて、モジュールシステムの登場以前のクラスパスの世界を振り返りながら、Gradleを使っている場合の話も含めて整理してみようと思います。
モジュールとは何か
複数のパッケージを関連するもので固めてグループ化したものです。
モジュールシステムによって以下が実現されました。
- 依存関係の明確化: モジュール同士の依存関係を宣言するので、依存性をモジュール単位で検証できます。
- 公開範囲の設定: モジュール内のパッケージは明示的にエクスポートされたもののみが他のモジュールからアクセスできます。
モジュールシステムの登場以前のクラスパスの世界
まずはモジュールシステムの登場以前のクラスパスの世界を確認しておこうと思います。
古い時代からある話なので、古いドキュメントを参照してみましょう。
まず前提Javaのクラスには位置づけの異なる以下の3種類が存在します。
- ブートストラップクラス…主にjre/lib/rt.jarに存在するクラスのことです。
- 拡張機能クラス…jre/lib/ext以下の.jarに存在するクラスのことです。
- ユーザクラス…クラスパスで指定された場所に存在しているクラスのことです。クラスパスはjava -classpathで指定するか、環境変数のCLASSPATHで指定します。
Java 8以前の環境がある場合はインストール先のjre/以下をみてみてください。rt.jarとかがありますよね。
実は自分で作ったJarもjre/lib/ext以下に放り込んでおくとパスが通った状態になります。普通はそんなことはしませんけど。
クラス検索の優先順位
最強はブートストラップクラスで、これが最優先で解決されます。次は拡張機能クラスのパスに保存されているのが強いです。
ユーザークラスは-jar
で指定されているjarの中に入っているものがもっとも強く、次が-classpath
で最後が環境変数のCLASSPATHです。
jre/lib/rt.jar > jre/lib/ext > -classpath > 環境変数のCLASSPATH
-classpath
に;
区切りで複数のパスが指定されているときは前から順番に優先されるので、以下の例だとpath1が優先されます。
java -classpath '/home/testuser/path1/*;/home/testuser/path2/*' Main
ややこしいのはTomcatのようなものを使っている場合でもっと複雑になります。
以下の図は公式ページからの抜粋ですが、クラスローダーが以下のような階層構造を持っていて、Webapp1とWebapp2が相互にお互いは見えないように専用のクラスローダーを持ちつつ、Tomcatとして共有するクラスはCommonクラスローダーが読み込む仕組みになっています。
これまで説明してきたブートストラップクラスとか拡張機能クラスはこの図でいうBootstrapクラスローダーが読み込んでいて、Systemクラスローダーが普通に-classpath
を指定した場合などで使われるクラスローダーです。
モジュールシステム
- Project Jigsaw: Quick Start Guide
- 【Java】モジュールシステムめも【Java Silver】 - くまごろーのプログラミングメモ
- Javaのモジュールシステム入門 | tyablog.net
- Java JMODファイルとは、JARファイルとの違い。 | tyablog.net
モジュールシステムの話になったときに登場する用語、ファイルをまず整理してみます。
module-info.java
module-info.javaファイルはモジュールシステムの中核に存在するファイルです。
依存する自分以外のモジュールと、モジュール内のパッケージで他のモジュールに公開する範囲を記述します。
モジュールシステムはモジュールのバージョンの管理を役割に含めていないので、バージョンの指定はしません。
JARファイル
JARファイルは健在です。モジュールを一つのファイルにまとめる手段として引き続き利用されます。
JMODファイル
モジュールという単位で、クラスファイルとネイティブコードなどをZipもので、コンパイル、リンク時に利用します。
JARのようにランタイム時に参照はできません。
かつて存在していたブートストラップクラスとか拡張機能クラスはJMODに組み込まれました。
module-info.java
難しいことはなくてこれだけといえばこれだけです。
module moduleb {
// modulecに依存している
requires modulec;
// transitiveがあるとmoduledをmodulebをrequiresしたモジュールからも参照できるようになる
requires transitive moduled;
// モジュール内のパッケージの公開範囲
exports jp.co.module.b;
}
自動モジュールと無名モジュール
自動モジュール
自動モジュールはmodule-info.java を含まないjarファイルをモジュールPATHに配置したものです。
この場合、モジュール名はManifestファイルにAutomatic-Module-Name属性があればその値になり、なければjarファイル名をから作られます。
Jarの中のパッケージは、全てexportsされたものとして扱われます。
無名モジュール
jarファイル、classファイルを、クラスPATHに配置したものが無名モジュールです。
モジュール名はなく、この状態のモジュールは自動モジュールからしかアクセスできません。
まとめると以下のようになります。
引用:イマドキのJava徹底入門(5) Javaのモジュールシステムを理解しよう(その2) | TECH+
ビルド方法と依存関係の調べ方
Gradleなどを使わずにコンパイルを手でやることはあまりないかもしれませんが、以下のようにします。
javac -d ..\クラスファイルの出力先 javaファイルのパス javaファイルのパス javaファイルのパス
たとえば以下のような感じです。
javac -d ..\mods src\module-info.java src\jp\co\module\cprivate\HelloCCommon.java src\jp\co\module\c\HelloC.java
標準ライブラリのモジュール以外に依存するモジュールがある場合は以下のようにします。
javac -d ..\クラスファイルの出力先 --module-path 依存するモジュールのパス javaファイルのパス javaファイルのパス javaファイルのパス
たとえば以下のような感じです。
javac -d ..\mods --module-path ..\mods src\module-info.java src\jp\co\module\bprivate\HelloBCommon.java src\jp\co\module\b\HelloB.java
実行するときはこんな感じになります。
java --module-path モジュールのパス -m モジュール名/モジュール内のメインメソッドを持つクラスのフルパス
たとえば以下のような感じです。
java --module-path ..\mods -m modulec/jp.co.module.c.HelloC
依存関係を調べることもできます。
jdeps --list-deps hello.jar
モジュールシステムとGradle
7と7以前で違うのですが、7の場合で書きます。
7ではjava.modularity.inferModulePath
がデフォルトで有効になりました。
以下公式の抜粋ですが、モジュールの宣言とGradleの設定は以下の対応関係にするべきとされています。ただ、本当にこの設定になっているかどうかはチェックされません。また、モジュール未対応のライブラリの扱いは残念な感じになっていて、そのままでは利用することができません。
以下のようにモジュールシステムに対応していない'commons-cli:commons-cli:1.4'を使うのはとっても面倒です。
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.apache.commons:commons-lang3:3.10'
implementation 'commons-cli:commons-cli:1.4'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
test {
useJUnitPlatform()
}
module modtest.main {
requires com.google.gson;
requires org.apache.commons.lang3;
}
この状態になってしまうので、モジュールシステムに対応していないライブラリに依存している場合は、アーティファクト変換を使用して、既存のJarをモジュール対応させるか、モジュールシステムを使うのをあきらめるかとかが公式では案内されています…
つまりどうしたらいいのか?
これからアプリケーションを開発するときどうしたら良いのかですが、私は以下のように理解しています。
過渡期で仕方ないのかもしれないですけど、中途半端な感じですね…
モジュールシステムに対応していないライブラリに依存している場合
自作のアプリケーションは自動モジュールにします。
モジュールシステムに対応していないライブラリに依存していない場合
自作のアプリケーションは名前付きモジュールにします。
シリーズJava再入門
いまからJava8にしよう!だってJava11よりサポート長いし!という判断もあると思いますが、11に引っ越そうとしている方や、11の次のLTSになることを予定されている17への移行を目指している方向けのJava再入門です。
シリーズJava再入門
いまからJava8にしよう!だってJava11よりサポート長いし!という判断もあると思いますが、11に引っ越そうとしている方や、11の次のLTSになることを予定されている17への移行を目指している方向けのJava再入門です。
- Java7以降の歴史 機能追加とサポート期間
- モジュールシステムとクラスパス、Gradle
- Java再入門 JVMオプション
- Java再入門 ~Java11 ジェネリクス、ダイアモンド演算子
- Java再入門 ~Java11 日付時刻API
- ~Java11 リソース付きtry文
- ~Java11 ラムダ式、メソッド参照、Stream、Optional、ローカル変数型推論
- ~Java11 ExecutorService/Future、Fork/Join
- 今後:テキストブロック
- 今後:switch式、パターンマッチングinstanceof
- 今後:record