【Java】模块
模块是什么?
-
- パッケージの上位概念
-
- 構成
パッケージ群と関連するリソース
自身の構成情報を規定するモジュール定義ファイル
传统的包装问题
无法在包级别上设置访问权限
-
- ライブラリ内部での利用を想定したパッケージの場合、ライブラリ外部からアクセスできないようにしたい
- しかし内部用途の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
//モジュール名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 java.se {
requires transitive java.compiler;
requires transitive java.datatranfer;
requires transitive java.desktop;
//略
}
发布包
-
- 自分のパッケージを公開したい場合
外部に公開したいパッケージ(lib)、ライブラリ内で利用するパッケージがある(internal)
exportsディレックティブで公開したライブラリのみを外部公開できる
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 mylib {
exports mylib.lib to foo;
}
将private方法改为public
ディープリフレクション:リフレクションを利用し強制的にアクセス
モジュール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 mylib {
opens mylib.lib;
exports mylib.lib;
}
特殊模块
-
- 擬似的なモジュール概念も提供されてる
自動モジュール
無名モジュール
自动模块
-
- モジュールパスに配置された.jarファイル
module-info.javaを持たないライブラリ
从宣言信息做出决定
- マニフェストファイル(META-INF/MANIFEST.MF)のAutomatic-Module-Name属性で指定された名前をモジュール名にする
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));
}
}
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ライブラリからアクセス可能にする