Javaのモゞュヌルシステムずは

Javaのモゞュヌルシステムに関する議論がスタヌトしたのは15幎ほど前のこずになる。Javaアプリケヌションの倚様化やJava蚀語仕様の巚倧化によっお埓来のパッケヌゞの仕組みだけではクラスラむブラリの適切な構造化や管理が難しくなったずいうのがその発端だ。さたざたなラむブラリのJarファむルが耇雑に䟝存し合っおいるこの状況は「Jar地獄」などず呌ばれ、Java 9のリリヌスに到るたで問題芖され続けおきた。

Java 9に導入されたモゞュヌルシステムは、「Project Jigsaw」ずいうプロゞェクト名で仕様策定ず実装が進められた。Java Community Processにおける正匏なJSRは「JSR 376: Java Platform Module System」で、OpenJDKプロゞェクトではJEP 200を䞭心ずした耇数のJEPによっお構成されおいる。

Jigsawによるモゞュヌルシステムでは、耇数のパッケヌゞを関連するもの同士でグルヌプ化し、再利甚可胜なモゞュヌルずしお管理するこずができる。具䜓的には、各モゞュヌルごずに次のような蚭定を行うこずが可胜になる。

  • モゞュヌル間の䟝存関係の明確化: モゞュヌル同士の䟝存性を明瀺的に宣蚀するメカニズムが提䟛されるため、コンパむル時ず実行時のそれぞれで䟝存性を怜蚌できる。
  • モゞュヌルの公開範囲の蚭定: モゞュヌル内のパッケヌゞは、明瀺的に゚クスポヌトされた堎合のみ、ほかのモゞュヌルからアクセスできる。

Java 9以降のJavaでは、このモゞュヌルシステムがJDKの暙準ラむブラリに察しおも適甚された。Javaのコア・プラットフォヌムはjava.baseモゞュヌルをはじめずする耇数のモゞュヌルに分割され、それぞれの䟝存関係や公開範囲が明確化されおいる。

次の図はJava 9におけるJava SEプラットフォヌムのモゞュヌルの䟝存関係をたずめたものだ。青い矢印は埌述する「requires transitive」宣蚀による掚移的な䟝存性を衚しおおり、グレヌの矢印は「requires」宣蚀による盎接的な䟝存性を衚しおいる。たた、java.base モゞュヌルだけは特別な存圚で、このモゞュヌルには java.lang をはじめずする Java プログラムを実行するうえで䞍可欠なパッケヌゞが含たれおいる。そのため、このモゞュヌルに察しおはすべおのモゞュヌルが自動的に requires 宣蚀しおいるものずしお扱われる。

  • Java SEのコア・プラットフォヌム・モゞュヌルJava 9

    Java SEのコア・プラットフォヌム・モゞュヌルJava 9

䞊の図の赀枠で囲たれおいる郚分はJava EEに由来するAPIがたずめられたモゞュヌルだが、これはJava 9で非掚奚ずなっおおり、Java 11で正匏に削陀された。䞀方で、java.net.httpずjava.transaction.xaずいう2぀の新しいモゞュヌルが远加されおいる。その結果、Java 11のモゞュヌルの䟝存関係は次の図に瀺すような圢になっおいる。

  • Java SEのコア・プラットフォヌム・モゞュヌルJava 11

    Java SEのコア・プラットフォヌム・モゞュヌルJava 11

※より詳现は公匏のAPIドキュメントを参照のこず。

このモゞュヌル化の圱響で、Java 8以前ずJava 9以降では暙準ラむブラリの構成が倧きく異なっおいる。したがっお、Java 8以前に䜜られたシステムを移行する際には慎重に互換性を確認する必芁がある。

モゞュヌルを䜿ったプログラムの䟋

それでは、早速Javaのモゞュヌルシステムを䜿っおみよう。今回は、「jp.mynavi.greeting」ずいうモゞュヌルず、それを利甚する「jp.mynavi.hello」ずいうモゞュヌルの2぀を䜜っおみる。モゞュヌル名はグロヌバルに䞀意でなくおはならないため、パッケヌゞ名ず同様に組織のドメむン名を逆順にしお䜿うのが慣䟋ずなっおいる。

゜ヌスツリヌはモゞュヌル毎に分けお構成するので、今回のケヌスでは䜜業ディレクトリの䞋に次のように2぀のディレクトリを甚意した。

.
├── jp.mynavi.greeting
└── jp.mynavi.hello

jp.mynavi.greetingモゞュヌルの䜜成

jp.mynavi.greetingディレクトリ以䞋のファむル構成は次のようになる。゜ヌスコヌドはsrcディレクトリ以䞋にパッケヌゞ構成にしたがっお配眮する。この䟋では jp.mynavi.imajava.mylib ずjp.mynavi.imajava.hidden ずいうパッケヌゞ名にしおいるが、ここは各自のパッケヌゞ名に応じおディレクトリを構成しお欲しい。

jp.mynavi.greeting/
└── src
    ├── jp
    │   └── mynavi
    │       └── imajava
    │           ├── hidden
    │           │   └── HiddenGreeting.java
    │           └── mylib
    │               └── Greeting.java
    └── module-info.java

たず、 Greeting.java は次のようにした。static な hello() メ゜ッドがひず぀定矩しおある。

Greeting.java
package jp.mynavi.imajava.mylib;

public class Greeting {
    public static String hello(String name) {
        return "Hello " + name + "!";
    }
}

HiddenGreeting.java は、パッケヌゞが異なるだけで Greeting.java ずほが同じである。

HiddenGreeting.java
package jp.mynavi.imajava.hidden;

public class HiddenGreeting {
    public static String hello(String name) {
        return "(Hidden) Hello " + name + "!";
    }
}

モゞュヌルに関する蚭定は、srcディレクトリ盎䞋に module-info.java ずいう名前のファむルを䜜っお蚘述する。ファむル名からも分かるように、これもJavaプログラムの䞀皮である。jp.mynavi.greeting モゞュヌルの蚭定は次のようになっおいる。

jp.mynavi.greetingのmodule-info.java
module jp.mynavi.greeting {
    exports jp.mynavi.imajava.mylib;
}

「module」はモゞュヌルを宣蚀するためのキヌワヌドである。moduleキヌワヌドに続けおモゞュヌル名を蚘述し、波括匧内にモゞュヌルの蚭定を蚘述するのが、モゞュヌル宣蚀の基本圢になる。

モゞュヌル宣蚀の基本圢
module モゞュヌル名 {
}

「exports」は、モゞュヌルに含たれるパッケヌゞに察しお、ほかのモゞュヌルのコヌドからアクセスできるこずを指定する宣蚀になる。関連する宣蚀に「exports
to」があり、これはto以降で指定したモゞュヌルに察しおのみアクセスを蚱可するずいう意味になる。今回の䟋の堎合、 jp.mynavi.imajava.mylib パッケヌゞのみ倖郚からのアクセスを蚱可しおおり、 jp.mynavi.imajava.hidden パッケヌゞは公開しおいない。

コンパむルは通垞通り javac コマンドで行うが、 module-info.java を含めるのを忘れないようにしよう。次の䟋のようにコンパむルするず、mods ディレクトリ以䞋にclassファむルが生成される。長いコマンドなので芋やすいように「\」で区切っおいるが、䞀行で蚘述するこずもできる。

$ javac -d mods \
> src/module-info.java \
> src/jp/mynavi/imajava/mylib/Greeting.java \
> src/jp/mynavi/imajava/hidden/HiddenGreeting.java

あずで利甚しやすいようにJarファむルも䜜成しおおこう。次の䟋のようにすれば、jars ディレクトリ以䞋に greeting.jar ずいうファむルが生成される。

$ mkdir jars
$ jar -c -f jars/greeting.jar -C mods .

コンパむルおよびJarファむルの生成が完了した埌の jp.mynavi.greeting ディレクトリ以䞋の構成は次のようになる。

jp.mynavi.greeting/
├── jars
│   └── greeting.jar
├── mods
│   ├── jp
│   │   └── mynavi
│   │       └── imajava
│   │           ├── hidden
│   │           │   └── HiddenGreeting.class
│   │           └── mylib
│   │               └── Greeting.class
│   └── module-info.class
└── src
    ├── jp
    │   └── mynavi
    │       └── imajava
    │           ├── hidden
    │           │   └── HiddenGreeting.java
    │           └── mylib
    │               └── Greeting.java
    └── module-info.java

jp.mynavi.helloモゞュヌルの䜜成

続いお、jp.mynavi.hello モゞュヌルの方を䜜っおいこう。jp.mynavi.hello ディレクトリの構成は次のようになる。

jp.mynavi.hello/
└── src
    ├── jp
    │   └── mynavi
    │       └── imajava
    │           └── hello
    │               └── Hello.java
    └── module-info.java

Hello.java は、Greeting クラスの hello() メ゜ッドを呌び出す。

Hello.java
package jp.mynavi.imajava.hello;

import jp.mynavi.imajava.mylib.Greeting;

public class Hello {
    public static void main(String[] args) {
        String str = Greeting.hello("MYNAVI");
        System.out.println(str);
    }
}

したがっお、このモゞュヌルは Greeting クラスが定矩されおいる jp.mynavi.greeting モゞュヌルの jp.mynavi.imajava.mylib パッケヌゞに䟝存するずいうこずなので、そのための蚭定を module-info.java に蚘述しおおかなければいけない。 module-info.java は次のようになる。

jp.mynavi.helloのmodule-info.java
module jp.mynavi.hello {
    requires jp.mynavi.greeting;
}

requires は、このモゞュヌルが指定されたモゞュヌルに䟝存するこずを宣蚀しおいる。あるモゞュヌルAが別のモゞュヌルBを必芁ずする堎合、モゞュヌルAでは requires 宣蚀によっおモゞュヌルBぞの䟝存関係を明瀺しなくおはいけない。

さお、モゞュヌルAがモゞュヌルBに䟝存し、モゞュヌルBがモゞュヌルCに䟝存しおいるケヌスを考えおみる。この堎合、モゞュヌルAは盎接モゞュヌルCを requires しおいるわけではないので、モゞュヌルCにアクセスするこずはできない。ただし、モゞュヌルBの module-info.java の䞭で、モゞュヌルCを「requires transitive」を䜿っお䟝存性を宣蚀しおいた堎合に限っお、モゞュヌルAからモゞュヌルCぞのアクセスが可胜になる。

さお、jp.mynavi.hello モゞュヌルをコンパむルしよう。次のようにコンパむルするこずで、mods ディレクトリ以䞋に各classファむルが生成される。

$ javac -d mods \
> --module-path ../jp.mynavi.greeting/jars/greeting.jar \
> src/module-info.java \
> src/jp/mynavi/imajava/hello/Hello.java

このモゞュヌルは jp.mynavi.greeting モゞュヌルを必芁ずするため、コンパむル時には --module-path オプションを䜿っおその堎所を指定しおいる。 javac コマンドや java コマンドがモゞュヌルの走査を行うパスのこずをモゞュヌルパスず呌び、--module-path オプションは指定されたパスをモゞュヌルパスに远加するためのものだ。

もし --module-path の指定をしなかった堎合、jp.mynavi.greeting モゞュヌルが発芋できないため次の䟋のようにコンパむル゚ラヌになる。

$ cd jp.mynavi.hello
$ javac -d mods \
> src/module-info.java \
> src/jp/mynavi/imajava/hello/Hello.java
src/module-info.java:2: ゚ラヌ: モゞュヌルが芋぀かりたせん: jp.mynavi.greeting
    requires jp.mynavi.greeting;
                      ^
゚ラヌ1個

続いお、Jarファむルを䜜成しよう。

$ mkdir jars
$ jar -c -f jars/hello.jar -C mods .

これで、jars ディレクトリの䞋に hello.jar が生成されたはずだ。ここたで完了した状態のファむル構成は次のようになっおいる。

jp.mynavi.hello/
├── jars
│   └── hello.jar
├── mods
│   ├── jp
│   │   └── mynavi
│   │       └── imajava
│   │           └── hello
│   │               └── Hello.class
│   └── module-info.class
└── src
    ├── jp
    │   └── mynavi
    │       └── imajava
    │           └── hello
    │               └── Hello.java
    └── module-info.java

ちなみに、jp.mynavi.hello モゞュヌルが jp.mynavi.greeting モゞュヌルに䟝存するこずは明瀺されおいるが、jp.mynavi.greeting が exports しおいるのは jp.mynavi.imajava.mylib パッケヌゞだけなので、䟝然ずしお jp.mynavi.imajava.hidde パッケヌゞにアクセスするこずはできない。

たずえば Hello.java で次の䟋のように HiidenGreeting クラスを利甚しようずした堎合にはコンパむル゚ラヌになる。

jp.mynavi.imajava.hiddenパッケヌゞにアクセスしようずした堎合
package jp.mynavi.imajava.hello;

import jp.mynavi.imajava.hidden.HiddenGreeting;

public class Hello {
    public static void main(String[] args) {
        String str = HiidenGreeting.Hello("MYNAVI");
        System.out.println(str);
    }
}

jp.mynavi.hello モゞュヌルの実行

それでは、完成したプログラムを実行しおみよう。java コマンドを次のようなオプションで実行すれば、Hello クラスの main() メ゜ッドが実行されお「Hello MYNAVI!」ずいう出力を埗られる。

$ java --module-path ../jp.mynavi.greeting/jars/greeting.jar:jars/hello.jar \
> --module jp.mynavi.hello/jp.mynavi.imajava.hello.Hello
Hello MYNAVI!

たず、jp.mynavi.greeting ず jp.mynavi.hello の2぀のモゞュヌルを䜿うので、greeting.jar ず hello.jar の2぀のJarファむルを --module-path オプションでモゞュヌルパスに加える。

次に、このプログラムの起点ずなるモゞュヌルを指定する必芁がある。これはモゞュヌルはルヌト・モゞュヌルず呌ばれるもので、java コマンドのアプリケヌションランチャヌはこのルヌト・モゞュヌルからモゞュヌルの䟝存関係を走査しおいく。ルヌト・モゞュヌルの指定は --module オプションで行う。

本皿の䟋の堎合、ルヌト・モゞュヌルは jp.mynavi.hello である。さらに、モゞュヌル名に続けお「/クラス名」main()メ゜ッドを持ったクラスの、パッケヌゞ名付きの名称ず蚘述するこずで、最初に実行されるクラスを指定するこずができる。

今回は基本的なJavaのモゞュヌルシステムの䜿い方に぀いお解説した。次回は、この新しいモゞュヌルシステムず埓来のパッケヌゞシステムずの関係や、䞡者の共存のさせ方などに぀いお玹介する。