0
0

More than 1 year has passed since last update.

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

Posted at

目次

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
このシステムの恩恵を受ける人は「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

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

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

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

「自システムのプログラムより、依存ライブラリへの影響の方が大きそうだな...:grinning:

これに対する影響調査は、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

こんな感じで2つともクローンする
Desktop Screenshot 2023.08.04 - 19.32.06.99.png

・無事にインポートできれば環境はOK
Desktop Screenshot 2023.08.04 - 20.02.59.79.png

2-2. 検証資材の概要

・ライブラリ側のmodule-info.java
公開したくない内部用のutilパッケージのexports(公開設定)はコメントアウトしています。

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)していることを宣言。

pen_study_module_system_client/module-info.java
module pen_study_module_system_client {
    requires pen_study_module_system_lib;
}

・クライアント側がライブラリを用いて実装したクラス
引数を整形して標準出力するAPIを呼び出しているだけ。
クライアント側プロジェクトのlibにライブラリ側のjarを仕込んであるので、import可能な状態。

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
「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以前のクラスパスとほぼ同じ動作をすると理解しています。
レガシーなライブラリも「無名モジュールとしてモジュールシステムの上で動作する」という点は、
覚えておく必要がありそうです。

<参考>

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
Desktop Screenshot 2023.08.04 - 23.41.55.71.png

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

・コンパイルエラーが解消される
お互いが無名モジュール化されたため、
ライブラリ側は「全てのパッケージを公開」し、
クライアント側は「可能な限り、パッケージを読み込む」という動作となっている。
Desktop Screenshot 2023.08.04 - 23.46.19.99.png

3. さいごに

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

0
0
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
0
0