Java 9モジュール性の手引き

Java 9モジュール化のガイド

1. 概要

Java 9では、パッケージの上に新しいレベルの抽象化が導入され、正式にはJavaプラットフォームモジュールシステム(JPMS)、または略して「モジュール」と呼ばれています。

このチュートリアルでは、新しいシステムについて説明し、そのさまざまな側面について説明します。

また、このガイドで学習するすべての概念を示す簡単なプロジェクトを作成します。

2. モジュールとは何ですか?

まず、モジュールの使用方法を理解する前に、モジュールが何であるかを理解する必要があります。

モジュールは、新しいモジュール記述子ファイルとともに、密接に関連するパッケージとリソースのグループです。

言い換えれば、これは「Javaパッケージのパッケージ」の抽象化であり、コードをさらに再利用可能にすることができます。

2.1. パッケージ

モジュール内のパッケージは、Javaの開始以来使用してきたJavaパッケージと同じです。

モジュールを作成すると、we organize the code internally in packages just like we previously did with any other project.

コードを整理するだけでなく、パッケージを使用して、モジュールの外部で公開されているコードを判別します。 これについては、この記事の後半で詳しく説明します。

2.2. リソース

各モジュールは、メディアや構成ファイルなどのリソースを担当します。

以前は、すべてのリソースをプロジェクトのルートレベルに配置し、アプリケーションのさまざまな部分に属するリソースを手動で管理していました。

モジュールを使用すると、必要な画像とXMLファイルを必要なモジュールとともに出荷できるため、プロジェクトの管理がはるかに容易になります。

2.3. モジュール記述子

モジュールを作成するとき、新しいモジュールのいくつかの側面を定義する記述子ファイルを含めます。

  • Name –モジュールの名前

  • Dependencies –このモジュールが依存する他のモジュールのリスト

  • Public Packages –モジュールの外部からアクセスできるようにするすべてのパッケージのリスト

  • Services Offered –他のモジュールで使用できるサービス実装を提供できます

  • Services Consumed –現在のモジュールをサービスのコンシューマーにすることができます

  • Reflection Permissions –他のクラスがリフレクションを使用してパッケージのプライベートメンバーにアクセスすることを明示的に許可します

モジュールの命名規則は、パッケージの命名方法に似ています(ドットは使用できますが、ダッシュは使用できません)。 プロジェクトスタイル(my.module)または逆引きDNS(com.example.mymodule)スタイル名を使用することは非常に一般的です。 このガイドでは、プロジェクトスタイルを使用します。

デフォルトではすべてのパッケージがモジュールプライベートであるため、公開したいすべてのパッケージをリストする必要があります。

反射についても同じことが言えます。 デフォルトでは、別のモジュールからインポートしたクラスでリフレクションを使用できません。

この記事の後半では、モジュール記述子ファイルの使用方法の例を見ていきます。

2.4. モジュールタイプ

新しいモジュールシステムには、4つのタイプのモジュールがあります。

  • System Modules -これらは、上記のlist-modulesコマンドを実行したときにリストされるモジュールです。 Java SEおよびJDKモジュールが含まれます。

  • Application Modules –これらのモジュールは、モジュールを使用することを決定したときに通常構築したいものです。 これらは、アセンブルされたJARに含まれているコンパイル済みのmodule-info.classファイルで名前が付けられて定義されています。

  • Automatic Modules –既存のJARファイルをモジュールパスに追加することで、非公式モジュールを含めることができます。 モジュールの名前は、JARの名前から派生します。 自動モジュールは、パスによってロードされる他のすべてのモジュールへの完全な読み取りアクセス権を持ちます。

  • Unnamed Module –クラスまたはJARがクラスパスにロードされるが、モジュールパスにはロードされない場合、名前のないモジュールに自動的に追加されます。 これは、以前に作成されたJavaコードとの下位互換性を維持するための包括的なモジュールです。

2.5. 分布

モジュールは、JARファイルとして、または「分解された」コンパイル済みプロジェクトとして、2つの方法のいずれかで配布できます。 もちろん、これは他のJavaプロジェクトと同じであるため、当然のことです。

「メインアプリケーション」と複数のライブラリモジュールで構成されるマルチモジュールプロジェクトを作成できます。

ただし、JARファイルごとに1つのモジュールしか持てないため、注意が必要です。

ビルドファイルを設定するときは、プロジェクトの各モジュールを個別のjarとしてバンドルする必要があります。

3. デフォルトのモジュール

Java 9をインストールすると、JDKの構造が新しくなったことがわかります。

元のパッケージをすべて取り、新しいモジュールシステムに移動しました。

コマンドラインに入力することで、これらのモジュールが何であるかを確認できます。

java --list-modules

これらのモジュールは、java, javafx, jdk, Oracleの4つの主要なグループに分けられます。

javaモジュールは、コアSE言語仕様の実装クラスです。

javafxモジュールはFXUIライブラリです。

JDK自体に必要なものはすべて、jdkモジュールに保持されます。

そして最後に、anything that is Oracle-specific is in the oracle modules.

4. モジュール宣言

モジュールを設定するには、パッケージのルートにmodule-info.javaという名前の特別なファイルを配置する必要があります。

このファイルはモジュール記述子と呼ばれ、新しいモジュールの構築と使用に必要なすべてのデータが含まれています。

本体が空であるか、モジュールディレクティブで構成されている宣言を使用してモジュールを構築します。

module myModuleName {
    // all directives are optional
}

モジュール宣言はmoduleキーワードで開始し、その後にモジュール名を付けます。

モジュールはこの宣言で機能しますが、通常はより多くの情報が必要になります。

それがモジュールディレクティブの出番です。

4.1. が必要です

最初のディレクティブはrequiresです。 このモジュールディレクティブを使用すると、モジュールの依存関係を宣言できます。

module my.module {
    requires module.name;
}

これで、my.modulemodule.nameboth a runtime and a compile-time dependencyが追加されます。

そして、このディレクティブを使用すると、モジュールは依存関係からエクスポートされたすべてのパブリックタイプにアクセスできます。

4.2. 静的が必要

別のモジュールを参照するコードを作成することもありますが、ライブラリのユーザーは決して使用したくないでしょう。

たとえば、別のロギングモジュールが存在するときに内部状態をきれいに出力するユーティリティ関数を作成できます。 しかし、私たちのライブラリのすべての消費者がこの機能を望んでいるわけではなく、追加のロギングライブラリを含めたくないのです。

これらの場合、オプションの依存関係を使用します。 requires staticディレクティブを使用して、コンパイル時のみの依存関係を作成します。

module my.module {
    requires static module.name;
}

4.3. 推移的が必要

私たちは通常、図書館と協力して生活を楽にします。

ただし、コードを取り込むモジュールがこれらの余分な「推移的な」依存関係も取り込むことを確認する必要があります。そうしないと、それらは機能しません。

幸い、requires transitiveディレクティブを使用して、ダウンストリームのコンシューマーにも必要な依存関係を読み取るように強制できます。

module my.module {
    requires transitive module.name;
}

これで、開発者がrequires my.moduleの場合、モジュールが引き続き機能するためにrequires module.nameも言う必要はありません。

4.4. 輸出

By default, a module doesn’t expose any of its API to other modules.このstrong encapsulationは、そもそもモジュールシステムを作成するための主要な動機の1つでした。

コードの安全性は大幅に向上しましたが、使用できるようにするには、APIを世界に公開する必要があります。

exportsディレクティブを使用して、名前付きパッケージのすべてのパブリックメンバーを公開します。

module my.module {
    exports com.my.package.name;
}

これで、誰かがrequires my.moduleを実行すると、com.my.package.nameパッケージのパブリックタイプにアクセスできますが、他のパッケージにはアクセスできません。

4.5. エクスポート…へ

exports…toを使用して、公開クラスを世界に公開できます。

しかし、全世界にAPIにアクセスさせたくない場合はどうでしょうか。

exports…toディレクティブを使用して、APIにアクセスできるモジュールを制限できます。

exportsディレクティブと同様に、パッケージをエクスポート済みとして宣言します。 ただし、このパッケージのインポートを許可しているモジュールをrequiresとしてリストします。 これがどのように見えるか見てみましょう:

module my.module {
    export com.my.package.name to com.specific.package;
}

4.6. Uses

serviceは、他のクラスによってconsumedになることができる特定のインターフェースまたは抽象クラスの実装です。

モジュールが消費するサービスをusesディレクティブで指定します。

the class name we use is either the interface or abstract class of the service, not the implementation class

module my.module {
    uses class.name;
}

ここで、requiresディレクティブとusesディレクティブには違いがあることに注意してください。

消費したいサービスを提供するモジュールをrequireするかもしれませんが、そのサービスは推移的な依存関係の1つからインターフェースを実装します。

万が一の場合に備えて、モジュールにallの推移的な依存関係を強制する代わりに、usesディレクティブを使用して、必要なインターフェースをモジュールパスに追加します。

4.7. 提供する…

モジュールは、他のモジュールが消費できるservice providerにすることもできます。

ディレクティブの最初の部分は、providesキーワードです。 ここに、インターフェイスまたは抽象クラス名を配置します。

次に、withディレクティブがあります。ここでは、implementsがインターフェースであるか、extendsが抽象クラスである実装クラス名を指定します。

まとめると次のようになります。

module my.module {
    provides MyInterface with MyInterfaceImpl;
}

4.8. Open

カプセル化は、このモジュールシステムの設計の原動力であると先に述べました。

Java 9より前は、リフレクションを使用して、パッケージ内のすべてのタイプとメンバー(privateのものも含む)を調べることができました。 真にカプセル化されたものはなく、ライブラリの開発者にとってあらゆる種類の問題を引き起こす可能性があります。

Java 9はstrong encapsulationを強制するため、we now have to explicitly grant permission for other modules to reflect on our classes.

古いバージョンのJavaのように完全なリフレクションを引き続き許可したい場合は、モジュール全体をopenするだけです。

open module my.module {
}

4.9. 開く

プライベートタイプの反映を許可する必要があるが、すべてのコードを公開したくない場合は、we can use the opens directive to expose specific packages.

ただし、これによりパッケージが全世界に開かれることを忘れないでください。

module my.module {
  opens com.my.package;
}

4.10. 開く…へ

さて、リフレクションは時々素晴らしいですが、それでもencapsulationから得られる限りのセキュリティが必要です。 We can selectively open our packages to a pre-approved list of modules, in this case, using the opens…to directive

module my.module {
    opens com.my.package to moduleOne, moduleTwo, etc.;
}

5. コマンドラインオプション

これまでに、Java 9モジュールのサポートがMavenとGradleに追加されたため、プロジェクトを手動でビルドする必要はほとんどありません。 ただし、コマンドラインからモジュールシステムを使用するには、howを知っておくことが重要です。

以下の完全な例のコマンドラインを使用して、システム全体が頭の中でどのように機能するかを固めるのに役立てます。

  • module-path– –module-pathオプションを使用して、モジュールパスを指定します。 これは、モジュールを含む1つ以上のディレクトリのリストです。

  • add-reads –モジュール宣言ファイルに依存する代わりに、requiresディレクティブと同等のコマンドラインを使用できます。 –add-reads

  • add-exports– exportsディレクティブのコマンドライン置換。

  • add-opens– モジュール宣言ファイルのopen句を置き換えます。

  • add-modules– モジュールのリストをデフォルトのモジュールセットに追加します

  • list-modules– すべてのモジュールとそのバージョン文字列のリストを出力します

  • patch-module –モジュール内のクラスを追加またはオーバーライドします

  • illegal-access=permit|warn|deny –単一のグローバル警告を表示して強力なカプセル化を緩和するか、すべての警告を表示するか、エラーで失敗します。 デフォルトはpermitです。

6. 視認性

コードの可視性について少し話す必要があります。

A lot of libraries depend on reflection to work their magic(JUnitとSpringが思い浮かびます)。

Java 9のデフォルトでは、onlyは、エクスポートされたパッケージのパブリッククラス、メソッド、およびフィールドにアクセスできます。 リフレクションを使用して非公開メンバーにアクセスし、setAccessible(true), を呼び出しても、これらのメンバーにアクセスすることはできません。

openopens、およびopens…toオプションを使用して、リフレクションのためのランタイムのみのアクセスを許可できます。 注、this is runtime-only!

プライベートタイプに対してコンパイルすることはできません。とにかくコンパイルする必要はありません。

リフレクションのためにモジュールにアクセスする必要があり、そのモジュールの所有者ではない場合(つまり、opens…toディレクティブを使用できない場合)、コマンドライン–add-opensを使用できます。 ■実行時にロックダウンされたモジュールへの独自のモジュールリフレクションアクセスを許可するオプション。

ここでの唯一の注意点は、これが機能するためにモジュールを実行するために使用されるコマンドライン引数にアクセスできる必要があるということです。

7. すべてを一緒に入れて

モジュールとは何か、モジュールの使用方法がわかったので、先に進んで、今学んだすべての概念を示す簡単なプロジェクトを作成しましょう。

簡単にするために、MavenやGradleは使用しません。 代わりに、コマンドラインツールを使用してモジュールを構築します。

7.1. プロジェクトの設定

まず、プロジェクト構造を設定する必要があります。 ファイルを整理するためにいくつかのディレクトリを作成します。

プロジェクトフォルダーを作成することから始めます。

mkdir module-project
cd module-project

これがプロジェクト全体のベースであるため、MavenまたはGradleビルドファイル、その他のソースディレクトリ、リソースなどのファイルをここに追加します。

また、プロジェクト固有のすべてのモジュールを保持するディレクトリを配置します。

次に、モジュールディレクトリを作成します。

mkdir simple-modules

プロジェクト構造は次のようになります。

module-project
|- // src if we use the default package
|- // build files also go at this level
|- simple-modules
  |- hello.modules
    |- com
      |- example
        |- modules
          |- hello
  |- main.app
    |- com
      |- example
        |- modules
          |- main

7.2. 私たちの最初のモジュール

基本的な構造が整ったので、最初のモジュールを追加しましょう。

simple-modules ディレクトリの下に、hello.modulesという名前の新しいディレクトリを作成します。

We can name this anything we want but follow package naming rules(つまり、単語を区切るピリオドなど)。 必要に応じて、メインパッケージの名前をモジュール名として使用することもできますが、通常は、このモジュールのJARの作成に使用するのと同じ名前を使用します。

新しいモジュールの下で、必要なパッケージを作成できます。 この場合、1つのパッケージ構造を作成します。

com.example.modules.hello

次に、このパッケージにHelloModules.javaという新しいクラスを作成します。 コードをシンプルにします。

package com.example.modules.hello;

public class HelloModules {
    public static void doSomething() {
        System.out.println("Hello, Modules!");
    }
}

最後に、hello.modulesのルートディレクトリに、モジュール記述子を追加します。 module-info.java

module hello.modules {
    exports com.example.modules.hello;
}

この例を単純にするために、com.example.modules.hello packageのすべてのパブリックメンバーをエクスポートするだけです。

7.3. 2番目のモジュール

最初のモジュールは素晴らしいですが、何もしません。

これで使用する2番目のモジュールを作成できます。

simple-modulesディレクトリの下に、main.appという別のモジュールディレクトリを作成します。 今回はモジュール記述子から始めます。

module main.app {
    requires hello.modules;
}

外の世界に何も公開する必要はありません。 代わりに、最初のモジュールに依存するだけなので、エクスポートするパブリッククラスにアクセスできます。

これで、それを使用するアプリケーションを作成できます。

新しいパッケージ構造を作成します:com.example.modules.main

次に、MainApp.java.という名前の新しいクラスファイルを作成します

package com.example.modules.main;

import com.example.modules.hello.HelloModules;

public class MainApp {
    public static void main(String[] args) {
        HelloModules.doSomething();
    }
}

そして、これがモジュールのデモに必要なすべてのコードです。 次のステップは、コマンドラインからこのコードをビルドして実行することです。

7.4. モジュールの構築

プロジェクトをビルドするには、単純なbashスクリプトを作成して、プロジェクトのルートに配置します。

compile-simple-modules.shというファイルを作成します。

#!/usr/bin/env bash
javac -d outDir --module-source-path simple-modules $(find simple-modules -name "*.java")

このコマンドには、javacコマンドとfindコマンドの2つの部分があります。

findコマンドは、simple-modulesディレクトリにあるすべての。javaファイルのリストを出力するだけです。 次に、そのリストをJavaコンパイラに直接フィードできます。

以前のバージョンのJavaとは異なる方法で行う必要があるのは、モジュールを構築していることをコンパイラに通知するためのmodule-source-pathパラメータを提供することだけです。

このコマンドを実行すると、2つのコンパイル済みモジュールを含むoutDirフォルダが作成されます。

7.5. コードの実行

そして、ついにコードを実行して、モジュールが正しく機能していることを確認できます。

プロジェクトのルートに別のファイルrun-simple-module-app.shを作成します。

#!/usr/bin/env bash
java --module-path outDir -m main.app/com.example.modules.main.MainApp

モジュールを実行するには、少なくともmodule-pathとメインクラスを指定する必要があります。 すべてが機能する場合、以下が表示されます。

>$ ./run-simple-module-app.sh
Hello, Modules!

7.6. サービスの追加

モジュールの作成方法の基本を理解したので、もう少し複雑にしましょう。

provides…withおよびusesディレクティブの使用方法を見ていきます。

HelloInterface.javaという名前のhello.modulesモジュールで新しいファイルを定義することから始めます。

public interface HelloInterface {
    void sayHello();
}

簡単にするために、このインターフェースを既存のHelloModules.javaクラスで実装します。

public class HelloModules implements HelloInterface {
    public static void doSomething() {
        System.out.println("Hello, Modules!");
    }

    public void sayHello() {
        System.out.println("Hello!");
    }
}

serviceを作成するために必要なのはこれだけです。

ここで、モジュールがこのサービスを提供することを世界に伝える必要があります。

module-info.javaに以下を追加します。

provides com.example.modules.hello.HelloInterface with com.example.modules.hello.HelloModules;

ご覧のとおり、インターフェイスとそれを実装するクラスを宣言します。

次に、このserviceを消費する必要があります。 main.appモジュールで、module-info.javaに以下を追加しましょう。

uses com.example.modules.hello.HelloInterface;

最後に、メインメソッドでは、このサービスを次のように使用できます。

HelloModules module = new HelloModules();
module.sayHello();

コンパイルして実行します。

#> ./run-simple-module-app.sh
Hello, Modules!
Hello!

これらのディレクティブを使用して、コードの使用方法をより明確にします。

実装をプライベートパッケージに入れ、インターフェイスをパブリックパッケージに公開できます。

これにより、余分なオーバーヘッドがほとんどなく、コードのセキュリティが大幅に向上します。

先に進み、他のディレクティブのいくつかを試して、モジュールとその機能についてさらに学習してください。

8. 名前のないモジュールへのモジュールの追加

The unnamed module concept is similar to the default package.したがって、実際のモジュールとは見なされませんが、デフォルトのモジュールと見なすことができます。

クラスが名前付きモジュールのメンバーでない場合、この名前のないモジュールの一部として自動的に考慮されます。

モジュールグラフで特定のプラットフォーム、ライブラリ、またはサービスプロバイダーモジュールを確保するために、デフォルトのルートセットにモジュールを追加する必要がある場合があります。 たとえば、Java 9コンパイラでJava 8プログラムをそのまま実行しようとすると、モジュールを追加する必要がある場合があります。

一般に、the option to add the named modules to the default set of root modules is *–add-modules <module>*(,<module>)*ここで、<module>はモジュール名です。

たとえば、すべてのjava.xml.bindモジュールへのアクセスを提供するには、構文は次のようになります。

--add-modules java.xml.bind

これをMavenで使用するには、同じものをmaven-compiler-pluginに埋め込むことができます。


    org.apache.maven.plugins
    maven-compiler-plugin
    3.8.0
    
        9
        9
        
            --add-modules
            java.xml.bind
        
    

9. 結論

この広範なガイドでは、新しいJava 9モジュールシステムの基本に焦点を当てて取り上げました。

私たちは、モジュールとは何かについて話すことから始めました。

次に、JDKに含まれるモジュールを検出する方法について説明しました。

また、モジュール宣言ファイルについて詳しく説明しました。

モジュールを構築するために必要なさまざまなコマンドライン引数について説明することで、理論を締めくくりました。

最後に、以前の知識をすべて実践し、モジュールシステム上に構築された簡単なアプリケーションを作成しました。

このコードなどを表示するには、必ずcheck it out over on Githubを実行してください。