Java 9以降とそれ以前のプログラムをサポートするライブラリを作成する
これまでに紹介したとおり、Java 9での大きな変更点はモジュール機能です。この違いにより、Java 9以降のモジュール対応したコードはそれ以前の環境では動きません。こういった大きな分断は、これまでのJavaにおける変更にはありませんでした。
そのため、Java 9以降への最適化か、それ以前で動くことを前提にした対応か、といった2つの選択肢になります。ただし、ライブラリ作成者がこのいずれか一方のみを選ばざるを得ない場合、これまでの環境優位性が大きく落ちてしまいます。
そこで、Java 9からバージョンでの問題を回避するために、複数のバージョンに対応したクラスファイルやリソースファイルを1つのjarファイルに格納することができる「Multi-Release Jar Files」という機能が加わりました。
その考え方は非常に単純であり、単にJava 9以前をサポートするjarファイルの中にJava 9以降向けのクラスファイルやリソースファイルも含めて1つのjarファイルとして配布するといったものです。また、この機能はモジュール機能のためだけにあるわけではなく、モジュール機能を使っていなくても利用できます。
Multi-Release Jar Filesの構造
Multi-Release Jar Filesの構造をもったjarファイルを作るには、大きく分けて2つのルールを守る必要があります。
1つめは、Javaファイルに含めるマニフェストファイル内の記述ルールです。そして、もう1つは、jarファイル内のフォルダ構造です。
マニフェストファイルには、リスト1に示すとおり、Multi-Releaseの指定が必要です。
Manifest-Version: 1.0 Multi-Release: true // この指定を追加する
また、jarファイル内のフォルダ構造は図1のようにする必要があります。
図1内で、versions/9以外にversions/10も記述したのは、この構造はJava 9以上のバージョンに対しても利用可能であることを示すためです。ただし、利用できるバージョン指定はメジャーバージョンのみであるため、9.1や、10.3といった記述はできません。
Multi-Release Jar Filesの作成方法
Multi-Release Jar Filesに沿ったjarファイルを作るには、Java 9以前の既存のコードと、Java 9向けのコードを用意する必要があります。
サンプルでは同じクラスをもつJava 8(mrlib8)とJava 9(mrlib9)向けのコードを、図2に示す構造にて2つ用意しました。
これら2つのプロジェクトでは同じクラス名を使っていますが、実装はそれぞれ異なっています。例えば、Java 8用に自分自身のプロセスIDを取得するコードを実装したものがリスト2です。
import java.lang.management.ManagementFactory; public class Process { public static long getPid(){ String name = ManagementFactory.getRuntimeMXBean().getName(); int idx = name.indexOf('@'); if(idx < 0){ return 0L; } return Long.parseLong(name.substring(0,idx)); } }
Java 9ではプロセスAPIが新たに追加されているため、JMXを使った実装は必要ありません。
そこで、Java 9用に記述し直したコードがリスト3です。
public class Process { public static long getPid(){ ProcessHandle handle = ProcessHandle.current(); return handle.pid(); } }
このように同じクラス、メソッドとして異なる実装を提供することができます。
そして、これらのコードからjarファイルを作成する手順がリスト4です。
## Java 8用のjarファイルを作成 javac --release 8 -d mrlib8/classes $(find mrlib8/src/main/java -name "*.java") (1)Java 8用にコンパイル jar -cfe mrjar.jar com.coltware.Main -C mrlib8/classes . (2)Java 8用のjarファイルの作成 ## Java 9向けに変更 javac --release 9 -d mrlib9/classes $(find mrlib9/src/main/java -name "*.java") (3)Java 9用にコンパイル jar -uf mrjar.jar --release 9 -C mrlib9/classes . (4)Java 9用のクラスファイルをjarファイルに追加
(1)では、Java 8向けにコンパイルをしています。javaコマンドに新たに追加された--releaseオプションを使っていますが、こちらは、今までの-sourceと-targetを同時に指定することとほぼ同様のオプションです。次に(2)では、作成されたclassファイルを使ってjarファイルを作成します。もし、すでにJava 8等で作成されたjarファイルがあればそれを使っても問題ありません。
そして、(3)ではJava 9向けにコンパイルします。このときにはもちろん、JDK 9を使います。また、ここまでは、Multi-Release向けの作業ではありませんので、実際のプロジェクトではそのプロジェクトに応じたコンパイルのオプションやjarファイルの作成はそれらのプロジェクトに応じて変更してください。
そして、(4)のjarコマンドで新たに用意された"--release"オプションに"9"を指定し、Java 9向けのコードをjarファイル内にパッケージングします。
この行程で自動的にMANIFEST.MFファイル内には、Multi-Release指定が追加されますので、Java 9向けにMANIFEST.MFファイルを自分で用意したり編集したりする必要はありません。
こうして作られたjarファイルのファイルエントリがリスト5です。
0 META-INF/ 113 META-INF/MANIFEST.MF 0 com/ 0 com/coltware/ 0 com/coltware/mrlib/ 698 com/coltware/mrlib/Process.class 311 com/coltware/mrlib/Hello.class 826 com/coltware/Main.class 0 META-INF/versions/9/ 0 META-INF/versions/9/com/ 0 META-INF/versions/9/com/coltware/ 0 META-INF/versions/9/com/coltware/mrlib/ 370 META-INF/versions/9/com/coltware/mrlib/Process.class 311 META-INF/versions/9/com/coltware/mrlib/Hello.class 1063 META-INF/versions/9/com/coltware/Main.class 238 META-INF/versions/9/module-info.class
また、今回jarコマンドにて作成しましたが、自分でjavaコマンドやjarコマンドを使って作業をするのではなく、IDEやMavenもしくはGradleなどを使って作業をしているというケースも多いと思います。
それらのツールは今回のMulti-Release Jar Filesの機能に対応していないことがありますが、その場合にはjarファイル内のフォルダ構造とマニフェストファイルのルールに準拠した形でjarファイルを作成すれば問題ありません。
例えば、Gradleであればリスト6のとおり作成することが可能です。
jar { from new File("mrlib8/classes") // Java 8のclassファイルの場所を指定 into('META-INF/versions/9') { from new File("mrlib9/classes") // Java 9のclassコードの場所を指定 } manifest.attributes( 'Multi-Release': 'true' // Multi-Release: trueの指定追加 ) }