JPMSとは?Javaプラットフォームモジュールシステムの紹介

Java 9までは、Javaの最上位のコード編成要素がパッケージでした。変更されたJava9以降、パッケージの上にモジュールがあります。モジュールは、関連するパッケージをまとめて収集します。

Javaプラットフォームモジュールシステム(JPMS)はコードレベルの構造であるため、JavaをJARファイルにパッケージ化するという事実は変わりません。最終的には、すべてがJARファイルにバンドルされたままになります。モジュールシステムは、module-info.javaファイルを組み込むことにより、JARが使用できる新しい高レベルの記述子を追加します。

大規模なアプリや組織は、モジュールを利用してコードをより適切に整理します。しかし、JDKとそのクラスがモジュール化されたため、誰もがモジュールを消費することになります。

Javaにモジュールが必要な理由

JPMSは、次の目的で実施されたプロジェクトJigsawの成果です。 

  • 開発者が大規模なアプリやライブラリを簡単に整理できるようにする
  • プラットフォームとJDK自体の構造とセキュリティを向上させる
  • アプリのパフォーマンスを向上させる
  • 小型デバイス向けのプラットフォームのより良いハンドル分解

JPMSはSE(Standard Edition)機能であるため、Javaのあらゆる側面にゼロから影響を与えることに注意してください。そうは言っても、この変更は、Java8からJava9に移行するときに、ほとんどのコードが変更なしで機能できるように設計されています。これにはいくつかの例外があり、この概要の後半で説明します。

モジュールの背後にある主なアイデアは、モジュールの外部コンシューマーから要素を隠しながら、モジュールに表示される関連パッケージのコレクションを許可することです。言い換えると、モジュールは別のレベルのカプセル化を可能にします。

クラスパスとモジュールパス

Javaでは、これまで、実行中のアプリケーションで利用できるものの最終的な目的はクラスパスでした。クラスパスはこの目的を果たし、よく理解されていますが、最終的には、すべての依存関係が配置される、大きくて差別化されていないバケットになります。

モジュールパスは、クラスパスの上にレベルを追加します。これはパッケージのコンテナとして機能し、アプリケーションで使用できるパッケージを決定します。

JDKのモジュール

現在、JDK自体はモジュールで構成されています。そこでJPMSの要点を見てみましょう。

システムにJDKがある場合は、ソースもあります。JDKとその入手方法に慣れていない場合は、この記事を参照してください。

JDKインストールディレクトリ内にはディレクトリがあり/libます。そのディレクトリ内にはsrc.zipファイルがあります。それを/srcディレクトリに解凍します。

/srcディレクトリ内を見て、ディレクトリに移動し/java.baseます。そこにmodule-info.javaファイルがあります。それを開きます。

先頭のJavadocコメントの後に、名前付きのセクションと module java.base それに続く一連のexports行があります。かなり難解になるので、ここではフォーマットについては詳しく説明しません。詳細はこちらをご覧ください。

のようなJavaの使い慣れたパッケージの多くがjava.iojava.baseモジュールからエクスポートされていることがわかります。これが、パッケージをまとめるモジュールの本質です。

の裏側 exportsrequires命令です。これにより、定義されているモジュールがモジュールを要求できるようになります。

モジュールに対してJavaコンパイラを実行する場合は、クラスパスと同様の方法でモジュールパスを指定します。これにより、依存関係を解決できます。

モジュラーJavaプロジェクトの作成

モジュール化されたJavaプロジェクトがどのように構成されているかを見てみましょう。

2つのモジュールを持つ小さなプログラムを作成します。1つは依存関係を提供し、もう1つはその依存関係を使用して実行可能なメインクラスをエクスポートします。

ファイルシステムの便利な場所に新しいディレクトリを作成します。それを呼び出します/com.javaworld.mod1。慣例により、Javaモジュールは、モジュールと同じ名前のディレクトリに存在します。

次に、このディレクトリ内にmodule-info.javaファイルを作成します。内部に、リスト1のコンテンツを追加します。

リスト1:com.javaworld.mod1 / module-info.java

module com.javaworld.mod1 { exports com.javaworld.package1; }

モジュールとそれがエクスポートするパッケージは異なる名前であることに注意してください。パッケージをエクスポートするモジュールを定義しています。

次に、このパス上に、module-info.javaファイルを含むディレクトリ内にファイルを作成します/com.javaworld.mod1/com/javaworld/package1。ファイルに名前を付けます Name.java。リスト2の内容をその中に入れてください。

リスト2:Name.java

 package com.javaworld.package1; public class Name { public String getIt() { return "Java World"; } } 

リスト2は、依存するクラス、パッケージ、およびモジュールになります。

次に、並列の別のディレクトリを作成し/com.javaworld.mod1 て呼び出します/com.javaworld.mod2。このディレクトリに、module-info.javaリスト3のように、作成済みのモジュールをインポートするモジュール定義を作成しましょう。

リスト3:com.javaworld.mod2 / module-info.java

 module com.javaworld.mod2 { requires com.javaworld.mod1; } 

リスト3はかなり自明です。com.javaworld.mod2モジュールを定義し、を必要としますcom.javaworld.mod1

/com.javaworld.mod2ディレクトリ内に、次のようなクラスパスを作成します/com.javaworld.mod2/com/javaworld/package2

次にHello.java、リスト4に示すコードを使用して、と呼ばれるファイルを内部に追加します。

リスト4:Hello.java

 package com.javaworld.package2; import com.javaworld.package1.Name; public class Hello { public static void main(String[] args) { Name name = new Name(); System.out.println("Hello " + name.getIt()); } } 

リスト4では、まずパッケージを定義してから、com.javawolrd.package1.Nameクラスをインポートします。これらの要素はいつものように機能することに注意してください。モジュールは、コードレベルではなく、ファイル構造レベルでパッケージを利用できるようにする方法を変更しました。

Similarly, the code itself should be familiar to you. It simply creates a class and calls a method on it to create a classic “hello world” example.

Running the modular Java example

The first step is to create directories to receive the output of the compiler. Create a directory called /target at the root of the project. Inside, create a directory for each module: /target/com.javaworld.mod1 and /target/com.javaworld.mod2.

Step 2 is to compile the dependency module, outputting it to the /target directory. At the root of the project, enter the command in Listing 5. (This assumes the JDK is installed.)

Listing 5: Building Module 1

 javac -d target/com.javaworld.mod1 com.javaworld.mod1/module-info.java com.javaworld.mod1/com/javaworld/package1/Name.java 

This will cause the source to be built along with its module information.

Step 3 is to generate the dependent module. Enter the command shown in Listing 6.

Listing 6: Building Module 2

 javac --module-path target -d target/com.javaworld.mod2 com.javaworld.mod2/module-info.java com.javaworld.mod2/com/javaworld/package2/Hello.java 

Let’s take a look at Listing 6 in detail. It introduces the module-path argument to javac. This allows us to define the module path in similar fashion to the --class-path switch. In this example, we are passing in the target directory, because that is where Listing 5 outputs Module 1.

Next, Listing 6 defines (via the -d switch) the output directory for Module 2. Finally, the actual subjects of compilation are given, as the module-info.java file and class contained in Module 2.

To run, use the command shown in Listing 7.

Listing 7: Executing the module main class

 java --module-path target -m com.javaworld.mod2/com.javaworld.package2.Hello 

The --module-path switch tells Java to use /target directory as the module root, i.e., where to search for the modules. The -m switch is where we tell Java what our main class is. Notice that we preface the fully qualified class name with its module.

You will be greeted with the output Hello Java World.

Backward compatibility 

You may well be wondering how you can run Java programs written in pre-module versions in the post Java 9 world, given that the previous codebase knows nothing of the module path. The answer is that Java 9 is designed to be backwards compatible. However, the new module system is such a big change that you may run into issues, especially in large codebases.

When running a pre-9 codebase against Java 9, you may run into two kinds of errors: those that stem from your codebase, and those that stem from your dependencies.

For errors that stem from your codebase, the following command can be helpful: jdeps. This command when pointed at a class or directory will scan for what dependencies are there, and what modules those dependencies rely on.

For errors that stem from your dependencies, you can hope that the package you are depending on will have an updated Java 9 compatible build. If not you may have to search for alternatives.

One common error is this one:

How to resolve java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

This is Java complaining that a class is not found, because it has migrated to a module without visibility to the consuming code. There are a couple of solutions of varying complexity and permanency, described here.

繰り返しますが、依存関係でこのようなエラーを発見した場合は、プロジェクトに確認してください。彼らはあなたが使用するためのJava9ビルドを持っているかもしれません。

JPMSはかなり抜本的な変更であり、採用するには時間がかかります。幸い、Java 8は長期サポートリリースであるため、緊急の急ぎはありません。

そうは言っても、長期的には、古いプロジェクトは移行する必要があり、新しいプロジェクトはモジュールをインテリジェントに使用する必要があり、約束された利点のいくつかを活用できることを願っています。

このストーリー「JPMSとは?Javaプラットフォームモジュールシステムの紹介」は、もともとJavaWorldによって公開されました。