Java 9 java.lang.Module API

Java 9 java.lang.Module API

1. 前書き

A Guide to Java 9 Modularityに続いて、この記事では、Javaプラットフォームモジュールシステムと一緒に導入されたjava.lang.ModuleAPIについて説明します。

このAPIは、プログラムでモジュールにアクセスし、モジュールから特定の情報を取得し、通常はモジュールとそのModuleDescriptorを操作する方法を提供します。

2. モジュール情報の読み取り

Moduleクラスは、名前付きモジュールと名前なしモジュールの両方を表します。 モジュールのグラフを定義として使用するNamed modules have a name and are constructed by the Java Virtual Machine when it creates a module layer,

名前のないモジュールには名前がなく、ClassLoader.All types that aren’t in a named module are members of the unnamed module related to their class loader.ごとに1つあります。

Moduleクラスの興味深い部分は、モジュール名、モジュールクラスローダー、モジュール内のパッケージなど、モジュールから情報を取得できるようにするメソッドを公開することです。

モジュールに名前が付いているか名前が付いていないかを確認する方法を見てみましょう。

2.1. 名前付きまたは名前なし

isNamed()メソッドを使用して、モジュールに名前が付けられているかどうかを識別できます。

HashMapなどの特定のクラスが名前付きモジュールの一部であるかどうかを確認する方法と、その名前を取得する方法を見てみましょう。

Class hashMapClass = HashMap.class;
Module javaBaseModule = hashMapClass.getModule();

assertThat(javaBaseModule.isNamed(), is(true));
assertThat(javaBaseModule.getName(), is("java.base"));

Personクラスを定義しましょう:

public class Person {
    private String name;

    // constructor, getters and setters
}

同様に、HashMapクラスの場合と同様に、Personクラスが名前付きモジュールの一部であるかどうかを確認できます。

Class personClass = Person.class;
Module module = personClass.getModule();

assertThat(module.isNamed(), is(false));
assertThat(module.getName(), is(nullValue()));

2.2. パッケージ

モジュールを使用する場合、モジュール内で使用可能なパッケージを知ることが重要になる場合があります。

特定のパッケージ(たとえば、java.lang.annotation)が特定のモジュールに含まれているかどうかを確認する方法を見てみましょう。

assertTrue(javaBaseModule.getPackages().contains("java.lang.annotation"));
assertFalse(javaBaseModule.getPackages().contains("java.sql"));

2.3. アノテーション

同様に、パッケージの場合はit’s possible to retrieve the annotations that are present in the module using the getAnnotations() methodです。

名前付きモジュールに注釈が存在しない場合、メソッドは空の配列を返します。

java.baseモジュールに存在するアノテーションの数を見てみましょう。

assertThat(javaBaseModule.getAnnotations().length, is(0));

名前のないモジュールで呼び出されると、getAnnotations()メソッドは空の配列を返します。

2.4. ClassLoader

Moduleクラス内で使用可能なgetClassLoader()メソッドのおかげで、特定のモジュールのClassLoaderを取得できます。

assertThat(
  module.getClassLoader().getClass().getName(),
  is("jdk.internal.loader.ClassLoaders$AppClassLoader")
);

2.5. 層

モジュールから抽出できるもう1つの貴重な情報は、ModuleLayerです。これは、Java仮想マシンのモジュールのレイヤーを表します。

モジュール層は、モジュールからロードされる可能性のあるクラスについてJVMに通知します。 このようにして、JVMは各クラスがどのモジュールのメンバーであるかを正確に認識します。

ModuleLayerには、その構成、親レイヤー、およびレイヤー内で使用可能なモジュールのセットに関連する情報が含まれています。

特定のモジュールのModuleLayerを取得する方法を見てみましょう。

ModuleLayer javaBaseModuleLayer = javaBaseModule.getLayer();

ModuleLayerを取得すると、その情報にアクセスできます。

assertTrue(javaBaseModuleLayer.configuration().findModule("jaa.base").isPresent());
assertThat(javaBaseModuleLayer.configuration().modules().size(), is(78));

特殊なケースは、Java仮想マシンの起動時に作成されるブートレイヤーです。 ブートレイヤーは、java.baseモジュールを含む唯一のレイヤーです。

3. ModuleDescriptorの処理

ModuleDescriptorは、名前付きモジュールを記述し、その各コンポーネントを取得するためのメソッドを定義します。

ModuleDescriptorオブジェクトは不変であり、複数の同時スレッドで安全に使用できます。

まず、ModuleDescriptor.を取得する方法を見てみましょう。

3.1. ModuleDescriptorの取得

ModuleDescriptorModuleと密接に関連しているため、Module:から直接取得することができます。

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

3.2. ModuleDescriptorの作成

It’s also possible to create a module descriptor using the ModuleDescriptor.Builder class、またはモジュール宣言のバイナリ形式を読み取ることにより、module-info.class

ModuleDescriptor.BuilderAPIを使用してモジュール記述子を作成する方法を見てみましょう。

ModuleDescriptor.Builder moduleBuilder = ModuleDescriptor
  .newModule("example.base");

ModuleDescriptor moduleDescriptor = moduleBuilder.build();

assertThat(moduleDescriptor.name(), is("example.base"));

これにより、通常のモジュールを作成しましたが、オープンモジュールまたは自動モジュールを作成する場合は、それぞれnewOpenModule()またはnewAutomaticModule()メソッドを使用できます。

3.3. モジュールの分類

モジュール記述子は、通常のモジュール、オープンモジュール、または自動モジュールを記述します。

ModuleDescriptor内で使用可能なメソッドのおかげで、モジュールのタイプを識別することができます。

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

assertFalse(moduleDescriptor.isAutomatic());
assertFalse(moduleDescriptor.isOpen());

3.4. 取得が必要

モジュール記述子を使用すると、モジュールの依存関係を表すRequiresのセットを取得できます。

これは、requires()メソッドを使用して可能です。

Set javaBaseRequires = javaBaseModule.getDescriptor().requires();
Set javaSqlRequires = javaSqlModule.getDescriptor().requires();

Set javaSqlRequiresNames = javaSqlRequires.stream()
  .map(Requires::name)
  .collect(Collectors.toSet());

assertThat(javaBaseRequires, empty());
assertThat(javaSqlRequires.size(), is(3));
assertThat(
  javaSqlRequiresNames,
  containsInAnyOrder("java.base", "java.xml", "java.logging")
);

All modules, except javabase, have the javabase module as a dependency

ただし、モジュールが自動モジュールの場合、java.baseを除いて、依存関係のセットは空になります。

3.5. 提供物の取得

provides()メソッドを使用すると、モジュールが提供するサービスのリストを取得できます。

Set javaBaseProvides = javaBaseModule.getDescriptor().provides();
Set javaSqlProvides = javaSqlModule.getDescriptor().provides();

Set javaBaseProvidesService = javaBaseProvides.stream()
  .map(Provides::service)
  .collect(Collectors.toSet());

assertThat(
  javaBaseProvidesService,
  contains("java.nio.file.spi.FileSystemProvider")
);
assertThat(javaSqlProvides, empty());

3.6. エクスポートの取得

exports()メソッドを使用して、モジュールがパッケージをエクスポートするかどうか、特に次のいずれかを確認できます。

Set javaBaseExports = javaBaseModule.getDescriptor().exports();
Set javaSqlExports = javaSqlModule.getDescriptor().exports();

Set javaSqlExportsSource = javaSqlExports.stream()
  .map(Exports::source)
  .collect(Collectors.toSet());

assertThat(javaBaseExports.size(), is(108));
assertThat(javaSqlExports.size(), is(3));
assertThat(
  javaSqlExportsSource,
  containsInAnyOrder("java.sql","javax.transaction.xa", "javax.sql")
);

特殊なケースとして、モジュールが自動モジュールである場合、エクスポートされたパッケージのセットは空になります。

3.7. 使用の取得

uses()メソッドを使用すると、モジュールのサービス依存関係のセットを取得できます。

Set javaBaseUses = javaBaseModule.getDescriptor().uses();
Set javaSqlUses = javaSqlModule.getDescriptor().uses();

assertThat(javaBaseUses.size(), is(34));
assertThat(javaSqlUses, contains("java.sql.Driver"));

モジュールが自動モジュールの場合、依存関係のセットは空になります。

3.8. オープンの取得

モジュールの開いているパッケージのリストを取得するときはいつでも、opens()メソッドを使用できます。

Set javaBaseUses = javaBaseModule.getDescriptor().opens();
Set javaSqlUses = javaSqlModule.getDescriptor().opens();

assertThat(javaBaseUses, empty());
assertThat(javaSqlUses, empty());

モジュールがオープンまたは自動の場合、セットは空になります。

4. モジュールの取り扱い

モジュールから情報を読み取る以外に、Module APIを使用して、モジュール定義を更新できます。

4.1. エクスポートの追加

特定のモジュールから特定のパッケージをエクスポートして、モジュールを更新する方法を見てみましょう。

Module updatedModule = module.addExports(
  "com.example.java9.modules", javaSqlModule);

assertTrue(updatedModule.isExported("com.example.java9.modules"));

これは、呼び出し元のモジュールがコードがメンバーであるモジュールである場合にのみ実行できます。

サイドノートとして、パッケージがモジュールによって既にエクスポートされている場合、またはモジュールが開いているモジュールである場合、影響はありません。

4.2. 読み取りの追加

特定のモジュールを読み取るようにモジュールを更新する場合は、addReads()メソッドを使用できます。

Module updatedModule = module.addReads(javaSqlModule);

assertTrue(updatedModule.canRead(javaSqlModule));

すべてのモジュールが自身を読み取るため、モジュール自体を追加してもこのメソッドは何もしません。

同様に、このメソッドは、モジュールが名前のないモジュールであるか、このモジュールがすでに他のモジュールを読み込んでいる場合は何もしません。

4.3. オープンの追加

パッケージを開いたモジュールを少なくとも呼び出し元のモジュールに更新する場合は、addOpens()を使用してパッケージを別のモジュールに開くことができます。

Module updatedModule = module.addOpens(
  "com.example.java9.modules", javaSqlModule);

assertTrue(updatedModule.isOpen("com.example.java9.modules", javaSqlModule));

パッケージが指定されたモジュールに対して既に開いている場合、このメソッドは効果がありません。

4.4. 用途の追加

サービスの依存関係を追加してモジュールを更新する場合は常に、メソッドaddUses()を選択します。

Module updatedModule = module.addUses(Driver.class);

assertTrue(updatedModule.canUse(Driver.class));

このメソッドは、名前のないモジュールまたは自動モジュールで呼び出されても何もしません。

5. 結論

この記事では、java.lang.Module APIの使用方法について説明し、モジュールの情報を取得する方法、ModuleDescriptorを使用してモジュールに関する追加情報にアクセスする方法、およびモジュールを操作する方法を学習しました。

いつものように、この記事のすべてのコード例はover on GitHubにあります。