Java9のModule機能について

Java9のModule機能について

こんにちは。飲食領域で開発統括をしている小林です。

現在、私が担当している領域のプロダクトの中にはサーバーサイド開発において、Java6を使用しているものもあり、Javaのバージョンアップについて、少しずつ議論を進めています。

有力な候補はJava8であるのは間違いないのですが、今年2017年にJava9がリリース予定ということで
今日はJava9で追加される大きな新機能のうちの一つModule機能について、プレリリース版で調べてみたことを書いてみたいと思います。

Module機能とは

従来のJava(8まで)が抱えるclasspathの問題の解決する新機能になります。
Project Jigsawと呼ばれているようです。
言葉で説明する前に、実際の動きを確認した方がわかりやすいと思いますので試していきましょう。

classpathを辿ればライブラリの作者が意図してないAPIまで触れる

  • 外部から使われるライブラリという想定のクラス
    CommonLib.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.lifestyle_techblog.lib;
import com.lifestyle_techblog.lib.util.MyUtil;
public class CommonLib {
  public void execute() {
          System.out.println(this.getClass().getName());
          // ライブラリ向けのクラスを呼び出す。
          MyUtil util = new MyUtil();
          util.run();
  }
}
  • ライブラリ内で使うためのクラス(クライアントサイドから使うことは想定されていない)
    MyUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.lifestyle_techblog.lib.util;
import com.lifestyle_techblog.lib.util.MyUtil;
public class MyUtil {
        public void run() {
                System.out.println("Getting Started:" + this.getClass().getName());
        }
}

lib.jarとしてまとめます。

1
2
$ javac -d classes/ CommonLib.java MyUtil.java
$ jar cvf lib.jar -C classes/ .

使ってみましょう。別のディレクトリにライブラリを呼ぶ出すクライアントコードを作ります。
CommonLibクラスではなく、クライアントから呼ばれることを想定しないMyUtilを呼び出してみます。

MyClient.java

1
2
3
4
5
6
7
8
9
10
11
12
package jp.lifestyle_techblog.client;
public class MyClient {
   public static void main(String argv[]) {
           MyUtil util = new MyUtil();
           util.run();
   }
}

さきほどのlib.jarへのclasspathを通して、コンパイルして実行。

1
2
$ javac -cp lib.jar -d classes/ MyClient.java
$ java -cp classes/:lib.jar jp.lifestyle_techblog.client.MyClient

出力結果

1
Getting started:com.lifestyle_techblog.lib.MyUtil

MyUtilのrunメソッドはpublicなのでclasspathを辿ればどこからでも呼び出せてしまいます。
ライブラリ側でこのクラスを修正した場合、作者の意図しないところで影響が出る可能性が出てきます。

では、MyUtilのメソッドをパッケージ・プライベートにすればどうでしょうか。
結局のところ、クライアント側で同じパッケージ内にクラスを用意すれば、呼び出すことが可能になります。

Java8までのJavaの言語や環境には、パッケージ全体にアクセス制御を適用する方法はありません。
パッケージやclasspathの単純な仕組みは便利でもありますが、一方で、パブリックなAPIを定義したときにクライアントによってAPIの前提を覆されない確実な方法が求められる場合もあり、それが今回のModule機能になります。

Module機能を試す

Java9のプレリリース版は以下から入手可能です。
https://jdk9.java.net/download/

今回はMac版を使います。
プレリリース版は安定リリースではないので、本番環境で使うことはできません。

以下のPathにインストールされるので、環境変数を設定します。
Java バージョンが9-eaである必要があります。

1
2
3
4
5
6
$ export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home
$ java -version
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+157)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+157, mixed mode)

module-info.javaへの宣言

module-info.javaを作成して、moduleを宣言していくことになります。
module-info.javaはソースツリーのトップに置きます。
以下のように公開するパッケージ名を記載します。もちろん複数のパッケージを指定することもできます。

1
2
3
module lib {
  exports com.lifestyle_techblog.lib;
}

コンパイルしてjar化します。

1
2
$ javac -d modules/ CommonLib.java MyUtil.java
$ jar cvf mlib.jar -C modules/ .

クライアント側にもmodule-info.javaを設定する必要があります。
使用するModuleを指定します。

1
2
3
module client {
  requires lib;
}

前述のクライアントコード(MyClient.java)をコンパイルしてみます。

1
2
3
4
5
6
7
$ javac -d modules/ -p lib.jar MyClient.java module-info.java
MyClient.java:3: エラー: package com.lifestyle_techblog.lib.util is not visible
import com.lifestyle_techblog.lib.util.MyUtil;
                                 ^
  (package com.lifestyle_techblog.lib.util is declared in module lib, which does not export it)
エラー1個

com.lifestyle_techblog.lib.util は exportsされていないので、MyUtilのメソッドを使おうとすると(publicであったとしても) コンパイルエラーとなります。

以下のようにMyClient.javaをexportsされたパッケージのクラスのみ(CommonLib.java)の使うように
書きなおせばコンパイルは通ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package jp.lifestyle_techblog.client;
import com.lifestyle_techblog.lib.CommonLib;
public class MyClient {
   public static void main(String argv[]) {
           CommonLib cl = new CommonLib();
           cl.execute();
   }
}

まとめ

Java9が実際に業務で使われるのはだいぶ先かもしれませんが、Javaテクノロジーがどういった課題を持っているかを知るためにも早いうちに検証しておくことは良いことだと思います。
大規模開発では共通機能をjar化して各チームに配布することもあるため、今回のModule機能はメンテナンス性を高めるうえで良いものだと感じました。