モジュールシステムを使ってみた~OpenJDK17へのアップデートに備えて~



# 目次
- **1. はじめに**
    - [1-1. 執筆時点の人物像](#1-1-執筆時点の人物像)
    - [1-2. 執筆のモチベーション](#1-2-執筆のモチベーション)
    - [1-3. モジュールシステムとは？](#1-3-モジュールシステムとは)
    - [1-4. JEP403という壁](#1-4-jep403という壁)
  
- **2. 検証(検証用のプロジェクトをGitHubで公開しています)**
    - [2-1. 検証環境](#2-1-検証環境)
    - [2-2. 検証資材の概要](#2-2-検証資材の概要)
    - [2-3. カプセル化の検証](#2-3-カプセル化の検証)
    - [2-4. 無名モジュール](#2-4-無名モジュール)

## 1-1. 執筆時点の人物像
・入社5年目
・Webシステム Java(Spring系) 
・AP基盤レイヤ担当
・その他、誰もやりたがらないこと（性能試験、CI/CD構築、試験環境構築...）を担当


## 1-2. 執筆のモチベーション
担当しているシステムが、
OpenJDK 1.8からOpenJDK 17へバージョンアップすることとなり、調査を実施しました。

RHELで最長サポートのものを選択していると、
1.8⇒11⇒17と段階的にアップデートできないプロジェクトは多いのかと思われます。
**【※1 RHELのOpenJDKサポート期限】**
https://access.redhat.com/articles/1299013

調査の中で、モジュールシステムと呼ばれる、
Javaの根幹的な機能追加を自環境でお試ししたため、内容をまとめてみました。

## 1-3. モジュールシステムとは？
**「パッケージ単位での公開/非公開の設定が可能となるビルド形式」**
（詳しい人からツッコミを受けそうな表現...）
![Desktop Screenshot 2023.08.04 - 11.47.56.93.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525170/699bbe2d-cfe8-bb17-a99a-c1b5ae99a34a.png)
このシステムの恩恵を受ける人は「Javaライブラリ提供者」です。

1.8以前のJavaでは、
**「内部実装の都合上は、publicなんだけど、ライブラリを使用するAPには公開したくない」**
という要求を実現できませんでした。

「仲間内だけで使うためのものを、意図しない使い方をされ、バグ扱いにされる」
なんてシーンを想像すると、この機能のメリットがイメージできると思います。
（本来論で言うと、セキュリティ的な観点が強いはずですが）

9以降のJavaでは、この問題にアプローチするために、
「パッケージ単位の公開設定」が可能となりました。


## 1-4. JEP403という壁
**筆者がモジュールシステムを理解しようしたきっかけです。**

OpenJDK 9からOpenJDK17までのJEP(Javaの大きな機能追加・修正単位)を読み漁る中で、
自システムへの影響がかなり大きそうな変化点を発見しました。

**JEP 403: JDK 内部を強力にカプセル化する**
[JEP 403: Strongly Encapsulate JDK Internals](https://openjdk.org/jeps/403)

一言で表すと、
**「公開するつもりがなかった、内部クラスへのアクセスを原則禁止にします！」**

このアクセス禁止措置の実現に
[1-3. モジュールシステムとは？](#1-3-モジュールシステムとは)で説明した機能が使われています。

OpenJDK 16までは温情措置的な感じで、逃げ道(-illegal-access)があったのですが、
OpenJDK 17以降は、逃げ道が使えなくなりました。

**<font color="Red">「自システムのプログラムより、依存ライブラリへの影響の方が大きそうだな...:grinning:」</font>**

これに対する影響調査は、OpenJDKの標準機能で提供されている「jdeps」を使い、
アップデート対象の依存ライブラリを特定するアプローチを取りましたが、
まだ結果が出ていないため、結果がわかったら記事にしようと思います。
クローズド環境での開発が必須であるため、jarを自力でかき集めてくる地獄の幕開けです。

## 2-1. 検証環境
筆者は「習うより慣れよ」派なので、理解したことをアウトプットしてみました。
**・Eclipse**
こだわりはないので、All in Oneで最新版をダウンロード。
https://mergedoc.osdn.jp/
```
Version: 2023-06 (4.28.0)
Build id: 20230608-1333
```

**・サンプルソース**
＜ライブラリ側＞
https://github.com/ktmk526179/pen-study-module-system-lib.git

＜クライアント（ライブラリ使用AP）側＞
https://github.com/ktmk526179/pen-study-module-system-client.git

こんな感じで２つともクローンする
![Desktop Screenshot 2023.08.04 - 19.32.06.99.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525170/d13beb3c-f235-0d50-e7da-729c997914d3.png)

**・無事にインポートできれば環境はOK**
![Desktop Screenshot 2023.08.04 - 20.02.59.79.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525170/732fca4e-7051-860d-4bf9-7611839a2fee.png)

## 2-2. 検証資材の概要

**・ライブラリ側のmodule-info.java**
公開したくない内部用のutilパッケージのexports(公開設定)はコメントアウトしています。
```java:pen_study_module_system_lib/module-info.java
module pen_study_module_system_lib {
    // 1つ1つexportする必要がある
    // 本来公開するべきパッケージ
    exports pen.study.core;

    // 公開したくない内部用パッケージ
    // exports pen.study.internal.util;
}
```
**・クライアント側のmodule-info.java**
module名「pen_study_module_system_lib」に依存(requires)していることを宣言。
```java:pen_study_module_system_client/module-info.java
module pen_study_module_system_client {
    requires pen_study_module_system_lib;
}
```
**・クライアント側がライブラリを用いて実装したクラス**
引数を整形して標準出力するAPIを呼び出しているだけ。
クライアント側プロジェクトのlibにライブラリ側のjarを仕込んであるので、import可能な状態。
```java:pen_study_module_system_lib/pen.study.client.ClientMain.java
package pen.study.client;

import java.util.ArrayList;
import java.util.List;

import pen.study.core.Array2Stdout;
//import pen.study.internal.util.Array2StringUtil;

/**
 * クライアント側想定のメインクラス
 * 
 * @author pentaro
 */
public class ClientMain {

    /**
     * 引数が標準出力される
     * 
     * @param args
     */
    public static void main(String[] args) {
	// リスト型に変換
	List<String> listArgs = new ArrayList<String>();

	// 詰めなおし
	for (String arg : args) {
	    // 引数の型をString配列にすればよかった
	    listArgs.add(arg);
	}

	// 呼び出し
	Array2Stdout.stdout(listArgs);

	// モジュールシステムが導入されるまでは、publicであれば呼び出せた
	// Array2StringUtil.array2String(listArgs);

    }

}
```

## 2-3. カプセル化の検証
**①exportsしなかったパッケージが呼び出せないことを確認**
ClientMain.javaの7行目のimport分と,35行目の呼び出し個所のコメントアウトを解除すると...
![Desktop Screenshot 2023.08.04 - 20.22.51.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525170/493c0155-3c85-528e-8262-d756a2c8912f.png)
「is not Accessible」とのことなので、きちんとカプセル化できていることが確認できた。
```
説明	リソース	パス	ロケーション	タイプ
Array2StringUtil を解決できません	ClientMain.java	/pen_study_module_system_client/src/pen/study/client	行 35	Java 問題
The type pen.study.internal.util.Array2StringUtil is not accessible	ClientMain.java	/pen_study_module_system_client/src/pen/study/client	行 7	Java 問題

```

## 2-4. 無名モジュール
両プロジェクトのmodule-info.javaを削除し、無名モジュール(※1)とすることで、
「pen.study.internal.util.Array2StringUtil」へのアクセス制限を削除します。

**※1 unnamed-module(無名モジュール)**
筆者は、とりあえずOpenJDK1.8以前のクラスパスとほぼ同じ動作をすると理解しています。
レガシーなライブラリも「無名モジュールとしてモジュールシステムの上で動作する」という点は、
覚えておく必要がありそうです。

＜参考＞

https://dev.java/learn/modules/unnamed-module/
>All modules have three central properties and this is also true for the unnamed module:
a name: the unnamed module has none (makes sense, right?), which means no other module can mention it in its declaration (for example to require it)
dependencies: the unnamed module reads all other modules that make it into the graph
exports: the unnamed module exports all its packages and also opens them for reflection

モジュール名：ない
依存関係：アクセス可能なすべてのモジュールを読み込む
公開設定：全パッケージが公開される

**・両プロジェクトの「module-info.java」を削除し、ライブラリ側をビルドする**
「pen_study_module_system_lib」を「右クリック」⇒「エクスポート」⇒「Java」⇒「JARファイル」
![Desktop Screenshot 2023.08.04 - 23.35.31.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525170/beb0d4f6-13e2-39fb-7ea8-59f4bf70f56d.png)
![Desktop Screenshot 2023.08.04 - 23.41.55.71.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525170/5af80cd1-3898-888f-3e06-ff5f04a40a5d.png)

任意の場所にjarを作成し「pen_study_module_system_client」（クライアント側）の
「lib」フォルダのファイルを上書きする。
無名モジュール化したライブラリへの上書きが完了したら「Ctr+B」でプロジェクトを再ビルドすると...

**・コンパイルエラーが解消される**
お互いが無名モジュール化されたため、
ライブラリ側は「全てのパッケージを公開」し、
クライアント側は「可能な限り、パッケージを読み込む」という動作となっている。
![Desktop Screenshot 2023.08.04 - 23.46.19.99.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3525170/878ac99d-1e71-a44e-78cd-0d14158bfc20.png)

## 3. さいごに
本記事で紹介した内容は、モジュールシステムのHelloworldレベルの内容です。
他にも様々な機能がありますが、この記事の内容が学習の足掛かりになれば幸いです。