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

Seite 3: Migration nach Java 9

Inhaltsverzeichnis

Einige der beschriebenen Änderungen führen unmittelbar zu der Frage, wie es um die Abwärtskompatibilität von Java 9 bestellt ist. Was ist etwa mit Code, der von internen APIs abhängt? Und sind von einem Tag auf den anderen alle JARs zu Modulen zu machen?

Prinzipiell ist Java 9 eine voll kompatible Weiterentwicklung, aber das ist nur dann so, wenn man sich an alle Regeln gehalten, das heißt ausschließlich auf standardisierte APIs und Details verlassen hat. Das wiederum ist bei vielen Anwendungen und insbesondere Bibliotheken und Frameworks nicht der Fall.

Im Folgenden sind die wahrscheinlichsten Probleme gelistet, die bei einer Migration auf Java 9 zu beachten sind:

  • Verwendung JDK-interner Java-APIs, wofür alle Klassen in sun.*- oder com.sun.*-Paketen in Frage kommen
  • Verwendung der der als "deprecated" markierten Methoden in LogManager und Pack200
  • "Split Packages", das heißt die Definition eines Typs in einem Paket, das bereits in einem anderen Modul vorkommt
  • Abschaffung von "Endorsed Standards Override Mechanism", "Extension Mechanism" und "Boot Class Path Override"
  • neues Ordnerlayout der Runtime Images (das heißt JRE und JDK)
  • neue Versions-Strings: zum Beispiel 9.1.4 statt 1.9.0_31 (das hat zwar nichts mit Modulen zu tun, kann aber dennoch zu Problemen führen)

Prinzipiell sind die meisten Punkte durch Änderungen im Code lösbar. Das funktioniert allerdings nur, wenn man die Kontrolle darüber hat. Bei Bibliotheken und Frameworks, die man nur verwendet, sind dementsprechend andere Lösungen gefragt. Zu dem Zweck wurden, sozusagen als Notausgänge, einige Command Line Flags eingeführt:

  • Den Zugriff auf private APIs kann man mit --add-exports und --add-opens erlauben.
  • Statt das im Detail freizugeben, kann die JVM auch mit --permit-illegal-access gestartet werden, was alle Zugriffsprüfungen deaktiviert.
  • Das Flag --patch-module erlaubt es, zur Startzeit Klassen neuen Module zuzuordnen und somit einige Split-Package-Fälle zu umgehen.
  • Mit --upgrade-module-path lassen sich JDK-Module gegen andere austauschen.

An anderer Stelle finden sich detailliertere Untersuchungen zur Kompatibilität von Java 9 und zum Konflikt zwischen Reflection und Strong Encapsulation.

Ein wichtiger Aspekt soll hier aber kurz Erwähnung finden: Eine Migration auf Java 9 lässt sich am besten vorbereiten, indem Projekte ihre Abhängigkeiten aktualisieren – nicht nur in puncto Bibliotheken und Frameworks, sondern auch in Sachen Werkzeuge wie Build Tools und IDEs. So lässt sich sicherstellen, dass schnell auf Java-9-kompatible Varianten gewechselt werden kann.

Die Inkompatibilitäten treten bereits bei der Ausführung auf Java 9 auf – unabhängig davon, ob die Anwendung selbst modularisiert wird. Sind sie gemeistert, steht eine Migration des Codes in das Modulsystem noch aus.

Der wichtigste Hinweis zur Modularisierung der eigenen Anwendung ist, dass sie nicht verpflichtend ist. JARs bleiben ein gültiges Format, und der Class Path wird weiterhin voll unterstützt. Eine Anwendung, die auf Java 8 läuft und keine der oben besprochenen Inkompatibilitäten nutzt, funktioniert auf Java 9 genauso weiter.

Daraus folgt, dass das Modulsystem einen Mechanismus haben muss, wodurch seine Module (die des JDK) und JARs (die der Anwendung darüber) zusammenarbeiten können. Er steht tatsächlich zur Verfügung und erlaubt es dank zweier explizit dafür geschaffener Features, die Grenze zwischen Modulen und JARs flexibel zu verschieben. Dadurch können sowohl Entwickler von Anwendungen als auch von Bibliotheken und Frameworks ihre Artefakte unabhängig voneinander modularisieren.

Eines dieser Features ist das sogenannte Unnamed Module. Es wird automatisch vom Modulsystem erstellt und enthält alle Klassen aus JARs, die auf dem Class Path aufgelistet sind.

Alle Application-Klassen landen im gleichen Modul, wodurch zwischen ihnen keine Modulgrenzen bestehen und das Chaos des Class Path innerhalb des Unnamed Module erhalten bleibt – die Konzepte Readability und Accessibility spielen keine Rolle. Es funktioniert also alles wie in Java 8 und davor.

Was passiert aber, wenn aus ihm heraus auf ein anderes Modul, zum Beispiel eine Klasse aus dem JDK, zugegriffen wird? Wie erwähnt, lässt sich ohne Readability-Beziehung keine Accessibility herstellen, die JDK-Klasse wäre daher nicht verwendbar. Da das Unnamed Module automatisch erstellt wurde, lässt sich nicht ohne weiteres feststellen, wovon es abhängt, und folgerichtig liest es automatisch alle anderen Module. Ebenso lässt sich die API nicht automatisch ableiten, weswegen es alle Pakete exportiert.

Da auch modulare JARs letzten Endes nur JARs sind, können Entwickler sie ebenfalls auf den Class Path legen. Für sie gelten die gleichen Regeln wie für reguläre JARs. Das heißt, ihre Module Declaration wird ignoriert, und alle enthaltenen Klassen gehen im Unnamed Module auf. Das erlaubt es Entwicklern einer Bibliothek, sie in ein Modul zu wandeln, ohne dass die Nutzer deswegen gezwungen sind, es auch so einzusetzen.

Zusammen ermöglicht das eine Bottom-up-Migration, im Zuge der Bibliotheken, deren Abhängigkeiten alle zu Modulen gewandelt wurden (also zunächst jene, die nur vom JDK abhängen), selbst zu Modulen werden.