Java モジュールシステム

Java 11 Gold 取得に向けた学習記録

# モジュールシステム

複数のパッケージをまとめる仕組みを**モジュールシステム**という。

## 定義

モジュールの定義は `module-info.java`（モジュール定義ファイル）に記述する。このファイルに記述する情報は **モジュールディレクティブ** という。

```java:module-info.java
module モジュール名 {
    exports 公開するパッケージ名;
    requires このモジュールが依存する別のモジュール名;
}
```

`module-info.java`を配置したフォルダはモジュールとして扱われる。

パッケージ内ではクラス単位でアクセス制御を行うのに対して、モジュールシステムではパッケージ単位でアクセス制御を行うことができる。

## モジュール名

モジュール名はパッケージと同様に**一意**であることが必要。そのため他のモジュール名と被らない名前にする必要がある。

通常は、モジュールに含めるパッケージ名に共通の文字列があれば、その部分をモジュール名として使用する。共通部分がない場合は、モジュールの目的を表現する名前にする事が望ましい。

ちなみにモジュール名とパッケージ名は重複しても良い。

ただし、ドット「`.`」で区切ったパッケージがフォルダ階層にマッピングされるのに対して、モジュール名にドット「`.`」を含めても**フォルダ階層にマッピングされない**。そのため、モジュールを配置するフォルダ名はキャメルケースなどで命名する、もしくは`mods`などのプロジェクト毎に変わらない名前にする。


## モジュールディレクティブ

モジュール宣言で使用する`requires`、`exports`、`provides`、`with`、`uses`、`opens`、`provides` キーワードなどは **モジュールディレクティブ** と呼ばれる。

## `requires`

自身のモジュールが利用（依存）する外部の**モジュール名**を指定する。

```java:requires
module my.module {
    requires another.module;
}
```

### `requires transive`
transitive=推移的

依存するモジュールがさらに依存するモジュールについても暗黙的に含めることができる。モジュールグラフで表現すると[こちら](#モジュールグラフ)。

```java:moduleA
module moduleA {
    requires transive moduleB; // 暗黙的に moduleC にも依存する
}
```

```java:moduleB
module moduleB {
    requires moduleC;
}
```

```java:moduleC
module moduleC {

}
```


## `exports`

自身のモジュール内で外部に公開する**パッケージ名**を指定する。

```java:exports
module my.module {
    exports com.example.mypackage;
}
```

## `provides` / `with`

モジュールが特定の [サービス](#サービスプロバイダ) の実装を提供する [サービスプロバイダ](#サービスプロバイダ) であることを宣言する。プログラム実行時に [サービスローダ](#サービスローダ) が動的にサービスプロバイダをロードする際に、この情報が検索時のヒントとして利用される。

```java:provides / with
module my.module {
    provides com.example.MyServiceInterface with com.example.MyServiceImpl;
}
```

`provides` では [サービス](#サービスプロバイダ) を指定し、`with` では [サービスプロバイダ](#サービスプロバイダ) を指定する。

### サービスプロバイダ

特定の機能を定義したインターフェースまたは抽象クラスのことを**サービス**と呼び、サービスの実装クラスもしくは具象クラスのことを**サービスプロバイダ**という。

```java:サービス
package com.example;

public interface MyServiceInterface {
    void provideService();
}
```

```java:サービスプロバイダ
package com.example.imple;

import com.example.MyServiceInterface;

public class MyServiceImpl implements MyServiceInterface {
    @Override
    public void provideService() {
        System.out.println("サービスを提供します");
    }
}
```

`provides` ディレクティブでは サービス を指定し、`with` ディレクティブでは サービスプロバイダ を指定する。これにより、自身のモジュールがサービスプロバイダを提供できることが外部に宣言される。

```java:module-info.java
module my.module {
    provides com.example.MyServiceInterface with com.example.impl.MyServiceImpl;
}
```

サービスプロバイダを含むパッケージは`exports`しない（推奨）。サービスプロバイダはコンパイル時ではなくプログラムの実行時に[サービスローダ](#サービスローダ)によってリフレクションを利用してロードされるので、あえて実装（サービスプロバイダ）を公開する必要がない。

```java:非推奨
module my.module {
    exports com.example.impl; // サービスプロバイダを含むパッケージは公開しなくて良い
    provides com.example.MyServiceInterface with com.example.impl.MyServiceImpl;
}
```

通常、特定のサービスに対しては複数のサービスプロバイダが存在する。

```java:別のサービスプロバイダ
package com.another.example.impl;

import com.example.MyServiceInterface;

public class AnotherServiceImpl implements MyServiceInterface {
    @Override
    public void provideService() {
        System.out.println("別のサービスを提供します");
    }
}
```

別のサービスプロバイダが定義されたモジュール。

```java:module-info.java（別のサービスプロバイダを提供するモジュール）
module my.another.module {
    requires my.module; // サービスの定義を含むモジュール
    provides com.example.MyServiceInterface with com.another.example.impl.AnotherServiceImpl;
}
```

### サービスローダ

`module-info.java`で`provide`ディレクティブによって指定されたサービスプロバイダ（サービスを実装したクラス。`MyServiceImpl`、`AnotherServiceImpl`）を、プログラムの実行時に動的に探し出し、ロードする機能を持つものを **サービスローダ** という。

動的に検索したいサービスは、`uses`ディレクティブで指定する必要がある。

```java:uses
module consumer.module {
    uses com.example.MyServiceInterface; // 動的にロードしたいサービス
}
```

サービスローダの機能は`java.util.ServiceLoader` から提供されている。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/ServiceLoader.html

サービスローダはサービスプロバイダ（サービスの実装クラス）をロードして、メソッドを実行するためのフレームワークを提供している。

ロードは`load()`メソッドによって行う。

> `public static <S> ServiceLoader<S> load​(Class<S> service)`

```java:ServiceLoader.load()
import java.util.ServiceLoader;

ServiceLoader<MyServiceInterface> serviceLoader = ServiceLoader.load(MyServiceInterface.class);
```

引数の`Class<S>`オブジェクト取得方法はいくつかバリエーションがある。

https://qiita.com/AsahinaKei/items/e5319b53e110e200e8dd

`ServiceLoader<S>`オブジェクトは`Iterable`インターフェースを実装しているため、反復処理（`for`文）を使って各要素にアクセスすることができる。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Iterable.html

```java:
import java.util.ServiceLoader;

ServiceLoader<MyServiceInterface> serviceLoader = ServiceLoader.load(MyServiceInterface.class);
for (MyServiceInterface service : serviceLoader) {
    // サービスプロバイダ（サービスの各実装クラス）のメソッドが実行される
    // この時、アプリケーション（クライアント）はどの実装クラスが取得されるかを知らない
    service.provideService();

    // >> サービスを提供します
    // >> 別のサービスを提供します
}
```



サービスローダによってアプリケーションとサービスプロバイダが分離されるため、アプリケーションコードを一切変更することなく、特定のモジュールをモジュールパスに追加するだけでプラグインや拡張機能として動的に追加することが可能になる。

ただし、[`uses`](#uses)ディレクティブでどのサービスを使用するのかを指定しておく必要がある。

:::note info
サービスローダは`uses`ディレクティブと併せて使う
:::


## `uses`

`uses`キーワードはモジュールが特定の[サービス](#サービスプロバイダ)を使用することを[サービスローダ](#サービスローダ)伝えることができる。サービスローダはこの情報を基に、サービスの実装を動的に取得する。

```java:uses
module consumer.module {
    requires my.module; // サービスとサービスプロバイダが含まれるモジュール
    requires my.another.module; // my.moduleとは異なるサービスプロバイダが含まれるモジュール
    uses com.example.MyServiceInterface; // サービス
}
```

## `opens`

リフレクションによるアクセスを許可するパッケージを指定する。

https://qiita.com/AsahinaKei/items/e5319b53e110e200e8dd

```java
module my.module {
    opens com.example.mypackage
}
```

モジュール宣言において、`exports` を使用して外部へ公開したパッケージは通常、`public`、　`protected`  宣言したものだけがリフレクションからのアクセスできる。

`opens` ディレクティブで指定したパッケージは、パッケージとしては公開されない一方で、`public`、　`protected` 以外もリフレクションからのアクセスの対象にすることができる。

また `opens <package> to <module>` とすることで、リフレクションを許可するモジュールを限定することができる。

```java:opens to
module moduleA {
    opens com.example.mypackage to moduleB;
}
```

モジュール全体をリフレクションの対象とすることもできる。

```java:opens（モジュール全体）
open module my.module {
    
}
```

## コンパイル

`module-info.java` をコンパイルすると、**モジュール記述子**が作成される。モジュール記述子は、モジュールのルートフォルダ内の `module-info.class` に格納される。

## プラットフォームモジュール

標準クラスライブラリをモジュールとして提供する`java.base`モジュールについては、`requires`を記述しなくても使用することができる。このようなモジュールを**プラットフォームモジュール**と呼ぶ。

## 名前つきモジュール

モジュール定義ファイル（`module-info.java`）を持つモジュール。

## 自動モジュール

`module-info.java`を持たないモジュール。

モジュールシステム自体が Java 9 で導入されたため、それ以前のアプリケーションもしくはライブラリとの互換性を保つためのもの。

`module-info.java`を持たないため自動的にモジュールとして扱われ、全てのパッケージが公開され、パッケージ単位でアクセス制御を行うことができない。

また、 `project_root/META_INF/` に配置したマニフェストファイル（`MANUFEST.MF`）内の `Automatic-Module-Name` 属性を定義することで、モジュール名を定義することはできる。マニフェストファイルすら存在しない場合、jarファイル名がモジュール名として扱われる。

## 無名モジュール

`module-info.java`を持たないことに加え、モジュールパスではなくクラスパス上に配置されたjarファイル。

[自動モジュール](#自動モジュール)と同様に、Java 9 以前のアプリケーションとの互換性のためのもの。Java 9 以降では、通常はモジュール宣言が必要なため、[名前つきモジュール](#名前つきモジュール)から無名モジュールを参照することはできない。

:::note info
名前つきモジュールから無名モジュールへはアクセスできない
:::



## モジュールグラフ

モジュール間の依存関係を表現した図を**モジュールグラフ**と言う。

`moduleA` が `moduleB` に依存し、`moduleB` が `moduleC` に依存している場合、モジュールグラフは以下のようになる。

![module.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/8d4f9f67-3f17-a36c-cd64-a9d4cf8f63ed.png)

この状況で `moduleB` が `moduleC` への依存を `moduleA` に伝播させることができる。

モジュールの依存を伝播させるためには[`transitive`](#requires)を使用する。

```java:module-info.java（moduleB）
module moduleB {
    requires transitive moduleC;
}
```
これによりモジュールAは`requires moduleC`を記述しなくても直接モジュールCが使用できるようになる。

この時、`moduleA` と `moduleC` には**推移的な依存関係**があると表現される。

![module_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/7896b1d6-8ce6-18b0-3537-da3841602ce4.png)


## 例
- モジュール名: `a.b.c`
- モジュールに含めるパッケージ
  - `x.y.z` パッケージ
  - `x.y.zz` パッケージ

（通常はパッケージの共通部分である`x.y`を使用してモジュール名は`x.y.myModule`などとした方が良い。）

```
project
   |
   |--- src
   |    |
   |    |--- abc  ◀️ モジュールのルートディレクトリ（階層にしない。キャメルケースが適切？）
   |          |
   |          |--- x  ◀️ パッケージのルートディレクトリ
   |          |    |
   |          |    |--- y
   |          |         |
   |          |         |--- z
   |          |         |    |
   |          |         |    |--- Main.java
   |          |         |
   |          |         |--- zz
   |          |              |
   |          |              |--- MyClass.java
   |          |
   |          |--- module-info.java
   |
   |--- mods  ◀️ コンパイルの出力先（ ◀️ ◀️ ◀️ コンパイル後のモジュールのルートディレクトリ）
          |
          |--- abc  ◀️ コンパイルしたモジュール用のフォルダ
```

### コンパイル

コンパイル時には`-d`オプションで出力先を指定することができる。

```shell:コンパイル時の構文
javac \
-d コンパイルしたクラスファイルの格納先 コンパイル対象のソースファイル
```

```shell:コンパイル
javac \
-d mods/abc \
src/abc/module-info.java  \
src/abc/x/y/z/Main.java  \
src/abc/x/y/zz/MyClass.java
```

### 実行

コンパイルしたクラスファイルを指定して実行する。

モジュールを実行したい場合、`--module-path`（`-p`）オプションでモジュールのルートディレクトリを指定し、`-module`（`-m`）オプションで実行したいモジュールのクラスを指定する。

モジュール名とパッケージはスラッシュ「`/`」で区切る。

```shell:モジュール実行時の構文
java \
--module-path コンパイル後のモジュールのルートディレクトリ \
-module モジュール名/FQCN（エントリーポイント）
```

```shell:モジュールの実行
java \
--module-path mods \
-module a.b.c/x.y.z.Main
```

### JARファイル化

モジュールをJARファイルにまとめたいときには以下のようにする。

- `--create`:　新しくJARファイルを作成する
- `--file-name`（`-f`）:　JARファイルの名前
- `--main-class`:　エントリポイントのFQCN
- `-C`:　JARファイル化するフォルダ（フォルダ名をドット「`.`」にすることでフォルダ内の全てのファイルを対象にする）


```shell:JARファイル化の構文
jar --create \
--file-name JARファイル名 \
--main-class エントリポイント \
-C JARファイルにするフォルダパス
```

```shell:JARファイル化
jar --create \
--file-name jars/a.b.c.jar \
--main-class x.y.z.Main \
-C mods/abc .
```

### JMODファイル化
JMODファイルは、モジュールを**バンドル化**して配布するための形式の1つ。

バンドル化とは、ソフトウェアの複数の要素（例えば、クラスファイル、リソースファイル、ライブラリなど）を一つのアーカイブファイルにまとめることを指す。

JavaのモジュールシステムにおいてJMOD形式のファイルはこのバンドル化の一種であり、モジュールに関連するすべてのファイルを一つの単位として扱いやすくする。

```shell:モジュールをJMOD形式化する構文
jmod create \
--class-path クラスパス \
出力JMODファイル名 モジュール名
```

```shell:モジュールをJMOD形式化する
jmod create \
--class-path mods/abc \
abc.jmod a.b.c
```

https://qiita.com/AsahinaKei/items/81c5d9829ddde75dbc24



### 情報を確認する1

モジュールが公開するパッケージや依存するモジュールを調べる方法。

```shell:モジュールの情報を確認する構文1
java \
--module-path コンパイル後のモジュールのルートディレクトリ \
--describe-module モジュール名
```

```shell:モジュールの情報を確認する1
java \
-module-path mods \
--describe-module a.b.c
```

### 情報を確認する2

対象のモジュールがJMOD形式の時の使用できる、モジュールが公開するパッケージや依存するモジュールを調べる方法。

```shell:モジュールの情報を確認する構文2
jmod describe モジュール名
```

```shell:モジュールの情報を確認する2
jmod describe a.b.c
```

### 情報を確認する3（依存関係のみ調べる）

`jdeps`コマンドを使うと、モジュールに限らずJARファイルやクラス同士の依存関係を調べることができる。

```shell:JARの依存関係を調べる
jdeps JARファイル名
```

```shell:モジュールの依存関係を調べる構文
jdeps --module-path コンパイル後のモジュールのルートディレクトリ -module モジュール名
```

```shell:モジュールの依存関係を調べる構文
jdeps --module-path mods -module a.b.c
```


# 参考
- [徹底攻略Java SE 11 Gold問題集[1Z0-816]対応](https://amzn.asia/d/0eV9L1Wp)
- [Java 9 Modulesの理解](https://www.oracle.com/jp/corporate/features/understanding-java-9-modules.html)