Javaのモジュールシステム入門

Javaのモジュールシステム

この記事では、Java9から導入されたモジュール機能。
JPMS (Java Platform Module System)、開発名称Jigsawについて、
カンタンにまとめてみました。

今から勉強するよという方や、
どういうものなのかだけでも、簡単に知っておきたい方など、
入門として参考になると幸いです。

JPMS (Java Platform Module System)

java9 から導入された機能で、パッケージを更に包括したモジュールという概念を追加。

名称: JPMS (Java Platform Module System)
開発段階では、プロジェクト"Jigsaw"と呼ばれていたもの。

java8までは、パッケージのアクセス範囲は。
public, private, package-private, protected といった制御のみでした。

java9では、モジュールという大きい入れモノを用意し、
さらにパッケージを細かくアクセス制御できるようになりました。

それって、一体何が嬉しいの?

モジュール提供者は、
本来アクセスして欲しくないパッケージへのアクセスを、
禁止する事ができるようになります。

publicなクラスなんだけれども、それはライブラリ内だけにとどめたい。
というようなケースに適しています。

実際、すでにあるライブラリは、本来アクセスしてほしくなかったパッケージに、
多くのアプリケーションが依存しており、
スムーズにライブラリ更新ができなくなってしまっているものもある。

publicパッケージの細分化

モジュールで細かく制御できるようになった、publicパッケージ。

  • public + exports
    指定パッケージを、モジュール外でも参照可能にする。
    (今までのpublicと同じイメージ)
  • public + exports-to
    指定パッケージを、特定モジュールに限定して参照可能にする。
  • public
    自モジュール内部でのみ、publicとする。
    後述するモジュール定義ファイルにとくに何も記載しなければ、
    内部パッケージのpublicクラスは全てこの状態になります。

module-info.java

モジュールシステムを使う場合は、
新たにモジュール定義ファイルが必要になります。

module-info.java ファイルというのを、javaソースコード直下に配置する。
配下のパッケージは、こういうモジュールですよ。と定義する為のファイルです。

src/main/java/module-info.java

module モジュール名 {
    // ここに依存モジュール、公開パッケージを列挙する
}

モジュール名

モジュール名は何でもいいようですが、
パッケージ名由来のものにしておくほうが誤解がないでしょう。

例えば、下記のようなパッケージをまとめるような、モジュールであれば、
net.tyablog.hoge というモジュール名にしておきます。

  • net.tyablog.hoge
  • net.tyablog.hoge.piyo
  • net.tyablog.hoge.foo.bar

依存モジュール

括弧の中には、requires を使って、
この定義しているモジュールが、どのモジュールに依存するかを列挙します。

デフォルトで、java.baseというモジュールはロードされています。
java.baseモジュールは、javaのよく使うモジュールがまとめられたもの。

暗黙的に、requires java.base; されている状態になっています。

書き方

module モジュール名 {
    requires 依存モジュール名1;
    requires 依存モジュール名2;
    requires 依存モジュール名3;
}

transitive で依存関係を伝播

requiresにtransitive記述子を追記すると、依存を伝播させることが可能です。

transitive: 推移的な

単純に、requiresするだけだと、
そのモジュールからしかアクセスできないものになります。
しかし、requires transitive 依存モジュール名;と書くと、
依存モジュールを伝播させる事ができます。

module net.tyablog.sample {
    // java.desktopを伝播できる
    requires transitive java.desktop;
}

net.tyablog.sample モジュールを、requiresした場合、
java.desktopモジュールもreqiresした事と同等になります。

図解

パッケージの公開

モジュールから公開するパッケージは、exports する必要があります。
exports してない、何も書かない場合は、
モジュール内でのみアクセスできる、publicなパッケージとして扱われます。

他のモジュールからアクセスを許可するパッケージは、exports しましょう。

module モジュール名 {
    exports パッケージ名1;
    exports パッケージ名2;
    exports パッケージ名3;
}

モジュールの種類

モジュールの定義方法はわかりましたね。

module-info.java で定義したものは、厳密にはアプリケーションモジュールと呼ばれます。
名称自体には、あまり意味はありません。
後述するモジュールと区別する為に、覚えておくとよいでしょう。

Java でこれから、作るものは積極的にモジュール対応させておくほうが良いです。
しかし、モジュールはJava9から導入されている為、現在、全てのライブラリが、
モジュール対応されているわけでもありません。

対応がされるまでは、新旧が入り混じった状況になります。
そこで、モジュール対応されていないライブラリは、
下記の二種類の形で扱われる事になります。

自動モジュール (Automatic Module)

module-info.java を含まない、jarファイル、
モジュールPATHに配置することで、自動モジュールとして扱われます。
モジュールPATHとは、クラスPATHの代わりになるもので、
クラスPATH同様、jarファイルの場所として、Javaに指定します。

module-info.java が存在しない為、
モジュール名は、マニュフェストファイルの Automatic-Module-Name属性を参照します。
しかし、だいたいはそんな属性を設定はされてないので、
属性値がない場合は、jarファイル名を元に命名されます。
e.g. hoge-1.0.0.jar -> hoge@1

内包するパッケージは、全てexports / opensされたものとして扱われます。
通常アクセス、リフレクションでアクセス可能

無名モジュール (Unnamed Module)

前述の自動モジュールとは違い、jarファイル、classファイルを、
クラスPATHに配置した場合に、無名モジュールとして扱われます。

こちらは無名というだけあって、モジュール名は付与されません。
そして、この状態のモジュールは、自動モジュールからしかアクセスできません。
アプリケーションモジュールからは、アクセスができないので注意。

参照できるようにするには、モジュールパスに配置して、自動モジュールにする。

モジュール種類によるアクセス範囲

モジュールの実行

最後に、
モジュールを使った簡単な、Hello World!の実装例を載せておきます。

$ tree src
src
└─ main
     └─ java
          ├─ module-info.java
          └─ net
              └─ tyablog
                  └─ sample
                      └─ Sample.java

src/main/java/module-info.java

// モジュールの定義
module net.tyablog.sample {
    // 公開するパッケージは、exportsする。
    // 外部モジュールを参照する場合は、requiresが追記。
}

src/main/java/net/tyablog/sample/Sample.java

package net.tyablog.sample;

public class Sample {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

コンパイル後、実行する場合は、
クラスPATHの代わりに、モジュールPATHを指定して実行します。

  • -p, --module-path
    ビルドしたクラス、jarファイルの場所を指定する。

  • -m, --module
    実行するモジュールのメインクラスを指定する。
    モジュール名/クラス名 という感じで指定。

ビルドは、Gradleを使っていますが、
./gradlew buildすると以下のように成果物ができます。

$ tree build
├─ classes
│   └─ java
│       └─ main
│           ├─ module-info.class
│           └─ net
│               └─ tyablog
│                   └─ sample
│                       └─ Sample.class
└─ libs
     └─ sample-1.0-SNAPSHOT.jar

ビルドしたクラスファイルを、モジュールPATHに指定して実行。

$ java -p build/classes/java/main -m net.tyablog.sample/net.tyablog.sample.Sample
Hello World!

ビルドしたjarファイルを、モジュールPATHに指定して実行。

java -p build/libs -m net.tyablog.sample/net.tyablog.sample.Sample
Hello World!

まとめ

Java9以降では、JPMSにより、
パッケージを包括する、モジュールという概念が登場。

module-info.java ファイルで、
モジュール名、依存モジュール、公開パッケージを定義できる。

モジュール内のpublicパッケージは、さらに細かくアクセス制御できるようになりました。

  • public + exports
    requiresしたモジュールは、参照可能になる。
  • public + exports-to
    指定のモジュールが、requiresした場合にのみ、参照可能。
  • public
    外部モジュールからは参照不可。

非モジュールのライブラリ(jarファイル)は、以下のように扱われる。

  • 自動モジュール (モジュールPATHに配置)
  • 無名モジュール (クラスPATHに配置)

無名モジュールは、アプリケーションモジュールからアクセスする事はできない。

実行時は、
クラスPATHの代わりに、モジュールPATH (-p <モジュールPATH>) を指定して実行する。
実行メインクラスは、-m <モジュール名/メインクラス> の形で指定する。

コメントを残す