プロジェクトジグソーの紹介

プロジェクトジグソーパズルの概要

1. 前書き

Project Jigsawは、次の2つの側面を目的とした新機能を備えた包括的なプロジェクトです。

  • Java言語でのモジュールシステムの導入

  • およびそのJDKソースおよびJavaランタイムでの実装

この記事では、Jigsawプロジェクトとその機能を紹介し、最後にシンプルなモジュラーアプリケーションでまとめます。

2. モジュール性

簡単に言えば、モジュール性は、次のことを実現するのに役立つ設計原則です。

  • コンポーネント間の疎結合

  • コンポーネント間の契約と依存関係をクリアする

  • 強力なカプセル化を使用した隠された実装

2.1. モジュール性の単位

さて、モジュール性の単位は何かという質問がありますか? Javaの世界、特にOSGiでは、JARはモジュール化の単位と見なされていました。

JARは関連するコンポーネントをグループ化するのに役立ちましたが、いくつかの制限があります。

  • JAR間の明示的な契約と依存関係

  • JAR内の要素の弱いカプセル化

2.2. JAR Hell

JARには別の問題、JAR地獄がありました。 クラスパス上にあるJARの複数のバージョンでは、ClassLoaderがJARから最初に見つかったクラスをロードし、非常に予期しない結果が発生しました。

クラスパスを使用するJVMのもう1つの問題は、アプリケーションのコンパイルは成功するが、実行時にクラスパスにJARがないため、アプリケーションは実行時にClassNotFoundExceptionで失敗することでした。

2.3. モジュール性の新しい単位

これらすべての制限により、モジュール化の単位としてJARを使用する場合、Java言語の作成者は、モジュールと呼ばれる言語の新しい構造を思いつきました。 これにより、Java向けにまったく新しいモジュラーシステムが計画されています。

3. プロジェクトジグソーパズル

このプロジェクトの主な動機は次のとおりです。

  • create a module system for the languageJEP 261で実装

  • apply it to the JDK sourceJEP 201で実装

  • modularize the JDKlibrariesJEP 200で実装

  • update the runtime to support modularityJEP 220で実装

  • be able to create smaller runtime with a subset of modules from JDKJEP 282で実装

もう1つの重要なイニシアチブは、JDKの内部API、sun.*パッケージおよびその他の非標準APIの下にあるAPIをカプセル化することです。 これらのAPIは、一般に使用されることを意図したものではなく、保守される予定もありませんでした。 しかし、これらのAPIの力により、Java開発者はさまざまなライブラリ、フレームワーク、ツールの開発にそれらを活用しました。 いくつかの内部APIの代替が提供されており、その他は内部モジュールに移動されました。

4. モジュール性のための新しいツール

  • jdeps –コードベースを分析してJDKAPIとサードパーティのJARへの依存関係を特定するのに役立ちます。 また、JDK APIが見つかるモジュールの名前にも言及しています。 これにより、コードベースのモジュール化が容易になります。

  • jdeprscan –非推奨のAPIの使用状況についてコードベースを分析するのに役立ちます

  • jlink –アプリケーションとJDKのモジュールを組み合わせることにより、より小さなランタイムを作成するのに役立ちます

  • jmod –jmodファイルの操作に役立ちます。 jmodは、モジュールをパッケージ化するための新しい形式です。 この形式では、ネイティブコード、構成ファイル、およびJARファイルに収まらないその他のデータを含めることができます

5. モジュールシステムアーキテクチャ

言語で実装されたモジュールシステムは、パッケージと同様に、これらをトップレベルの構成体としてサポートします。 開発者はコードをモジュールに編成し、それぞれのモジュール定義ファイルでコード間の依存関係を宣言できます。

module-info.javaという名前のモジュール定義ファイルには、次のものが含まれています。

  • その名前

  • 公開するパッケージ

  • 依存するモジュール

  • 消費するサービス

  • 提供するサービスの実装

上記リストの最後の2つの項目は一般的には使用されません。 これらは、サービスがjava.util.ServiceLoaderインターフェースを介して提供および消費されている場合にのみ使用されます。

モジュールの一般的な構造は次のようになります。

src
 |----com.example.reader
 |     |----module-info.java
 |     |----com
 |          |----example
 |               |----reader
 |                    |----Test.java
 |----com.example.writer
      |----module-info.java
           |----com
                |----example
                     |----writer
                          |----AnotherTest.java

上の図は、com.example.readercom.example.writerの2つのモジュールを定義しています。 それぞれの定義はmodule-info.javaで指定され、コードファイルはそれぞれcom/example/readercom/example/writerの下に配置されます。

5.1. モジュール定義の用語

いくつかの用語を見てみましょう。モジュールを定義するときに使用します(つまり、module-info.java)内で:

  • module:モジュール定義ファイルは、このキーワードで始まり、その後に名前と定義が続きます

  • requires:依存するモジュールを示すために使用されます。このキーワードの後に​​モジュール名を指定する必要があります

  • transitiverequiresキーワードの後に​​指定されます。これは、requires transitive <modulename>を定義するモジュールに依存するモジュールは、<modulename>に暗黙的に依存することを意味します。

  • exports:公開されているモジュール内のパッケージを示すために使用される。このキーワードの後に​​パッケージ名を指定する必要がある

  • opens:実行時にのみアクセス可能で、Reflection APIを介したイントロスペクションにも使用できるパッケージを示すために使用されます。これはSpringやHibernateのようなライブラリにとって非常に重要であり、Reflection APIに強く依存している。 opensはモジュールレベルでも使用できます。その場合、実行時にモジュール全体にアクセスできます。

  • uses:このモジュールが使用しているサービスインターフェイスを示すために使用される。タイプ名i.e.、完全なクラス/インタフェース名は、このキーワードの後に​​指定しなければならない

  • provides … with ..。:providesキーワードの後に​​識別されるサービスインターフェイスに対して、withキーワードの後に​​識別される実装を提供することを示すために使用されます。

6. シンプルなモジュール式アプリケーション

次の図に示すように、モジュールとその依存関係を持つ単純なモジュラーアプリケーションを作成しましょう。

image

com.example.student.modelはルートモジュールです。 これは、次のプロパティを含むモデルクラスcom.example.student.model.Studentを定義します。

public class Student {
    private String registrationId;
    //other relevant fields, getters and setters
}

com.example.student.modelパッケージで定義されたタイプを持つ他のモジュールを提供します。 これは、ファイルmodule-info.javaで定義することで実現されます。

module com.example.student.model {
    exports com.example.student.model;
}

com.example.student.serviceモジュールは、インターフェイスcom.example.student.service.StudentServiceに抽象的なCRUD操作を提供します。

public interface StudentService {
    public String create(Student student);
    public Student read(String registrationId);
    public Student update(Student student);
    public String delete(String registrationId);
}

これはcom.example.student.modelモジュールに依存し、パッケージcom.example.student.serviceで定義されたタイプを他のモジュールで使用できるようにします。

module com.example.student.service {
    requires transitive com.example.student.model;
    exports com.example.student.service;
}

上記のモジュールの実装com.example.student.service.dbimpl.StudentDbServiceを提供する別のモジュールcom.example.student.service.dbimplを提供します。

public class StudentDbService implements StudentService {

    public String create(Student student) {
        // Creating student in DB
        return student.getRegistrationId();
    }

    public Student read(String registrationId) {
        // Reading student from DB
        return new Student();
    }

    public Student update(Student student) {
        // Updating sutdent in DB
        return student;
    }

    public String delete(String registrationId) {
        // Deleting sutdent in DB
        return registrationId;
    }
}

これはcom.example.student.serviceに直接依存し、com.example.student.modelに推移的に依存し、その定義は次のようになります。

module com.example.student.service.dbimpl {
    requires transitive com.example.student.service;
    requires java.logging;
    exports com.example.student.service.dbimpl;
}

最後のモジュールはクライアントモジュールです。これは、サービス実装モジュールcom.example.student.service.dbimplを利用して操作を実行します。

public class StudentClient {

    public static void main(String[] args) {
        StudentService service = new StudentDbService();
        service.create(new Student());
        service.read("17SS0001");
        service.update(new Student());
        service.delete("17SS0001");
    }
}

その定義は次のとおりです。

module com.example.student.client {
    requires com.example.student.service.dbimpl;
}

7. サンプルのコンパイルと実行

上記のモジュールをWindowsおよびUnixプラットフォーム向けにコンパイルおよび実行するためのスクリプトを提供しました。 これらは、core-java-9プロジェクトhereの下にあります。 Windowsプラットフォームの実行順序は次のとおりです。

  1. コンパイル学生モデル

  2. コンパイル学生サービス

  3. compile-student-service-dbimpl

  4. コンパイル学生クライアント

  5. run-student-client

Linuxプラットフォームの実行順序は非常に簡単です。

  1. コンパイルモジュール

  2. run-student-client

上記のスクリプトでは、次の2つのコマンドライン引数を紹介します。

  • –module-source-path

  • –module-path

Java 9はクラスパスの概念を廃止し、代わりにモジュールパスを導入しています。 このパスは、モジュールを検出できる場所です。

これは、コマンドライン引数–module-pathを使用して設定できます。

複数のモジュールを一度にコンパイルするには、–module-source-pathを使用します。 この引数は、モジュールのソースコードの場所を提供するために使用されます。

8. JDKソースに適用されるモジュールシステム

すべてのJDKインストールには、src.zipが付属しています。 このアーカイブには、JDK Java APIのコードベースが含まれています。 アーカイブを抽出すると、複数のフォルダーが見つかります。いくつかはjavaで始まり、いくつかはjavafxで始まり、残りはjdk.で始まります。各フォルダーはモジュールを表します。

image

javaで始まるモジュールはJDKモジュール、javafxで始まるモジュールはJavaFXモジュール、jdkで始まるモジュールはJDKツールモジュールです。

すべてのJDKモジュールとすべてのユーザー定義モジュールは、暗黙的にjava.baseモジュールに依存します。 java.baseモジュールには、Utils、Collections、IO、Concurrencyなどの一般的に使用されるJDKAPIが含まれています。 JDKモジュールの依存関係グラフは次のとおりです。

image

JDKモジュールの定義を調べて、module-info.javaでそれらを定義するための構文を理解することもできます。

9. 結論

この記事では、単純なモジュラーアプリケーションの作成、コンパイル、実行について説明しました。 また、JDKソースコードがどのようにモジュール化されているかを見ました。

リンカーツールを使用してより小さなランタイムを作成する(jlink、他の機能の中でもモジュラーjarを作成する)など、いくつかのエキサイティングな機能があります。 これらの機能の詳細については、今後の記事で紹介します。

Project Jigsawは大きな変化であり、特にツールやライブラリ作成者を含め、開発者のエコシステムにどのように受け入れられるのかを待つ必要があります。

この記事で使用されているコードはover on GitHubにあります。