#モジュールとは
- パッケージの上位概念
- 構成
- パッケージ群と関連するリソース
- 自身の構成情報を規定するモジュール定義ファイル
#従来のパッケージ問題点
##パッケージ単位でアクセス権限を設定できない
- ライブラリ内部での利用を想定したパッケージの場合、ライブラリ外部からアクセスできないようにしたい
- しかし内部用途のpublicクラスを不可視にする手段はない
##.jarファイルは依存関係を表現できない
- 複数のパッケージを.jar形式の圧縮ファイルとしてまとめ、ライブラリとして提供
- しかし.jarではライブラリ間の依存関係は表現できない
- .jarの中でどの型のAPIが外部からの利用を想定しているのか、動作のためにどのライブラリが必要かわからない
###モジュール利用で解決!
- 特定のライブラリ、フレームワークをグループにして束ねる
- モジュール化されたライブラリではpublicクラスもより細かな管理が可能
- 現在のモジュールの中だけでpublic
- 特定のモジュールに対してのみpublic
- 全モジュールに対してpublic (従来と同じpublic)
#モジュール基本
- /sec直下のmodule-info.javaでモジュール定義ファイル記述
- モジュールが依存するパッケージ、外に公開するパッケージ情報を列挙
- 必要ライブラリをrequiresで宣言してないと
The type java.net.http.HttpRequest is not accessible
module-info.java
//モジュール名foo
//fooモジュールではjava.net.httpを必要
module foo {
requires java.net.http;
requires mylib;
requires gson;
}
#標準ライブラリのモジュール
- 標準ライブラリもモジュール化されている
##基本モジュール
- java.base
- 標準ライブラリを一個一個requires追加するのは面倒なので、よく利用するパッケージをjava.baseモジュールにまとめられている
- java.baseモジュールは暗黙的にロードされるので明示的にrequires宣言不要
- Java標準ライブラリ全体を定義したjava.seモジュールもあるが不要なモジュールもロードしてしまう
#推移的な依存
- 推移的:ロードしたモジュールの先でさらに他のモジュールに依存していた場合を依存先もロード
- requires transitiveで推移的な依存をいい感じに解決
module-info.java
module java.se {
requires transitive java.compiler;
requires transitive java.datatranfer;
requires transitive java.desktop;
//略
}
#パッケージ公開
- 自分のパッケージを公開したい場合
- 外部に公開したいパッケージ(lib)、ライブラリ内で利用するパッケージがある(internal)
- exportsディレックティブで公開したライブラリのみを外部公開できる
module-info.java
module mylib {
exports mylib.lib;
}
//OK
import mylib.lib.MainLib;
//NG! interna;パッケージは不可視
//import mylib.internal.Sublib;
public class ModuleClient {
public static void main(String[] args) {
var main = new MainLib();
main.run();
}
}
module foo {
requires java.net.http;
requires mylib;
requires gson;
}
##特定のモジュールに対してのみパッケージ公開
- exports パッケージ to モジュール
- 以下の場合、libパッケージはfooモジュールにのみ公開される
- 公開先は
,
で複数指定可能
module-info.java
module mylib {
exports mylib.lib to foo;
}
##privateメソッドをopenにする
-
ディープリフレクション:リフレクションを利用し強制的にアクセス
- モジュールexportsのみではディープリフレクションは不可能
import java.lang.reflect.InvocationTargetException;
import mylib.lib.MainLib;
public class ModuleClient2 {
public static void main(String[] args) {
try {
var clazz = MainLib.class;
var con = clazz.getConstructor();
var m = con.newInstance();
var name = clazz.getDeclaredField("name");
name.setAccessible(true); //エラー
System.out.println(name.get(m));
} catch (InstantiationException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
###opensディレクティブでパッケージ宣言して解決!
- あくまでも実行時のみパッケージ公開する
- リフレクション以外で型を参照する場合はexportsディレクティブも併記
- **
open module mylib {
**にするとモジュール配下のパッケージを全部open扱いにする
module-info.java
module mylib {
opens mylib.lib;
exports mylib.lib;
}
#特殊モジュール
- 擬似的なモジュール概念も提供されてる
- 自動モジュール
- 無名モジュール
##自動モジュール
- モジュールパスに配置された.jarファイル
-
module-info.java
を持たないライブラリ
###マニフェスト情報から決定
- マニフェストファイル(META-INF/MANIFEST.MF)のAutomatic-Module-Name属性で指定された名前をモジュール名にする
MANIFEST.MF
Manifest-Version: 1.0
Automatic-Module-Name: hoge.bar
###.jarファイル名で決定
- 命名規則
- 拡張子
.jar
は削除 - ハイフン以降の文字が数値/ドットのみの場合、ハイフン以降削除
- 英数字以外は
.
に変換 - 繰り返しドットは単一ドットに、先頭/末尾のドットは除去
- ex: hoge-bar-1.0.5.jarはhoge.bar
- 拡張子
- 動作ルール
- 配下の全パッケージをexports/opens
- モジュールパスに登録された全モジュール、無名モジュールをrequires
- 自動モジュールでexports/opensされたパッケージを他モジュールから利用するにはrequires必要
##無名モジュール
- クラスパスに配置された.jarファイル
- モジュール名持たない
- コンパイル時にアプリケーションモジュールからの参照は不可能
-
自動モジュールからの参照は可能
- アプリケーションモジュール:module-info.javaをもるモジュール
- プラットフォームモジュール:標準ライブラリを構成するモジュール
#非モジュールライブラリとの共存
- モジュール化されたアプリから非モジュールライブラリを利用する
- 以下のGsonライブラリはJavaオブジェクトをJSON形式の文字列に変換する
- java.sqlパッケージ依存
- 変換対象のJavaオブジェクトにディープリフレクション
- Gsonが内部的に利用するjava.sqlモジュールへの参照がないとjava.sql.Timeクラスにもアクセスできない
//NG例 実行時エラー
import com.google.gson.Gson;
public class NoModuleLib {
public static void main(String[] args) {
var g = new Gson();
var a = new Article("Java 11の変更点", "https://codezine.jp/article/corner/751");
//オブジェクト内容をJSON化した結果を出力
System.out.println(g.toJson(a));
}
}
Article.java
public class Article {
private String title;
private String url;
public Article(String title, String url) {
this.title = title;
this.url = url;
}
@Override
public String toString() {
return String.format("タイトル:%s(%s)",
this.title, this.url);
}
}
##実行オプションで明示的にモジュール追加
- 実行構成のVM引数で
-
--add-opens=モジュール名/パッケージ名=アクセス許可するモジュール
-
--add-modules=java.sql --add-opens=foo/example=gson
- fooモジュールのexampleパッケージをgsonライブラリからアクセス可能にする
-