Einführung in Project Jigsaw

1. Einleitung

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, das uns dabei hilft, Folgendes zu 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 betrachtet.

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

  • explizite Verträge und Abhängigkeiten zwischen JARs
  • schwache Einkapselung 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, dass der ClassLoader die erste gefundene Klasse aus der JAR lud , mit sehr unerwarteten Ergebnissen.

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

2.3. Neue Einheit der Modularität

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

3. Projekt Puzzle

Die Hauptmotive für dieses Projekt sind:

  • Erstellen Sie ein Modulsystem für die Sprache - implementiert unter JEP 261
  • Wenden Sie es auf die JDK-Quelle an - implementiert unter JEP 201
  • Modularisieren Sie die JDK- Bibliotheken - implementiert unter JEP 200
  • Aktualisieren Sie die Laufzeit, um die Modularität zu unterstützen - implementiert unter JEP 220
  • in der Lage sein, eine kleinere Laufzeit mit einer Teilmenge von Modulen aus JDK zu erstellen - implementiert unter JEP 282

Eine weitere wichtige Initiative besteht darin, die internen APIs im JDK zu kapseln, diejenigen, die sich unter der Sonne befinden. * -Pakete und andere nicht standardmäßige APIs. Diese APIs sollten niemals von der Öffentlichkeit 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. Es wurden nur wenige interne APIs 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. Außerdem wird 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 Einfügen von nativem Code, Konfigurationsdateien und anderen Daten, die nicht in JAR-Dateien passen

5. Modulsystemarchitektur

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

Eine Moduldefinitionsdatei mit dem Namen module-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
  • jede Implementierung für den von ihm bereitgestellten Dienst

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

Eine allgemeine Struktur des Moduls sieht folgendermaßen aus:

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

Die obige Abbildung definiert zwei Module: com.baeldung.reader und com.baeldung.writer . Für jeden von ihnen ist die Definition in module-info.java und in den Codedateien unter com / baeldung / reader bzw. com / baeldung / writer angegeben.

5.1. Moduldefinitionsterminologien

Schauen wir uns einige der Terminologien an. Wir werden beim Definieren des Moduls (dh innerhalb der module-info.java) Folgendes verwenden :

  • Modul : Die Moduldefinitionsdatei beginnt mit diesem Schlüsselwort, gefolgt von ihrem Namen und ihrer Definition
  • erfordert : wird verwendet, um die Module anzugeben, von denen es abhängt; Nach diesem Schlüsselwort muss ein Modulname angegeben werden
  • transitiv : Nach der angegebenen erfordert Schlüsselwort; Dies bedeutet, dass jedes Modul, das von der Moduldefinition abhängt , transitiv ist, eine implizite Abhängigkeit vom < Modulennamen> erhält
  • Exporte : wird verwendet, um die öffentlich verfügbaren Pakete innerhalb des Moduls anzugeben. Nach diesem Schlüsselwort muss ein Paketname angegeben werden
  • opens: is used to indicate the packages that are accessible only at runtime and also available for introspection via Reflection APIs; this is quite significant to libraries like Spring and Hibernate, highly rely on Reflection APIs; opens can also be used at the module level in which case the entire module is accessible at runtime
  • uses: is used to indicate the service interface that this module is using; a type name, i.e., complete class/interface name, has to specified after this keyword
  • provides … with ...: they are used to indicate that it provides implementations, identified after the with keyword, for the service interface identified after the provides keyword

6. Simple Modular Application

Let us create a simple modular application with modules and their dependencies as indicated in the diagram below:

The com.baeldung.student.model is the root module. It defines model class com.baeldung.student.model.Student, which contains the following properties:

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

It provides other modules with types defined in the com.baeldung.student.model package. This is achieved by defining it in the file module-info.java:

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

The com.baeldung.student.service module provides an interface com.baeldung.student.service.StudentService with abstract CRUD operations:

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

It depends on the com.baeldung.student.model module and makes the types defined in the package com.baeldung.student.service available for other modules:

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

We provide another module com.baeldung.student.service.dbimpl, which provides the implementation com.baeldung.student.service.dbimpl.StudentDbService for the above module:

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 student in DB return student; } public String delete(String registrationId) { // Deleting student in DB return registrationId; } }

It depends directly on com.baeldung.student.service and transitively on com.baeldung.student.model and its definition will be:

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

The final module is a client module – which leverages the service implementation module com.baeldung.student.service.dbimpl to perform its operations:

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

And its definition is:

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

7. Compiling and Running the Sample

We have provided scripts for compiling and running the above modules for the Windows and the Unix platforms. These can be found under the core-java-9 project here. The order of execution for Windows platform is:

  1. compile-student-model
  2. compile-student-service
  3. compile-student-service-dbimpl
  4. compile-student-client
  5. run-student-client

The order of execution for Linux platform is quite simple:

  1. compile-modules
  2. run-student-client

In the scripts above, you will be introduced to the following two command line arguments:

  • –module-source-path
  • –module-path

Java 9 is doing away with the concept of classpath and instead introduces module path. This path is the location where the modules can be discovered.

We can set this by using the command line argument: –module-path.

To compile multiple modules at once, we make use of the –module-source-path. This argument is used to provide the location for the module source code.

8. Module System Applied to JDK Source

Every JDK installation is supplied with a src.zip. This archive contains the code base for the JDK Java APIs. If you extract the archive, you will find multiple folders, few starting with java, few with javafx and the rest with jdk. Each folder represents a module.

The modules starting with java are the JDK modules, those starting with javafx are the JavaFX modules and others starting with jdk are the JDK tools modules.

All JDK modules and all the user defined modules implicitly depend on the java.base module. The java.base module contains commonly used JDK APIs like Utils, Collections, IO, Concurrency among others. The dependency graph of the JDK modules is:

You can also look at the definitions of the JDK modules to get an idea of the syntax for defining them in the module-info.java.

9. Conclusion

In this article, we looked at creating, compiling and running a simple modular application. We also saw how the JDK source code had been modularized.

There are few more exciting features, like creating smaller runtime using the linker tool – jlink and creating modular jars among other features. We will introduce you to those features in details in future articles.

Project Jigsaw ist eine große Veränderung, und wir müssen abwarten, wie es vom Entwickler-Ökosystem akzeptiert wird, insbesondere von den Tools und Bibliothekserstellern.

Der in diesem Artikel verwendete Code ist auf GitHub zu finden.