Was ist JPMS? Einführung in das Java Platform Module System

Bis Java 9 war Javas oberstes Code-Organisationselement das Paket. Beginnend mit Java 9, das sich geändert hat: Über dem Paket befindet sich jetzt das Modul. Das Modul sammelt zusammengehörige Pakete.

Das Java Platform Module System (JPMS) ist eine Struktur auf Codeebene, sodass es nichts an der Tatsache ändert, dass wir Java in JAR-Dateien verpacken. Letztendlich ist immer noch alles in JAR-Dateien gebündelt. Das Modulsystem fügt einen neuen übergeordneten Deskriptor hinzu, den JARs verwenden können, indem die module-info.javaDatei integriert wird.

Große Apps und Organisationen nutzen Module, um Code besser zu organisieren. Aber jeder wird Module verbrauchen, da das JDK und seine Klassen jetzt modularisiert sind.

Warum Java Module benötigt

JPMS ist das Ergebnis des Projekts Jigsaw, das mit folgenden Zielen durchgeführt wurde: 

  • Erleichtern Sie Entwicklern das Organisieren großer Apps und Bibliotheken
  • Verbessern Sie die Struktur und Sicherheit der Plattform und des JDK selbst
  • Verbessern Sie die App-Leistung
  • Bessere Handhabung der Zerlegung der Plattform für kleinere Geräte

Es ist erwähnenswert, dass das JPMS eine SE-Funktion (Standard Edition) ist und daher jeden Aspekt von Java von Grund auf beeinflusst. Die Änderung soll jedoch ermöglichen, dass der größte Teil des Codes beim Wechsel von Java 8 zu Java 9 ohne Änderungen funktioniert. Es gibt einige Ausnahmen, die wir später in dieser Übersicht beachten werden.

Die Hauptidee hinter einem Modul besteht darin, die Sammlung verwandter Pakete zu ermöglichen, die für das Modul sichtbar sind, während Elemente vor externen Verbrauchern des Moduls verborgen werden. Mit anderen Worten, ein Modul ermöglicht eine andere Kapselungsebene.

Klassenpfad vs. Modulpfad

In Java war der Klassenpfad bisher das Endergebnis für das, was einer laufenden Anwendung zur Verfügung steht. Obwohl der Klassenpfad diesem Zweck dient und gut verstanden wird, handelt es sich letztendlich um einen großen, undifferenzierten Bucket, in den alle Abhängigkeiten gestellt werden.

Der Modulpfad fügt eine Ebene über dem Klassenpfad hinzu. Es dient als Container für Pakete und bestimmt, welche Pakete für die Anwendung verfügbar sind.

Module im JDK

Das JDK selbst besteht jetzt aus Modulen. Schauen wir uns zunächst die Schrauben und Muttern von JPMS an.

Wenn Sie ein JDK auf Ihrem System haben, haben Sie auch die Quelle. Wenn Sie mit dem JDK und dessen Beschaffung nicht vertraut sind, lesen Sie diesen Artikel.

In Ihrem JDK-Installationsverzeichnis befindet sich ein /libVerzeichnis. In diesem Verzeichnis befindet sich eine src.zipDatei. Entpacke das in ein /srcVerzeichnis.

Schauen Sie in das /srcVerzeichnis und navigieren Sie zum /java.baseVerzeichnis. Dort finden Sie die module-info.javaDatei. Öffne es.

Nach den Javadoc-Kommentaren am Kopf finden Sie einen Abschnitt mit dem Namen,  module java.base gefolgt von einer Reihe von exportsZeilen. Wir werden hier nicht auf das Format eingehen, da es ziemlich esoterisch wird. Die Details finden Sie hier.

Sie können sehen, dass viele der bekannten Pakete aus Java java.ioaus dem java.baseModul exportiert werden. Dies ist die Essenz eines Moduls, das die Pakete zusammenfasst.

Die Kehrseite von  exportsist die requiresAnweisung. Dadurch kann ein Modul für das zu definierende Modul benötigt werden.

Wenn Sie den Java-Compiler für Module ausführen, geben Sie den Modulpfad ähnlich wie den Klassenpfad an. Dadurch können die Abhängigkeiten gelöst werden.

Erstellen eines modularen Java-Projekts

Schauen wir uns an, wie ein moduliertes Java-Projekt aufgebaut ist.

Wir werden ein kleines Programm erstellen, das zwei Module enthält, eines, das eine Abhängigkeit bereitstellt, und das andere, das diese Abhängigkeit verwendet und eine ausführbare Hauptklasse exportiert.

Erstellen Sie ein neues Verzeichnis an einem geeigneten Ort in Ihrem Dateisystem. Nennen Sie es /com.javaworld.mod1. Konventionell befinden sich Java-Module in einem Verzeichnis, das denselben Namen wie das Modul hat.

Erstellen Sie nun in diesem Verzeichnis eine module-info.javaDatei. Fügen Sie im Inneren den Inhalt aus Listing 1 hinzu.

Listing 1: com.javaworld.mod1 / module-info.java

module com.javaworld.mod1 { exports com.javaworld.package1; }

Beachten Sie, dass das Modul und das exportierte Paket unterschiedliche Namen haben. Wir definieren ein Modul, das ein Paket exportiert.

Erstellen Sie nun eine Datei auf diesem Pfad in dem Verzeichnis, das die module-info.javaDatei enthält : /com.javaworld.mod1/com/javaworld/package1. Benennen Sie die Datei  Name.java. Fügen Sie den Inhalt von Listing 2 ein.

Listing 2: Name.java

 package com.javaworld.package1; public class Name { public String getIt() { return "Java World"; } } 

Listing 2 wird zu einer Klasse, einem Paket und einem Modul, von denen wir abhängen.

Jetzt erstellen wir ein weiteres Verzeichnis parallel dazu /com.javaworld.mod1 und rufen es auf /com.javaworld.mod2. Erstellen Sie in diesem Verzeichnis eine module-info.javaModuldefinition, die das bereits erstellte Modul wie in Listing 3 importiert.

Listing 3: com.javaworld.mod2 / module-info.java

 module com.javaworld.mod2 { requires com.javaworld.mod1; } 

Listing 3 ist ziemlich selbsterklärend. Es definiert das com.javaworld.mod2Modul und benötigt com.javaworld.mod1.

/com.javaworld.mod2Erstellen Sie im Verzeichnis einen Klassenpfad wie folgt : /com.javaworld.mod2/com/javaworld/package2.

Fügen Sie nun eine Datei Hello.javamit dem in Listing 4 angegebenen Code hinzu.

Listing 4: Hello.java

 package com.javaworld.package2; import com.javaworld.package1.Name; public class Hello { public static void main(String[] args) { Name name = new Name(); System.out.println("Hello " + name.getIt()); } } 

In Listing 4 definieren wir zunächst das Paket und importieren dann die com.javawolrd.package1.NameKlasse. Beachten Sie, dass diese Elemente genauso funktionieren wie immer. Die Module haben geändert, wie die Pakete auf Dateistrukturstufe und nicht auf Codeebene verfügbar gemacht werden.

Similarly, the code itself should be familiar to you. It simply creates a class and calls a method on it to create a classic “hello world” example.

Running the modular Java example

The first step is to create directories to receive the output of the compiler. Create a directory called /target at the root of the project. Inside, create a directory for each module: /target/com.javaworld.mod1 and /target/com.javaworld.mod2.

Step 2 is to compile the dependency module, outputting it to the /target directory. At the root of the project, enter the command in Listing 5. (This assumes the JDK is installed.)

Listing 5: Building Module 1

 javac -d target/com.javaworld.mod1 com.javaworld.mod1/module-info.java com.javaworld.mod1/com/javaworld/package1/Name.java 

This will cause the source to be built along with its module information.

Step 3 is to generate the dependent module. Enter the command shown in Listing 6.

Listing 6: Building Module 2

 javac --module-path target -d target/com.javaworld.mod2 com.javaworld.mod2/module-info.java com.javaworld.mod2/com/javaworld/package2/Hello.java 

Let’s take a look at Listing 6 in detail. It introduces the module-path argument to javac. This allows us to define the module path in similar fashion to the --class-path switch. In this example, we are passing in the target directory, because that is where Listing 5 outputs Module 1.

Next, Listing 6 defines (via the -d switch) the output directory for Module 2. Finally, the actual subjects of compilation are given, as the module-info.java file and class contained in Module 2.

To run, use the command shown in Listing 7.

Listing 7: Executing the module main class

 java --module-path target -m com.javaworld.mod2/com.javaworld.package2.Hello 

The --module-path switch tells Java to use /target directory as the module root, i.e., where to search for the modules. The -m switch is where we tell Java what our main class is. Notice that we preface the fully qualified class name with its module.

You will be greeted with the output Hello Java World.

Backward compatibility 

You may well be wondering how you can run Java programs written in pre-module versions in the post Java 9 world, given that the previous codebase knows nothing of the module path. The answer is that Java 9 is designed to be backwards compatible. However, the new module system is such a big change that you may run into issues, especially in large codebases.

When running a pre-9 codebase against Java 9, you may run into two kinds of errors: those that stem from your codebase, and those that stem from your dependencies.

For errors that stem from your codebase, the following command can be helpful: jdeps. This command when pointed at a class or directory will scan for what dependencies are there, and what modules those dependencies rely on.

For errors that stem from your dependencies, you can hope that the package you are depending on will have an updated Java 9 compatible build. If not you may have to search for alternatives.

One common error is this one:

How to resolve java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

This is Java complaining that a class is not found, because it has migrated to a module without visibility to the consuming code. There are a couple of solutions of varying complexity and permanency, described here.

Wenn Sie solche Fehler mit einer Abhängigkeit entdecken, wenden Sie sich an das Projekt. Möglicherweise verfügen sie über einen Java 9-Build, den Sie verwenden können.

JPMS ist eine ziemlich umfassende Änderung, deren Annahme einige Zeit in Anspruch nehmen wird. Glücklicherweise gibt es keine dringende Eile, da Java 8 eine langfristige Support-Version ist.

Auf lange Sicht müssen ältere Projekte migriert werden, und neue müssen Module intelligent nutzen, um hoffentlich einige der versprochenen Vorteile zu nutzen.

Diese Geschichte "Was ist JPMS? Einführung in das Java Platform Module System" wurde ursprünglich von JavaWorld veröffentlicht.