Java 9-Tutorial für diejenigen, die mit Legacy-Code arbeiten müssen

Guten Abend, Kollegen. Vor genau einem Monat haben wir von Manning einen Auftrag für die Übersetzung von Modern Java erhalten , der nächstes Jahr eines unserer bemerkenswertesten neuen Produkte sein soll. Das Problem von "Modern" und "Legacy" in Java ist so akut, dass die Notwendigkeit eines solchen Buches ziemlich reif ist. Das Ausmaß der Katastrophe und die Lösung von Problemen in Java 9 werden in einem Artikel von Wayne Citrin kurz beschrieben, dessen Übersetzung wir Ihnen heute anbieten möchten.

Alle paar Jahre, mit der Veröffentlichung einer neuen Version von Java, beginnen die Sprecher von JavaOne, neue Sprachkonstrukte und APIs zu genießen und ihre Tugenden zu loben. Und eifrige Entwickler sind inzwischen bestrebt, neue Funktionen einzuführen. Ein solches Bild ist weit von der Realität entfernt - es berücksichtigt nicht, dass die meisten Programmierer damit beschäftigt sind , vorhandene Anwendungen zu unterstützen und zu finalisieren , und schreibt keine neuen Anwendungen von Grund auf neu.

Die meisten Anwendungen - insbesondere kommerzielle - müssen abwärtskompatibel mit früheren Java-Versionen sein, die nicht alle diese neuen Super-Duper-Funktionen unterstützen. Schließlich sind die meisten Kunden und Endbenutzer, insbesondere im Großunternehmenssegment, besorgt über ein radikales Upgrade der Java-Plattform und warten lieber, bis sie stärker wird.

Sobald der Entwickler eine neue Gelegenheit ausprobiert, ist er daher mit Problemen konfrontiert. Würden Sie die Standardschnittstellenmethoden in Ihrem Code verwenden? Vielleicht - wenn Sie Glück haben und Ihre Anwendung nicht mit Java 7 oder niedriger interagieren muss. java.util.concurrent.ThreadLocalRandom Klasse java.util.concurrent.ThreadLocalRandom , um Pseudozufallszahlen in einer Multithread-Anwendung zu generieren? Es funktioniert nicht, wenn Ihre Anwendung gleichzeitig auf Java 6, 7, 8 oder 9 ausgeführt werden soll.

Mit der Veröffentlichung der neuen Version fühlen sich Entwickler, die Legacy-Code unterstützen, wie Kinder gezwungen, auf ein Fenster einer Konditorei zu starren. Sie dürfen nicht hinein, daher ist ihr Schicksal Enttäuschung und Frustration.

Gibt es in der neuen Version von Java 9 etwas für Programmierer, die an der Unterstützung von Legacy-Code beteiligt sind? Etwas, das ihnen das Leben erleichtern könnte? Zum Glück ja.

Was mit der Unterstützung von Legacy-Code getan werden musste, ist das Erscheinungsbild von Java 9

Natürlich können Sie die Funktionen der neuen Plattform in Legacy-Anwendungen integrieren, in denen Sie die Abwärtskompatibilität einhalten müssen. Insbesondere gibt es immer Möglichkeiten, die neuen APIs zu nutzen. Es kann sich jedoch als etwas hässlich herausstellen.

Sie können beispielsweise eine späte Bindung anwenden, wenn Sie auf die neue API zugreifen möchten, wenn Ihre Anwendung auch mit älteren Java-Versionen arbeiten muss, die diese API nicht unterstützen. Angenommen, Sie möchten die in Java 8 eingeführte Klasse java.util.stream.LongStream verwenden und die Methode anyMatch(LongPredicate) dieser Klasse verwenden, die Anwendung muss jedoch mit Java 7 kompatibel sein. Sie können eine anyMatch(LongPredicate) erstellen:

 public classLongStreamHelper { private static Class longStreamClass; private static Class longPredicateClass; private static Method anyMatchMethod; static { try { longStreamClass = Class.forName("java.util.stream.LongStream"); longPredicateClass = Class.forName("java.util.function.LongPredicate"); anyMatchMethod = longStreamClass.getMethod("anyMatch", longPredicateClass): } catch (ClassNotFoundException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null } catch (NoSuchMethodException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null; } public static boolean anyMatch(Object theLongStream, Object thePredicate) throws NotImplementedException { if (longStreamClass == null) throw new NotImplementedException(); try { Boolean result = (Boolean) anyMatchMethod.invoke(theLongStream, thePredicate); return result.booleanValue(); } catch (Throwable e) { // lots of potential exceptions to handle. Let's simplify. throw new NotImplementedException(); } } } 

Es gibt Möglichkeiten, diesen Vorgang zu vereinfachen oder allgemeiner oder effektiver zu gestalten - Sie haben die Idee.

Anstatt theLongStream.anyMatch(thePredicate) wie in Java 8 LongStreamHelper.anyMatch(theLongStream, thePredicate) können Sie LongStreamHelper.anyMatch(theLongStream, thePredicate) in jeder Java-Version LongStreamHelper.anyMatch(theLongStream, thePredicate) . Wenn Sie mit Java 8 arbeiten, funktioniert dies, aber wenn Sie mit Java 7 arbeiten, NotImplementedException das Programm eine NotImplementedException .

Warum ist das hässlich? Da der Code zu kompliziert werden kann, wenn Sie auf viele APIs zugreifen müssen (dies ist bereits jetzt mit einer einzigen API unpraktisch). Darüber hinaus ist diese Vorgehensweise nicht typsicher, da im Code LongStream oder LongPredicate nicht direkt erwähnt LongStream LongPredicate . Schließlich ist diese Praxis aufgrund des Overheads der Reflexion sowie zusätzlicher try-catch Blöcke viel weniger effizient. Obwohl dies auf diese Weise möglich ist, ist es daher nicht allzu interessant und aufgrund von Nachlässigkeit mit Fehlern behaftet.

Ja, Sie können auf die neuen APIs zugreifen, und Ihr Code behält gleichzeitig die Abwärtskompatibilität bei, aber Sie werden mit den neuen Sprachkonstrukten keinen Erfolg haben. Angenommen, wir müssen Lambda-Ausdrücke in Code verwenden, der abwärtskompatibel bleiben und in Java 7 funktionieren soll. Sie haben kein Glück. Mit dem Java-Compiler können Sie keine höhere Quellversion als das Ziel angeben. Wenn Sie also die Konformitätsstufe des Quellcodes auf 1,8 (d. H. Java 8) festlegen und die Zielkonformitätsstufe 1,7 (Java 7) beträgt, lässt der Compiler dies nicht zu.

JAR-Dateien mit mehreren Versionen helfen Ihnen dabei

In jüngerer Zeit hat sich eine weitere großartige Gelegenheit ergeben, die neuesten Java-Funktionen zu verwenden und gleichzeitig Anwendungen mit älteren Java-Versionen zu ermöglichen, bei denen solche Anwendungen nicht unterstützt wurden. In Java 9 wird diese Funktion sowohl für neue APIs als auch für neue Java-Sprachkonstrukte bereitgestellt: Es handelt sich um JAR-Dateien mit mehreren Versionen .

JAR-Dateien mit mehreren Versionen unterscheiden sich fast nicht von den guten alten JAR-Dateien, aber mit einer wichtigen Einschränkung: In den neuen JAR-Dateien ist eine neue „Nische“ erschienen, in der Sie Klassen schreiben können, die die neuesten Java 9-Funktionen verwenden. Wenn Sie mit Java 9 arbeiten, dann Die JVM findet diese "Nische", verwendet die Klassen daraus und ignoriert die gleichnamigen Klassen aus dem Hauptteil der JAR-Datei.

Bei der Arbeit mit Java 8 oder niedriger ist der JVM jedoch die Existenz dieser „Nische“ nicht bekannt. Sie ignoriert es und verwendet Klassen aus dem Hauptteil der JAR-Datei. Mit der Veröffentlichung von Java 10 wird eine neue ähnliche „Nische“ für Klassen angezeigt, die die relevantesten Funktionen von Java 10 usw. verwenden.

In JEP 238 , einem Vorschlag für die Java-Entwicklung, der ausgehungerte JAR-Dateien beschreibt, wird ein einfaches Beispiel bereitgestellt. Angenommen, wir haben eine JAR-Datei mit vier Klassen, die in Java 8 oder niedriger ausgeführt werden:

 JAR root - A.class - B.class - C.class - D.class 

Stellen Sie sich nun vor, dass wir nach der Veröffentlichung von Java 9 die Klassen A und B neu schreiben, damit sie die neuen Funktionen von Java 9 verwenden können. Dann kommt Java 10 heraus und wir schreiben Klasse A neu, damit sie die neuen Funktionen von Java 10 verwenden können. sollte die Anwendung mit Java 8 weiterhin einwandfrei funktionieren. Die neue JAR-Datei mit mehreren Versionen sieht folgendermaßen aus:

 JAR root - A.class - B.class - C.class - D.class - META-INF Versions - 9 - A.class - B.class - 10 - A.class 

Die JAR-Datei hat nicht nur eine neue Struktur erhalten. In seinem Manifest wird nun darauf hingewiesen, dass diese Datei mehrfach versioniert ist.

Wenn Sie diese JAR-Datei in der Java 8-JVM ausführen, wird der Abschnitt \META-INF\Versions ignoriert, da er nicht einmal vermutet oder gesucht wird. Es werden nur die Originalklassen A, B, C und D verwendet.

Unter Java 9 werden die Klassen in \META-INF\Versions\9 verwendet. Außerdem werden sie anstelle der ursprünglichen Klassen A und B verwendet, die Klassen in \META-INF\Versions\10 ignoriert.

Unter Java 10 werden beide Zweige \META-INF\Versions . insbesondere Version A von Java 10, Version B von Java 9 und die Standardversionen C und D.

Wenn Sie also eine neue ProcessBuilder-API von Java 9 in Ihrer Anwendung benötigen, aber sicherstellen müssen, dass die Anwendung unter Java 8 weiterhin funktioniert, schreiben Sie einfach die neuen Versionen Ihrer Klassen mit ProcessBuilder in den Abschnitt \META-INF\Versions\9 der JAR-Datei und belassen Sie die alten Klassen im Hauptteil des Archivs, der standardmäßig verwendet wird. Dies ist der einfachste Weg, die neuen Funktionen von Java 9 zu nutzen, ohne die Abwärtskompatibilität zu beeinträchtigen.

Java 9 JDK verfügt über eine Version des Tools jar.exe, die die Erstellung von JAR-Dateien mit mehreren Versionen unterstützt. Andere Nicht-JDK-Tools bieten diese Unterstützung ebenfalls.

Java 9: ​​Module, Module überall

Das Java 9-Modulsystem (auch als Project Jigsaw bekannt) ist zweifellos die größte Änderung in Java 9. Eines der Modularisierungsziele besteht darin, den Kapselungsmechanismus von Java zu stärken, damit der Entwickler angeben kann, welche APIs für andere Komponenten bereitgestellt werden und zählen können. dass die JVM die Kapselung erzwingt. Die Kapselung ist bei der Modularisierung leistungsfähiger als bei public/protected/private Zugriffsmodifikatoren für Klassen oder Klassenmitglieder.

Das zweite Ziel der Modularisierung besteht darin, anzugeben, welche Module andere Module benötigen, und vor dem Starten der Anwendung sicherzustellen, dass alle erforderlichen Module vorhanden sind. In diesem Sinne sind Module stärker als der herkömmliche Klassenpfadmechanismus, da Klassenpfadpfade nicht im Voraus überprüft werden und Fehler aufgrund des Fehlens der erforderlichen Klassen möglich sind. Somit kann ein falscher Klassenpfad bereits erkannt werden, wenn die Anwendung Zeit hat, lange genug zu arbeiten, oder nachdem sie mehrmals gestartet wurde.
Das gesamte Modulsystem ist groß und komplex, und eine ausführliche Diskussion darüber würde den Rahmen dieses Artikels sprengen (hier eine gute, ausführliche Erklärung ). Hier werde ich auf die Aspekte der Modularisierung eingehen, die dem Entwickler bei der Unterstützung älterer Anwendungen helfen.

Modularisierung ist eine gute Sache, und der Entwickler sollte versuchen, den neuen Code nach Möglichkeit in Module aufzuteilen, auch wenn der Rest der Anwendung (noch) nicht modularisiert ist. Glücklicherweise ist dies dank der Spezifikation für die Arbeit mit Modulen einfach zu bewerkstelligen.

Zunächst wird die JAR-Datei modularisiert (und in ein Modul umgewandelt), wobei die Datei module-info.class (kompiliert aus module-info.java) im Stammverzeichnis der JAR-Datei angezeigt wird. module-info.java enthält Metadaten, insbesondere den Namen des Moduls, dessen Pakete exportiert werden ( module-info.java von außen sichtbar werden), welche Module dieses Modul benötigt, und einige andere Informationen.

Die Informationen in module-info.class sind nur sichtbar, wenn die JVM danach sucht. Das heißt, das System behandelt die modularisierten JAR-Dateien wie gewohnt, wenn es mit älteren Java-Versionen funktioniert (es wird davon ausgegangen, dass der Code für die Arbeit mit einer älteren Java-Version kompiliert wurde Genau genommen erfordert es ein wenig Chemie, und es ist immer noch Java 9, das als Zielversion von module-info.class angegeben ist, aber das ist real.

Daher sollten Sie in der Lage sein, modularisierte JAR-Dateien mit Java 8 und niedriger auszuführen, sofern sie auch in anderer Hinsicht mit früheren Java-Versionen kompatibel sind. Beachten Sie auch, dass module-info.class unter Vorbehalt in versionierten Bereichen von JAR-Dateien mit mehreren Versionen abgelegt werden können .

In Java 9 gibt es sowohl einen Klassenpfad als auch einen Modulpfad. und einen Modulpfad. Der Klassenpfad funktioniert wie gewohnt. Wenn Sie eine modularisierte JAR-Datei in einen Klassenpfad einfügen, wird sie wie jede andere JAR-Datei verschwendet. Das heißt, wenn Sie die JAR-Datei modularisiert haben und Ihre Anwendung noch nicht bereit ist, sie als Modul zu behandeln, können Sie sie in den Klassenpfad einfügen. Sie funktioniert wie immer. Ihr Legacy-Code sollte damit recht erfolgreich umgehen können.

Beachten Sie außerdem, dass die Auflistung aller JAR-Dateien im Klassenpfad als Teil eines einzelnen, nicht benannten Moduls betrachtet wird. Ein solches Modul wird als das häufigste angesehen, exportiert jedoch alle Informationen in andere Module und kann auf alle anderen Module verweisen. Wenn Sie also noch keine modularisierte Java-Anwendung haben, aber einige alte Bibliotheken nicht modularisiert sind (und dies wahrscheinlich auch nie tun werden), können Sie einfach alle diese Bibliotheken in den Klassenpfad einfügen, und das gesamte System funktioniert einwandfrei.

Java 9 verfügt über einen Modulpfad, der mit dem Klassenpfad zusammenarbeitet. Bei Verwendung von Modulen aus diesem Pfad kann die JVM (sowohl zur Kompilierungszeit als auch zur Laufzeit) prüfen, ob alle erforderlichen Module vorhanden sind, und einen Fehler melden, wenn Module fehlen. Alle JAR-Dateien im Klassenpfad sind als Mitglieder eines unbenannten Moduls für die im modularen Pfad aufgelisteten Module zugänglich - und umgekehrt.

Es ist nicht schwierig, die JAR-Datei vom Klassenpfad in den Modulpfad zu übertragen - und die Modularisierung voll auszunutzen. Zuerst können Sie die Datei module-info.class zur JAR-Datei hinzufügen und dann die modularisierte JAR-Datei in den module-info.class . Ein solches neu erstelltes Modul kann weiterhin auf alle verbleibenden JAR-Dateien in der Klassenpfad-JAR zugreifen, da sie in das unbenannte Modul eintreten und weiterhin Zugriff haben.

Es ist auch möglich, dass Sie die JAR-Datei nicht modulieren möchten oder dass die JAR-Datei nicht Ihnen, sondern jemand anderem gehört, sodass Sie sie nicht selbst modulieren können. In diesem Fall kann die JAR-Datei weiterhin in den Modulpfad eingefügt werden. Sie wird zu einem automatischen Modul.

Ein automatisches Modul wird als Modul betrachtet, auch wenn es keine module-info.class . Dieses Modul hat denselben Namen wie die darin enthaltene JAR-Datei, und andere Module können es explizit anfordern. Es exportiert automatisch alle öffentlich verfügbaren APIs und liest (d. H. Erfordert) alle anderen benannten Module sowie namenlosen Module.

Somit kann eine nicht modularisierte JAR-Datei aus einem Klassenpfad in ein Modul umgewandelt werden, ohne etwas zu tun. Vererbte JAR-Dateien werden automatisch in Module konvertiert. Es fehlen lediglich einige Informationen, die bestimmen, ob alle erforderlichen Module vorhanden sind oder was fehlt.

Nicht jede nicht modularisierte JAR-Datei kann in den Modulpfad verschoben und in ein automatisches Modul umgewandelt werden. Es gibt eine Regel: Ein Paket kann Teil nur eines benannten Moduls sein . Wenn sich ein Paket in mehr als einer JAR-Datei befindet, kann nur eine JAR-Datei mit diesem Paket in der Komposition in ein automatisches Modul umgewandelt werden. Der Rest kann im Klassenpfad verbleiben und dem unbenannten Modul beitreten.

Auf den ersten Blick erscheint der hier beschriebene Mechanismus kompliziert, in der Praxis ist er jedoch sehr einfach. In diesem Fall können Sie die alten JAR-Dateien nur im Klassenpfad belassen oder in den Modulpfad verschieben. Sie können sie in Module aufteilen oder nicht. Wenn Ihre alten JAR-Dateien modularisiert sind, können Sie sie im Klassenpfad belassen oder in den Modulpfad verschieben.

In den meisten Fällen sollte einfach alles wie bisher funktionieren. Ihre geerbten JAR-Dateien sollten im neuen modularen System verwurzelt sein. Je mehr Sie den Code modulieren, desto mehr Abhängigkeitsinformationen müssen Sie überprüfen, und die fehlenden Module und APIs werden in viel früheren Entwicklungsstadien erkannt und sparen Ihnen wahrscheinlich viel Arbeit.

Java 9 "Do it yourself": Modulares JDK und Jlink

Eines der Probleme mit älteren Java-Anwendungen besteht darin, dass der Endbenutzer möglicherweise nicht mit einer geeigneten Java-Umgebung arbeitet. Eine Möglichkeit, den Zustand einer Java-Anwendung zu gewährleisten, besteht darin, eine Laufzeit mit der Anwendung bereitzustellen. Mit Java können Sie private (weiterverteilbare) JREs erstellen, die innerhalb der Anwendung verteilt werden können. Hier erfahren Sie, wie Sie eine private JRE erstellen. In der Regel wird die JRE-Dateihierarchie verwendet, die mit dem JDK installiert wird, die erforderlichen Dateien werden gespeichert und die optionalen Dateien mit den Funktionen, die möglicherweise in Ihrer Anwendung benötigt werden, werden gespeichert.

Der Vorgang ist etwas mühsam: Sie müssen eine Hierarchie von Installationsdateien beibehalten und dabei vorsichtig sein, damit Sie keine einzelne Datei und kein einzelnes Verzeichnis verpassen. Dies an sich wird nicht schaden, aber Sie möchten immer noch alles überflüssige loswerden, da diese Dateien Speicherplatz beanspruchen. Ja, es ist einfach nachzugeben und einen solchen Fehler zu machen.

Warum also nicht diese Arbeit an das JDK delegieren?

In Java 9 können Sie eine eigenständige Umgebung erstellen, die der Anwendung hinzugefügt wird. In dieser Umgebung ist alles vorhanden, damit die Anwendung funktioniert. Sie müssen sich keine Sorgen mehr machen, dass der Computer des Benutzers die falsche Umgebung für die Ausführung von Java hat. Sie müssen sich keine Sorgen mehr machen, dass Sie selbst eine private JRE nicht ordnungsgemäß erstellt haben.

Eine Schlüsselressource zum Erstellen solcher in sich geschlossenen ausführbaren Bilder ist ein modulares System. Jetzt können Sie nicht nur Ihren eigenen Code, sondern auch Java 9 JDK selbst modularisieren. Jetzt ist die Java-Klassenbibliothek eine Sammlung von Modulen, und die JDK-Tools bestehen auch aus Modulen. Für das Modulsystem müssen Sie die Basisklassenmodule angeben, die in Ihrem Code benötigt werden, und Sie müssen die erforderlichen JDK-Elemente angeben.

Um alles zusammenzubringen, bietet Java 9 ein spezielles neues Tool namens jlink . Durch das Starten von jlink erhalten Sie eine Hierarchie von Dateien - genau die, die Sie zum Ausführen Ihrer Anwendung benötigen, nicht mehr und nicht weniger. Ein solcher Satz ist viel kleiner als der Standard-JRE, außerdem ist er plattformspezifisch (dh er wird für ein bestimmtes Betriebssystem und einen bestimmten Computer ausgewählt). Wenn Sie solche ausführbaren Images für andere Plattformen erstellen möchten, müssen Sie jlink daher im Kontext der Installation auf jeder bestimmten Plattform ausführen, für die Sie ein solches Image benötigen.

Beachten Sie auch: Wenn Sie jlink mit einer Anwendung ausführen, in der nichts modularisiert ist, verfügt das Tool einfach nicht über die erforderlichen Informationen, um die JRE zu komprimieren, sodass jlink nur noch die gesamte JRE packen muss. Selbst in diesem Fall ist es für Sie etwas bequemer: jlink packt die JRE für Sie, sodass Sie sich keine Gedanken darüber machen müssen, wie die Dateihierarchie korrekt kopiert wird.

Mit jlink wird es einfach, die Anwendung und alles, was Sie zum Ausführen benötigen, zu packen - und Sie müssen sich keine Sorgen machen, dass Sie etwas falsch machen. Das Tool packt nur den Teil der Laufzeit, der für die Arbeit der Anwendung erforderlich ist. Das heißt, eine ältere Java-Anwendung erhält garantiert eine Umgebung, in der sie betriebsbereit ist.

Das Zusammentreffen von Alt und Neu

, Java- , , . Java 9, , API , ( ) , , Java.

Java 9: -, , , Java.

JAR- Java 9 JAR-, Java . , Java 9, Java 8 – .

Java, , JAR- , . , , « » .

JDK jlink, , . , Java – .

Java, Java 9 , – , , Java.

Source: https://habr.com/ru/post/de417733/


All Articles