目次
-
1. はじめに
-
2. 検証(検証用のプロジェクトをGitHubで公開しています)
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. モジュールシステムとは?
「パッケージ単位での公開/非公開の設定が可能となるビルド形式」
(詳しい人からツッコミを受けそうな表現...)
このシステムの恩恵を受ける人は「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以降は、逃げ道が使えなくなりました。
「自システムのプログラムより、依存ライブラリへの影響の方が大きそうだな...」
これに対する影響調査は、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-2. 検証資材の概要
・ライブラリ側のmodule-info.java
公開したくない内部用のutilパッケージのexports(公開設定)はコメントアウトしています。
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)していることを宣言。
module pen_study_module_system_client {
requires pen_study_module_system_lib;
}
・クライアント側がライブラリを用いて実装したクラス
引数を整形して標準出力するAPIを呼び出しているだけ。
クライアント側プロジェクトのlibにライブラリ側のjarを仕込んであるので、import可能な状態。
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行目の呼び出し個所のコメントアウトを解除すると...
「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ファイル」
任意の場所にjarを作成し「pen_study_module_system_client」(クライアント側)の
「lib」フォルダのファイルを上書きする。
無名モジュール化したライブラリへの上書きが完了したら「Ctr+B」でプロジェクトを再ビルドすると...
・コンパイルエラーが解消される
お互いが無名モジュール化されたため、
ライブラリ側は「全てのパッケージを公開」し、
クライアント側は「可能な限り、パッケージを読み込む」という動作となっている。
3. さいごに
本記事で紹介した内容は、モジュールシステムのHelloworldレベルの内容です。
他にも様々な機能がありますが、この記事の内容が学習の足掛かりになれば幸いです。