LoginSignup
4
13

More than 3 years have passed since last update.

【Java】モジュール

Posted at

モジュールとは

  • パッケージの上位概念
  • 構成
    • パッケージ群と関連するリソース
    • 自身の構成情報を規定するモジュール定義ファイル

従来のパッケージ問題点

パッケージ単位でアクセス権限を設定できない

  • ライブラリ内部での利用を想定したパッケージの場合、ライブラリ外部からアクセスできないようにしたい
  • しかし内部用途の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ライブラリからアクセス可能にする
4
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
13