Einführung in das Projekt Jigsaw

Einführung in Project Jigsaw

1. Einführung

Project Jigsaw ist ein Dachprojekt mit den neuen Funktionen, die auf zwei Aspekte abzielen:

  • die Einführung des Modulsystems in der Java-Sprache

  • und seine Implementierung in JDK-Quell- und Java-Laufzeit

In diesem Artikel stellen wir Ihnen das Jigsaw-Projekt und seine Funktionen vor und schließen es schließlich mit einer einfachen modularen Anwendung ab.

2. Modularität

Einfach ausgedrückt ist Modularität ein Konstruktionsprinzip, mit dem wir Folgendes erreichen:

  • lose Kupplung zwischen Bauteilen

  • Klare Verträge und Abhängigkeiten zwischen Komponenten

  • versteckte Implementierung mit starker Kapselung

2.1. Einheit der Modularität

Nun stellt sich die Frage, was die Einheit der Modularität ist. In der Java-Welt, insbesondere mit OSGi, wurden JARs als Einheit der Modularität angesehen.

JARs haben beim Gruppieren der zugehörigen Komponenten geholfen, haben jedoch einige Einschränkungen:

  • ausdrückliche Verträge und Abhängigkeiten zwischen JARs

  • schwache Kapselung von Elementen innerhalb der JARs

2.2. JAR Hölle

Es gab ein anderes Problem mit JARs - die JAR-Hölle. Mehrere Versionen der JARs, die auf dem Klassenpfad liegen, führten dazu, dassClassLoader die erste gefundene Klasse aus der JAR lud, mit sehr unerwarteten Ergebnissen.

Das andere Problem mit der JVM, die Klassenpfad verwendet, war, dass die Kompilierung der Anwendung erfolgreich war, die Anwendung jedoch zur Laufzeit mitClassNotFoundException fehlschlägt, da zur Laufzeit JARs im Klassenpfad fehlen.

2.3. Neue Einheit der Modularität

Mit all diesen Einschränkungen haben die Entwickler der Java-Sprache bei der Verwendung von JAR als Modularitätseinheit ein neues Konstrukt in der Sprache Module entwickelt. Und damit ist ein ganz neues Baukastensystem für Java geplant.

3. Projekt Puzzle

Die Hauptmotive für dieses Projekt sind:

  • create a module system for the language - implementiert unterJEP 261

  • apply it to the JDK source - implementiert unterJEP 201

  • modularize the JDKlibraries - implementiert unterJEP 200

  • update the runtime to support modularity - implementiert unterJEP 220

  • be able to create smaller runtime with a subset of modules from JDK - implementiert unterJEP 282

Eine weitere wichtige Initiative besteht darin, die internen APIs im JDK, diejenigen, die sich unter densun.*-Paketen befinden, und andere nicht standardmäßige APIs zu kapseln. Diese APIs sollten niemals öffentlich verwendet werden und sollten niemals gewartet werden. Die Leistungsfähigkeit dieser APIs hat die Java-Entwickler jedoch dazu veranlasst, sie bei der Entwicklung verschiedener Bibliotheken, Frameworks und Tools zu nutzen. Einige interne APIs wurden ersetzt und die anderen wurden in interne Module verschoben.

4. Neue Tools für Modularität

  • jdeps - Hilft bei der Analyse der Codebasis, um die Abhängigkeiten von JDK-APIs und JARs von Drittanbietern zu identifizieren. Es wird auch der Name des Moduls erwähnt, in dem sich die JDK-API befindet. Dies erleichtert die Modularisierung der Codebasis

  • jdeprscan - Hilft bei der Analyse der Codebasis für die Verwendung veralteter APIs

  • jlink - hilft beim Erstellen einer kleineren Laufzeit, indem die Module der Anwendung und des JDK kombiniert werden

  • jmod - hilft bei der Arbeit mit jmod-Dateien. jmod ist ein neues Format zum Packen der Module. Dieses Format ermöglicht das Einschließen von nativem Code, Konfigurationsdateien und anderen Daten, die nicht in JAR-Dateien passen

5. Modul Systemarchitektur

Das in der Sprache implementierte Modulsystem unterstützt diese wie Pakete als Top-Level-Konstrukt. Entwickler können ihren Code in Modulen organisieren und Abhängigkeiten zwischen ihnen in ihren jeweiligen Moduldefinitionsdateien deklarieren.

Eine Moduldefinitionsdatei mit dem Namenmodule-info.java enthält:

  • seinen Namen

  • die Pakete, die es öffentlich zur Verfügung stellt

  • von welchen Modulen es abhängt

  • alle Dienste, die es verbraucht

  • jegliche Implementierung für den von ihm bereitgestellten Service

Die letzten beiden Elemente in der obigen Liste werden nicht häufig verwendet. Sie werden nur verwendet, wenn Dienste über diejava.util.ServiceLoader-Schnittstelle bereitgestellt und genutzt werden.

Eine allgemeine Struktur des Moduls sieht wie folgt aus:

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

Die obige Abbildung definiert zwei Module:com.example.reader undcom.example.writer. Für jeden von ihnen ist die Definition inmodule-info.java und die Codedateien untercom/example/reader bzw.com/example/writer angegeben.

5.1. Moduldefinitionsterminologien

Schauen wir uns einige der Terminologien an. Wir werden beim Definieren des Moduls verwenden (d. h. innerhalb dermodule-info.java):

  • module: Die Moduldefinitionsdatei beginnt mit diesem Schlüsselwort, gefolgt von ihrem Namen und ihrer Definition

  • requires: wird verwendet, um die Module anzugeben, von denen es abhängt; Nach diesem Schlüsselwort muss ein Modulname angegeben werden

  • transitive: wird nach dem Schlüsselwortrequires angegeben; Dies bedeutet, dass jedes Modul, das von dem Modul abhängt, dasrequires transitive <modulename> definiert, eine implizite Abhängigkeit von <modulename> erhält

  • exports: wird verwendet, um die öffentlich verfügbaren Pakete innerhalb des Moduls anzugeben. Nach diesem Schlüsselwort muss ein Paketname angegeben werden

  • opens: wird verwendet, um die Pakete anzugeben, auf die nur zur Laufzeit zugegriffen werden kann und die auch über Reflection-APIs zur Introspektion verfügbar sind. Dies ist für Bibliotheken wie Spring und Hibernate von großer Bedeutung, die sich stark auf Reflection-APIs verlassen. opens kann auch auf Modulebene verwendet werden. In diesem Fall ist das gesamte Modul zur Laufzeit verfügbar

  • uses: wird verwendet, um die von diesem Modul verwendete Serviceschnittstelle anzugeben. Nach diesem Schlüsselwort muss ein Typname angegeben werden, d. h. ein vollständiger Klassen- / Schnittstellenname

  • provides … with ...: Sie werden verwendet, um anzuzeigen, dass Implementierungen bereitgestellt werden, die nach dem Schlüsselwortwith für die nach dem Schlüsselwortprovidesidentifizierte Service-Schnittstelle identifiziert wurden

6. Einfache modulare Anwendung

Erstellen wir eine einfache modulare Anwendung mit Modulen und ihren Abhängigkeiten, wie in der folgenden Abbildung dargestellt:

image

com.example.student.model ist das Root-Modul. Es definiert die Modellklassecom.example.student.model.Student, die die folgenden Eigenschaften enthält:

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

Es bietet anderen Modulen Typen, die im Paketcom.example.student.modeldefiniert sind. Dies wird erreicht, indem es in der Dateimodule-info.java definiert wird:

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

Das Modulcom.example.student.service bietet eine Schnittstellecom.example.student.service.StudentService mit abstrakten CRUD-Operationen:

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

Dies hängt vom Modulcom.example.student.model ab und stellt die im Paketcom.example.student.service definierten Typen für andere Module zur Verfügung:

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

Wir stellen ein weiteres Modulcom.example.student.service.dbimpl bereit, das die Implementierungcom.example.student.service.dbimpl.StudentDbService für das obige Modul bereitstellt:

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;
    }
}

Es hängt direkt voncom.example.student.service und transitiv voncom.example.student.model ab und seine Definition lautet:

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

Das letzte Modul ist ein Client-Modul, das das Service-Implementierungsmodulcom.example.student.service.dbimpl nutzt, um seine Operationen auszuführen:

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");
    }
}

Und seine Definition ist:

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

7. Kompilieren und Ausführen des Beispiels

Wir haben Skripte zum Kompilieren und Ausführen der oben genannten Module für die Windows- und die Unix-Plattform bereitgestellt. Diese finden Sie untercore-java-9 Projekthere. Die Ausführungsreihenfolge für die Windows-Plattform lautet:

  1. compile-student-model

  2. compile-student-service

  3. compile-student-service-dbimpl

  4. compile-student-client

  5. Run-Student-Client

Die Ausführungsreihenfolge für die Linux-Plattform ist recht einfach:

  1. Kompiliermodule

  2. Run-Student-Client

In den obigen Skripten werden Ihnen die folgenden zwei Befehlszeilenargumente vorgestellt:

  • –Module-source-path

  • –Module-Pfad

Java 9 beseitigt das Konzept des Klassenpfads und führt stattdessen den Modulpfad ein. Dieser Pfad ist der Ort, an dem die Module erkannt werden können.

Wir können dies mithilfe des Befehlszeilenarguments festlegen:–module-path.

Um mehrere Module gleichzeitig zu kompilieren, verwenden wir–module-source-path. Dieses Argument wird verwendet, um den Speicherort für den Modulquellcode anzugeben.

8. Modulsystem für JDK-Quelle

Jede JDK-Installation wird mit einemsrc.zip geliefert. Dieses Archiv enthält die Codebasis für die JDK-Java-APIs. Wenn Sie das Archiv extrahieren, finden Sie mehrere Ordner, von denen einige mitjava beginnen, wenige mitjavafx und der Rest mitjdk.. Jeder Ordner repräsentiert ein Modul.

image

Die Module, die mitjava beginnen, sind die JDK-Module, diejenigen, die mitjavafx beginnen, sind die JavaFX-Module und andere, die mitjdk beginnen, sind die JDK-Werkzeugmodule.

Alle JDK-Module und alle benutzerdefinierten Module hängen implizit vom Moduljava.baseab. Dasjava.base-Modul enthält häufig verwendete JDK-APIs wie Utils, Collections, IO und Concurrency. Das Abhängigkeitsdiagramm der JDK-Module lautet:

image

Sie können sich auch die Definitionen der JDK-Module ansehen, um eine Vorstellung von der Syntax für deren Definition inmodule-info.javazu erhalten.

9. Fazit

In diesem Artikel haben wir uns mit dem Erstellen, Kompilieren und Ausführen einer einfachen modularen Anwendung befasst. Wir haben auch gesehen, wie der JDK-Quellcode modularisiert wurde.

Es gibt nur wenige aufregendere Funktionen, wie z. B. das Erstellen einer kleineren Laufzeit mit dem Linker-Tool - jlink und das Erstellen modularer Jars unter anderem. Wir werden Sie in zukünftigen Artikeln ausführlich über diese Funktionen informieren.

Project Jigsaw ist eine große Veränderung, und wir werden abwarten müssen, wie es vom Entwickler-Ökosystem akzeptiert wird, insbesondere mit den Werkzeugen und Erstellern der Bibliothek.

Der in diesem Artikel verwendete Code lautetover on GitHub.