Modulare Java-Zukunft: Das Java Platform Module System erklärt

Seite 2: Das Modulsystem

Inhaltsverzeichnis

Das Java Platform Module System gibt Entwicklern die Möglichkeit, Artefakte in Module zu verwandeln. Das JDK wurde in knapp hundert davon zerlegt (einzusehen per java --list-modules), die in dem neuen und bisher unspezifizierten Dateiformat JMOD vorliegen und im Java-Installationsverzeichnis im Unterordner jmods zu finden sind.

Module außerhalb des JDK werden als sogenannte Modular JARs erzeugt. Dabei handelt es sich um normale JARs, die jedoch einen kleinen Zusatz haben: den Module Descriptor (dazu gleich mehr).

Der Compiler und die JVM verarbeiten Module getrennt von "normalen" JARs, und dementsprechend bekommen die Kommandos javac und java außer dem Class Path einen Module Path. Die Pfade funktionieren ähnlich, aber üblicherweise gehören JARs auf den Class Path und Modular JARs auf den Module Path.

Beim Module Descriptor handelt es sich um eine Datei module-info.class im Root-Verzeichnis des JAR. Kompiliert wird sie üblicherweise aus der sogenannten Module Declaration, einer Datei module-info.java im Root-Verzeichnis des Projekts.

Bei der Deklaration handelt es sich um ein gänzlich neues Java-Konstrukt. Es definiert alle Eigenschaften eines Moduls – dazu gehören in erster Linie sein Name, seine Abhängigkeiten und seine öffentliche API, die durch das Exportieren von Paketen festgelegt wird. Eine einfache Deklaration sieht folgendermaßen aus:

module org.codefx.foo {
requires org.codefx.bar;
exports org.codefx.foo.api;
}

Das Beispiel definiert ein Modul org.codefx.foo. Wie Paketnamen sollen die für Module möglichst global eindeutig sein, wozu sich ebenfalls die Benennungsstrategie des Umdrehens eines Domain-Namens anbietet.

Das Modul definiert eine Abhängigkeit von org.codefx.bar. Zusätzliche lassen sich durch weitere requires-Klauseln angeben. Außerdem exportiert das Modul das Paket org.codefx.foo.api. Dass dessen Name so anfängt wie der des Moduls, ist kein Zufall, denn beide verwenden die gleiche Benennungsstrategie – das ist aber keineswegs verpflichtend. Weitere Pakete können Entwickler durch mehr exports-Klauseln exportieren.

Sowohl beim Kompilieren als auch beim Ausführen ist das Modulsystem mit von der Partie. Es wertet die Modulbeschreibungen aus und ist so in der Lage, problematische Situationen früh zu erkennen.

Zunächst werden die requires-Klauseln betrachtet. Wenn ein Modul foo die Klausel requires bar enthält, sagt man dazu umgangssprachlich "foo benötigt bar" oder "foo hängt von bar ab". Das Modulsystem bildet das durch das neue Konzept der Readability ab. Im Beispiel ist "bar lesbar durch foo" beziehungsweise "foo ließt bar".

Dazu muss bar allerdings tatsächlich auffindbar sein – sonst gibt es einen Kompilierfehler (wenn gerade der Compiler läuft) oder eine Exception (wenn die JVM das Programm startet). Das heißt, dass das Modulsystem sowohl zur Kompilier- als auch zur Startzeit sicherstellt, dass alle benötigten Abhängigkeiten verfügbar sind – das JPMS fasst das als "Reliable Configuration" zusammen. Das ermöglicht es viele Fehler, die sonst erst zur Laufzeit aufgefallen sind (zum Beispiel NoClassDefFoundError), beim Start der JVM zu finden.

Auf Readability aufbauend gibt es das Konzept der Accessibility, das regelt, welche Typen für welchen Code zugreifbar sind. Ein Typ Drink in einem Modul bar ist für Code in einem Modul foo zugänglich, wenn die folgenden drei Bedingungen erfüllt sind:

  • Drink ist public.
  • Das Paket, das Drink enthält, wird von bar exportiert.
  • Das Modul foo liest bar.

Ohne weiteres Zutun gelten die ersten beiden Bedingungen auch für Reflection.

Zusammengefasst gesagt, public heißt nicht mehr "öffentlich für alle", sondern zunächst nur "öffentlich im gleichen Modul" – für jede Öffnung für andere Module müssen Entwickler eine bewusste Entscheidung treffen.

Das Modulsystem bezeichnet das als "Strong Encapsulation" (starke Kapselung), und es ist der Hauptgrund für viele der hitzigen Diskussionen um Java 9. Auf der einen Seite erlaubt es dem JDK und Bibliotheken, sicherheitskritischen oder nur für die interne Benutzung gedachten Code so zu kapseln, dass er sich nicht ohne weiteres verwenden lässt. Auf der anderen Seite sorgt es dafür, dass von vielen Bibliotheken und Frameworks verwendete Herangehensweisen nicht mehr so einfach oder vielleicht sogar überhaupt nicht mehr funktionieren. Beim Umstieg auf Java 9 wird das einige Arbeit verursachen – insbesondere für große Anwendungen.

Neben Reliable Configuration und Strong Encapsulation gibt das Modulsystem noch weitere Versprechen. Die starke Kapselung soll die Sicherheit des JDK verbessern beziehungsweise weniger aufwendig gestalten. Da sie dem JDK, aber auch Bibliotheken und Frameworks die Möglichkeit gibt zu verhindern, dass anderer Code Implementierungsdetails verwendet und so versehentlich davon abhängt, soll sich auch deren Wartbarkeit und damit letzten Endes die Entwicklungsgeschwindigkeit verbessern.

Ein weiterer interessanter Aspekt ist, dass dank des Modulsystems für jede Klasse über ihren Paketnamen klar ist, aus welchem Modul sie stammt, und sich somit das Laden von Klassen beschleunigen lässt. Das soll die Performance beim Starten der JVM verbessern, wo typischerweise viele Klassen geladen werden.