【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`


```java: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**で推移的な依存をいい感じに解決

```java:module-info.java
module java.se { 
  requires transitive java.compiler;
  requires transitive java.datatranfer;
  requires transitive java.desktop;
//略
}
```
#パッケージ公開
* 自分のパッケージを公開したい場合
    * 外部に公開したいパッケージ（lib）、ライブラリ内で利用するパッケージがある（internal）
* **exportsディレックティブ**で公開したライブラリのみを外部公開できる

```java:module-info.java
module mylib { 
  exports mylib.lib;
}
```

```java
//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();
  }
}
```
```java
module foo {
  requires java.net.http;
  requires mylib;
  requires gson;
}
```

##特定のモジュールに対してのみパッケージ公開
* **exports パッケージ to モジュール**
* 以下の場合、libパッケージはfooモジュールにのみ公開される
* 公開先は`,`で複数指定可能

```java:module-info.java
module mylib {
  exports mylib.lib to foo;
}
```

##privateメソッドをopenにする 
* **ディープリフレクション**：リフレクションを利用し強制的にアクセス
    * モジュールexportsのみではディープリフレクションは不可能

```java
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扱いにする

```java:module-info.java
module mylib {
  opens mylib.lib;
  exports mylib.lib;
}
```

#特殊モジュール
* 擬似的なモジュール概念も提供されてる
    * **自動モジュール**
    * **無名モジュール**

##自動モジュール
* モジュールパスに配置された.jarファイル
* `module-info.java`を持たないライブラリ

###マニフェスト情報から決定
* マニフェストファイル(META-INF/MANIFEST.MF)の**Automatic-Module-Name属性**で指定された名前をモジュール名にする

```java: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クラスにもアクセスできない

```java
//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));
  }
}
```

```java: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ライブラリからアクセス可能にする