W3docs

Migration zu Java-Modulen

Strategien zur Migration classpath-basierter Java-Anwendungen auf das Java Platform Module System.

Sie müssen nicht modularisieren, um auf Java 9+ umzusteigen. Eine Classpath-Anwendung läuft unverändert — alles wird zu einem einzigen großen unbenannten Modul. Migration ist ein optionaler Schritt, den Sie unternehmen, wenn Sie starke Kapselung, ein benutzerdefiniertes jlink-Runtime oder einen saubereren Abhängigkeitsgraphen wünschen. Die Kunst besteht darin, dies schrittweise zu tun, denn die drei Modularten — benannte, automatische und unbenannte — ermöglichen die Koexistenz von modularem und nicht-modularem Code.

Dieses Kapitel behandelt, wann Migration sich lohnt, wie Sie eine bestehende Anwendung zuerst auf einem modernen JDK zum Laufen bringen, die zwei Modularisierungsrichtungen (Bottom-up vs. Top-down), die Werkzeuge, die die schwere Arbeit übernehmen, und ein ausführbares Beispiel, das den Modulgraphen so inspiziert, wie jdeps es tut. Falls die Begriffe hier unbekannt sind, beginnen Sie mit der Module-Einführung und Deklaration eines Moduls.

Zuerst: einfach auf Java 9+ ausführen (ohne Module)

Bevor Sie eine module-info.java hinzufügen, kompilieren Sie Ihre bestehende Anwendung neu und führen Sie sie auf dem neuen JDK mit allem im Classpath aus. Die wahrscheinlichen Fehler haben nichts mit Ihren Modulen zu tun, sondern alles mit dem jetzt modularen JDK:

  • Entfernte Java EE-Modulejava.xml.bind (JAXB), java.activation, CORBA usw. wurden aus dem JDK entfernt. Fügen Sie sie als gewöhnliche Abhängigkeiten hinzu.
  • Gekapselte Internasun.misc.Unsafe und ähnliches ist nicht mehr zugänglich. --add-exports / --add-opens-Flags sind ein Notausgang, während Sie die Verwendung entfernen.
  • Geteilte Pakete — zwei JARs, die dasselbe Paket bereitstellen, kollidieren nun auf dem Modulpfad (aber nicht auf dem Classpath).

Bringen Sie die Anwendung zuerst auf dem Classpath zum Laufen. Erst dann beginnen Sie mit der Modularisierung.

Bottom-up-Migration

Die bevorzugte Strategie, wenn Sie die gesamte Codebasis kontrollieren:

  1. Beginnen Sie mit den Blatt-Bibliotheken — Module, die von nichts Ihrem abhängen.
  2. Geben Sie jeder eine module-info.java und verschieben Sie sie auf den Modulpfad.
  3. Ihre Abhängigen können sie nun mit requires einbinden. Arbeiten Sie aufwärts im Abhängigkeitsbaum bis zum Einstiegspunkt der Anwendung.

Das funktioniert, weil ein benanntes Modul andere benannte Module und automatische Module einfordern kann — solange die eigenen Abhängigkeiten eines Blatts mindestens automatisch sind, können Sie es modularisieren. Jeder Schritt hält den Build grün. Wenn ein Blatt steckbares Verhalten bereitstellt, ist dies auch ein natürlicher Punkt, um eine Service-Grenze einzuführen, sodass die Abhängigen uses eine Schnittstelle statt einer konkreten Klasse verwenden.

Top-down-Migration

Wenn Sie die Bibliotheken nicht kontrollieren (Drittanbieter-JARs ohne Deskriptoren), gehen Sie den umgekehrten Weg:

  1. Modularisieren Sie zuerst Ihren eigenen Code auf oberster Ebene.
  2. Platzieren Sie die noch nicht modularen Drittanbieter-JARs auf dem Modulpfad, wo sie zu automatischen Modulen werden.
  3. Fordern Sie sie mit requires unter ihrem automatischen Namen an (aus dem JAR-Dateinamen oder Automatic-Module-Name).
  4. Sobald jede Bibliothek eine echte module-info liefert, tauschen Sie die automatische Abhängigkeit gegen die benannte aus — ohne Änderung an Ihrem requires.

Automatische Module sind das Gerüst, das Top-down möglich macht; sie erlauben Ihrem benannten Code, von JARs abzuhängen, die noch nicht modular sind.

Werkzeuge und Fallstricke

  • jdeps analysiert die tatsächlichen Abhängigkeiten eines JARs und kann sogar eine erste module-info.java generieren (jdeps --generate-module-info). Beginnen Sie dort, anstatt Direktiven von Hand zu schreiben.
  • Automatic-Module-Name — wenn Sie eine Bibliothek veröffentlichen, fügen Sie diesen Manifest-Eintrag hinzu, bevor Sie einen vollständigen Deskriptor schreiben. Er fixiert einen stabilen Modulnamen, damit Downstream-Nutzer des automatischen Moduls nicht beeinträchtigt werden, wenn Sie das JAR später umbenennen.
  • Geteilte Pakete müssen zusammengeführt werden. Der Modulpfad verbietet zwei Modulen, dasselbe Paket zu besitzen; der Classpath tolerierte dies. Dies ist der häufigste Migrationsblockierer.
  • opens für Reflexion. Frameworks, die über Ihre Klassen reflektieren (Jackson, JPA, Spring), benötigen opens, sonst erhalten Sie zur Laufzeit InaccessibleObjectException, obwohl die Kompilierung erfolgreich war.
Warnung

Geteilte Pakete sind der Fehler, der die meisten überrascht. Auf dem Classpath fusionierten zwei JARs, die jeweils Klassen in com.example.util enthielten, einfach; auf dem Modulpfad lehnt der Resolver sie mit einem "module reads package ... from both"-Fehler ab. Es gibt kein Flag, das dies legal macht — Sie müssen das duplizierte Paket in ein einziges Modul zusammenführen (oder eines umbenennen). Prüfen Sie auf geteilte Pakete mit jdeps, bevor Sie mit dem Schreiben von Deskriptoren beginnen.

Ein praktisches Beispiel: Inspektion eines Abhängigkeitsgraphen wie jdeps

Die Migrationsplanung beginnt mit „was hängt wovon ab." Dieses Programm liest zur Laufzeit die Moduldeskriptoren der Boot-Layer — dieselben requires-Informationen, die jdeps ausgibt — und erkennt auch, ob es als benanntes Modul oder auf dem Classpath läuft, genau die Prüfung, die anzeigt, wie weit eine Migration fortgeschritten ist.

java— editable, runs on the server

Was Sie aus der Ausführung mitnehmen:

  • Das Programm meldete, dass es als UNNAMED-Modul läuft — genau das, was Sie von Classpath-Code erwarten, und das Laufzeitsignal, dass diese Codebasis noch nicht modularisiert wurde. Wenn Sie nach dem Hinzufügen einer module-info und dem Wechsel auf den Modulpfad erneut ausführen, würde dies auf NAMED umschalten — eine konkrete „Sind wir schon da?"-Prüfung während der Migration.
  • inspect("java.sql") gab seine echte requires-Liste aus, einschließlich einer transitiven Abhängigkeit im Stil von java.transaction.xa oder java.logging. Dies sind dieselben Informationen, die jdeps liefert — die wahren Abhängigkeiten eines Moduls zu kennen ist Schritt eins beim Schreiben seiner module-info.java, und der Deskriptor enthält sie bereits.
  • Einige requires gaben (transitive) aus. Das sind die Abhängigkeiten, die ein Modul re-exportiert; wenn Sie requires java.sql verwenden, lesen Sie automatisch auch seine transitiven Abhängigkeiten. Das Erkennen dieser Abhängigkeiten zeigt Ihnen, welche requires transitive-Zeilen Sie kopieren müssen, wenn Sie Code modularisieren, der ein solches Modul umhüllt.
  • findModule gab ein Optional zurück und verdeutlicht damit, dass ein Modul in einem gegebenen Runtime möglicherweise einfach nicht vorhanden ist — ein mit jlink gekürztes Image könnte java.desktop vollständig weglassen. Migrationspläne müssen berücksichtigen, welche Module tatsächlich im Ziel-Runtime vorhanden sind.
  • Jedes Modul hängt letztendlich von java.base ab, und es erscheint nie in einer requires-Liste, weil es implizit ist. Die Gesamtanzahl der Boot-Module zeigt, dass das JDK ein Graph ist, den Sie programmatisch abfragen können — die Grundlage, auf der Werkzeuge wie jdeps und jlink aufbauen, um eine Anwendung zu analysieren und zu verkleinern.

Eine sinnvolle Migrationsreihenfolge, zusammengefasst

  1. Auf dem Classpath auf dem neuen JDK ausführen; Fehler durch entfernte Module und interne API-Nutzung beheben.
  2. jdeps verwenden, um Abhängigkeiten zu kartieren und geteilte Pakete zu finden.
  3. Automatic-Module-Name zu allen Bibliotheken hinzufügen, die Sie veröffentlichen.
  4. Bottom-up modularisieren, wenn Sie den Baum besitzen; top-down (mit Stütze auf automatische Module), wenn nicht.
  5. opens hinzufügen, wo Frameworks reflektieren; zur Laufzeit verifizieren, nicht nur zur Kompilierzeit.

Damit ist der Teil über das Java Platform Module System abgeschlossen: Sie wissen jetzt, was Module sind, wie man eines deklariert, die drei Arten und wie sie koexistieren, wie Services Module entkoppeln und wie man eine bestehende Anwendung ohne Neuentwicklung auf den Modulpfad migriert.

Übungen

Übung
Ihr Team besitzt den gesamten Quellbaum einer Anwendung und möchte durchgängig starke Kapselung. Bibliothek A hängt von nichts Ihrem ab; Bibliothek B hängt von A ab; die ausführbare Datei hängt von B ab. Welche Migrationsreihenfolge hält jeden Zwischenbuild grün mit den wenigsten automatischen Modul-Workarounds?
Ihr Team besitzt den gesamten Quellbaum einer Anwendung und möchte durchgängig starke Kapselung. Bibliothek A hängt von nichts Ihrem ab; Bibliothek B hängt von A ab; die ausführbare Datei hängt von B ab. Welche Migrationsreihenfolge hält jeden Zwischenbuild grün mit den wenigsten automatischen Modul-Workarounds?
Was this page helpful?