LoginSignup
7

More than 1 year has passed since last update.

posted at

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

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
What you can do with signing up
7