Erste Schritte mit Java 9 und dem Jigsaw Project - Teil Eins

Ursprünglicher Autor: Florian Trossbach
  • Übersetzung
Guten Morgen, Habr!

Seit dem Buch " Java. Eine neue Generation der Entwicklung " verfolgen wir die Entwicklung von seit langem angekündigten neuen Funktionen dieser Sprache, die unter dem allgemeinen Namen " Project Jigsaw " zusammengefasst sind. Heute bieten wir eine Übersetzung des Artikels vom 24. November an, die das Vertrauen weckt, dass Jigsaw dennoch in Java 9 ausgeführt wird.

Acht Jahre sind seit dem Start des Jigsaw-Projekts vergangen, dessen Aufgabe es ist, die Java-Plattform zu modularisieren und ein gemeinsames Modulsystem zu implementieren. Jigsaw wird voraussichtlich zum ersten Mal in Java 9 angezeigt. Diese Version war zuvor sowohl für Java 7 als auch für Java 8 geplant. Der Umfang von Jigsaw hat sich ebenfalls mehrfach geändert. Jetzt gibt es allen Grund zu der Annahme, dass Jigsaw fast fertig ist, da es auf der JavaOne 2015-Konferenz im Oracle-Plenum viel Aufmerksamkeit geschenkt und mehrere Reden zu diesem Thema gleichzeitig gehalten hat . Was bedeutet das für Sie? Was ist ein Jigsaw-Projekt und wie kann man damit arbeiten?

Dies ist die erste von zwei Veröffentlichungen, in denen ich eine kurze Einführung in das Modulsystem geben und das Verhalten von Jigsaw anhand zahlreicher Codebeispiele demonstrieren möchte. Im ersten Teil werden wir diskutieren, was ein Modulsystem ist, wie das JDK modularisiert wurde und das Verhalten des Compilers und der Laufzeit in bestimmten Situationen berücksichtigen.

Was ist ein Modul?

Das Modul ist einfach zu beschreiben: Es ist eine Einheit im Programm, wobei jedes Modul sofort Antworten auf drei Fragen enthält. Diese Antworten werden in der Datei jedes Moduls aufgezeichnet .module-info.java


  • Wie heißt das Modul?
  • Was exportiert er?
  • Was wird dafür benötigt?




Einfaches Modul Die

Antwort auf die erste Frage ist einfach. (Fast) jedes Modul hat einen Namen. Es muss den Konventionen für Paketnamen entsprechen, um beispielsweise Konflikte zu vermeiden. Zur Beantwortung der zweiten Frage bietet das Modul eine Liste aller Pakete dieses bestimmten Moduls, die als öffentliche APIs betrachtet werden und daher von anderen Modulen verwendet werden können. Wenn die Klasse kein exportiertes Paket ist, kann niemand von außerhalb Ihres Moduls darauf zugreifen - auch wenn es öffentlich ist. Die Antwort auf die dritte Frage ist eine Liste der Module, von denen dieses Modul abhängt. Alle öffentlichen Typen exportiertde.codecentric.mymodule




Diese Module stehen dem abhängigen Modul zur Verfügung. Das Jigsaw-Team versucht, den Begriff "ein anderes Modul lesen " einzuführen .

Dies ist eine wesentliche Änderung des Status quo. Bis einschließlich Java 8 war jeder öffentliche Typ im Pfad zu Ihren Klassen für jeden anderen Typ verfügbar. Mit dem Aufkommen von Jigsaw ändert sich das Java-Eingabehilfesystem von

  • Öffentlichkeit
  • privat
  • Standardeinstellung
  • geschützt


auf

  • Öffentlichkeit für alle, die dieses Modul lesen (Exporte)
  • public für einige Module, die dies lesen (Export nach, dies wird im zweiten Teil besprochen)
  • public für jede andere Klasse in diesem Modul
  • privat
  • Standardeinstellung
  • geschützt


Modularisierte JDK-

Modulabhängigkeiten sollten einen azyklischen Graphen bilden und somit zyklische Abhängigkeiten vermeiden. Um dieses Prinzip umzusetzen, musste das Jigsaw-Team das folgende große Problem lösen: Die Java-Laufzeitumgebung, die, wie berichtet, voller zyklischer und unlogischer Abhängigkeiten ist, muss in Module aufgeteilt werden. Das Ergebnis ist eine solche Grafik :



Die Basis der Grafik ist java.base. Dies ist das einzige Modul, das nur eingehende Kanten hat. Jedes Modul, das Sie erstellen, liest unabhängig davon, ob Sie es deklarieren oder nicht - wie im Fall einer implizierten Erweiterung . Exporte Pakete wie die , , usw.java.base
java.lang.Object
java.base
java.lang
java.util
java.math


JDK-Modularisierung bedeutet, dass Sie jetzt angeben können, welche Java-Laufzeitmodule Sie verwenden möchten. Daher sollte Ihre Anwendung keine Umgebung verwenden, die Swing oder Corba unterstützt, es sei denn, Sie lesen Module oder . Die Schaffung einer solchen angepassten Umgebung wird im zweiten Teil beschrieben. Aber eher trockene Theorie ... pohimichit die Quellcode in diesem Artikel beschrieben verfügbar ist hier , darunter ein Shell - Skript zu kompilieren, Paket, und das Beispiel auszuführen. Der hier betrachtete praktische Fall ist sehr einfach. Ich habe ein Modul , das eine bestimmte Validierung einer Postleitzahl durchführt. Dieses Modul wird vom Modul gelesen (das nicht nur Postleitzahlen prüfen konnte, sondern hier nicht, um es nicht zu komplizieren). java.desktop
java.corba







de.codecentric.zipvalidator
de.codecentric.addresschecker

Der zip-Validator wird in der folgenden Datei beschrieben : module de.codecentric.zipvalidator { exports de.codecentric.zipvalidator.api; } Dieses Modul exportiert also das Paket und liest keine anderen Module (außer ). Dieses Modul wird von addresschecker gelesen:module-info.java






de.codecentric.zipvalidator.api
java.base


module de.codecentric.addresschecker{
    exports de.codecentric.addresschecker.api;
    requires de.codecentric.zipvalidator;
}


Die allgemeine Struktur des Dateisystems sieht wie folgt aus:

two-modules-ok/
├── de.codecentric.addresschecker
│   ├── de
│   │   └── codecentric
│   │       └── addresschecker
│   │           ├── api
│   │           │   ├── AddressChecker.java
│   │           │   └── Run.java
│   │           └── internal
│   │               └── AddressCheckerImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           ├── internal
│   │           │   └── ZipCodeValidatorImpl.java
│   │           └── model
│   └── module-info.java


Es besteht eine Vereinbarung, wonach das Modul in einem gleichnamigen Verzeichnis mit diesem Modul abgelegt wird.
Im ersten Beispiel sieht alles gut aus: Wir arbeiten streng nach den Regeln und greifen in unserer Klasse nur auf und aus dem exportierten Paket zu:AddressCheckerImpl
ZipCodeValidator
ZipCodeValidatorFactory


public class AddressCheckerImpl implements AddressChecker {
    @Override
    public boolean checkZipCode(String zipCode) {
        return ZipCodeValidatorFactory.getInstance().zipCodeIsValid(zipCode);
    }
}


Führen Sie nun den Bytecode aus und generieren Sie ihn. Zum Kompilieren (was wir natürlich zuerst tun müssen, da der Adresschecker den zipvalidator liest), tun wir diesjavac
zipvalidator


javac -d de.codecentric.zipvalidator \
$(find de.codecentric.zipvalidator -name "*.java")


Es kommt mir bekannt vor - es wird noch nicht über Module gesprochen, da der zipvalidator von keinem Benutzermodul abhängig ist. Der Befehl hilft uns einfach, die Dateien im angegebenen Verzeichnis aufzulisten. Aber wie melden wir die Struktur unserer Module, wenn wir zur Kompilierung kommen? Dazu wird in Jigsaw - or ein Schalter eingegeben . Um den Adresschecker zu kompilieren, verwenden wir den folgenden Befehl: javac -modulepath. -d de.codecentric.addresschecker \ $ (find de.codecentric.addresschecker -name "* .java") Mit modulepath teilen wir javac mit, wo die kompilierten Module zu finden sind (in diesem Fall this.), es sieht aus wie ein Pfadwechsel zu Klassen (classpath switch).find
.java

javac
modulepath
-mp









Das Kompilieren mehrerer Module für sich scheint jedoch etwas kompliziert zu sein - es ist besser, den anderen Schalter -modulesourcepath zu verwenden, um mehrere Module gleichzeitig zu kompilieren:

javac -d . -modulesourcepath . $(find . -name "*.java")


Dieser Code durchsucht alle Unterverzeichnisse. Verzeichnisse von Modulen und kompiliert alle darin enthaltenen Java-Dateien.
Nachdem wir alles zusammengestellt haben, wollen wir natürlich versuchen, was passiert ist:

java -mp . -m de.codecentric.addresschecker/de.codecentric.addresschecker.api.Run 76185


Wieder geben wir den Pfad zu den Modulen an und teilen der JVM mit, wo sich die kompilierten Module befinden. Wir setzen auch die Hauptklasse (und den Parameter).

Hurra, hier ist das Fazit:

76185 is a valid zip code


Modular Jar

Wie Sie wissen, sind wir es in der Java-Welt gewohnt, unseren Bytecode in JAR-Dateien zu empfangen und zu senden. Jigsaw führt das Konzept des modularen Glases ein. Die modulare JAR-Datei ist der üblichen JAR- Datei sehr ähnlich, enthält jedoch auch eine kompilierte. Sofern solche Dateien für die gewünschte Zielversion kompiliert werden, sind diese Archive abwärtskompatibel. Ist kein gültiger Typname, sodass der kompilierte von älteren JVMs ignoriert wird. Um die JAR-Datei für den zipvalidator zu erstellen, schreiben Sie:module-info.class.
module-info.java
module-info.class




jar --create --file bin/zipvalidator.jar \
--module-version=1.0 -C de.codecentric.zipvalidator 
.

Wir geben die Ausgabedatei, die Version (obwohl die Verwendung mehrerer Versionen des Moduls in Jigsaw zur Laufzeit nicht separat angegeben ist) und das zu packende Modul an.
Da addresschecker auch eine Hauptklasse hat, können wir diese angeben:

jar --create --file=bin/addresschecker.jar --module-version=1.0 \
--main-class=de.codecentric.addresschecker.api.Run \
-C de.codecentric.addresschecker .


Die Hauptklasse wird nicht angezeigt , wie zu erwarten war (ursprünglich hatte das Jigsaw-Team dies geplant), ist jedoch normalerweise im Manifest vermerkt. Wenn Sie dieses Beispiel mit ausführenmodule-info.java




java -mp bin -m de.codecentric.addresschecker 76185


Wir erhalten die gleiche Antwort wie im vorherigen Fall. Wir geben wieder den Pfad zu den Modulen an, der in diesem Fall zum bin-Verzeichnis führt, in dem wir unsere Gläser geschrieben haben. Die Hauptklasse muss nicht angegeben werden, da das Manifest addresschecker.jar diese Informationen bereits enthält. Sagen Sie den Namen des Schaltmoduls . Bisher war alles einfach und angenehm. Als nächstes basteln wir ein bisschen an den Modulen und sehen, wie sich die Stichsäge während des Kompilierens und Ausführens verhält, wenn Sie anfangen, ungeordnet zu sein. Verwenden von nicht exportierten Typen In diesem Beispiel sehen wir, was passiert, wenn wir über ein anderes Modul auf einen Typ zugreifen, den wir nicht verwenden sollten. Da wir die Fabriksache satt haben , ändern wir die Implementierung auf-m








AddressCheckerImpl


return new ZipCodeValidatorImpl().zipCodeIsValid(zipCode);


Wenn wir versuchen zu kompilieren, erhalten wir die erwarteten Ergebnisse

error: ZipCodeValidatorImpl is not visible because 
package de.codecentric.zipvalidator.internal is not visible

Daher funktioniert die Verwendung nicht exportierter Typen während der Kompilierung nicht.
Aber wir sind kluge Jungs, also betrügen wir ein bisschen und verwenden Reflexion.

ClassLoader classLoader = AddressCheckerImpl.class.getClassLoader();
try {
    Class aClass = classLoader.loadClass("de.[..].internal.ZipCodeValidatorImpl");
    return ((ZipCodeValidator)aClass.newInstance()).zipCodeIsValid(zipCode);
} catch (Exception e) {
    throw new  RuntimeException(e);
}

Perfekt kompiliert, lass uns laufen. Aber nein, es ist nicht so einfach, Jigsaw zu täuschen:

java.lang.IllegalAccessException:
class de.codecentric.addresschecker.internal.AddressCheckerImpl 
(in module de.codecentric.addresschecker) cannot access class [..].internal.ZipCodeValidatorImpl 
(in module de.codecentric.zipvalidator) because module
de.codecentric.zipvalidator does not export package
de.codecentric.zipvalidator.internal to module
de.codecentric.addresschecker


Daher beinhaltet Jigsaw das Überprüfen nicht nur zur Kompilierungszeit, sondern auch zur Laufzeit! Und sagt uns sehr deutlich, was wir falsch gemacht haben.

Zyklische Abhängigkeiten

Im folgenden Fall wurde uns plötzlich klar, dass die Addresschecker-API eine Klasse enthielt, die der zipvalidator verwenden konnte. Da wir faul sind, anstatt die Klasse in ein anderes Modul umzugestalten, deklarieren wir eine Abhängigkeit für addresschecker:

module de.codecentric.zipvalidator{
        requires de.codecentric.addresschecker;
        exports de.codecentric.zipvalidator.api;
}


Da zyklische Abhängigkeiten per Definition verboten sind, behindert uns der Compiler (zum Wohle des Gemeinwohls):

./de.codecentric.zipvalidator/module-info.java:2: 
error: cyclic dependence involving de.codecentric.addresschecker


Dies ist nicht möglich, und wir werden vorab gewarnt, auch während der Kompilierung.

Implizite Lesbarkeit

Um die Funktionalität zu erweitern, erben wir den zipvalidator, indem wir ein neues Modul einführen, das ein bestimmtes Modell des Validierungsergebnisses und nicht nur einen banalen Booleschen Wert enthält . Die neue Dateistruktur wird hier angezeigt:de.codecentric.zipvalidator.model


three-modules-ok/
├── de.codecentric.addresschecker
│   ├── de
│   │   └── codecentric
│   │       └── addresschecker
│   │           ├── api
│   │           │   ├── AddressChecker.java
│   │           │   └── Run.java
│   │           └── internal
│   │               └── AddressCheckerImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           └── internal
│   │               └── ZipCodeValidatorImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator.model
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           └── model
│   │               └── api
│   │                   └── ZipCodeValidationResult.java
│   └── module-info.java


Eine Klasse ist eine einfache Aufzählung mit Instanzen der Form "zu kurz", "zu lang" usw. Die Klasse wird folgendermaßen vererbt:ZipCodeValidationResult

module-info.java


module de.codecentric.zipvalidator{
       exports de.codecentric.zipvalidator.api;
       requires de.codecentric.zipvalidator.model;
}


Jetzt sieht unsere Implementierung von ZipCodeValidator so aus:

@Override
public ZipCodeValidationResult zipCodeIsValid(String zipCode) {
   if (zipCode == null) {
       return ZipCodeValidationResult.ZIP_CODE_NULL;
[snip]
   } else {
       return ZipCodeValidationResult.OK;
   }
}


Das Adressprüfmodul ist jetzt so angepasst, dass es eine Aufzählung als Rückgabetyp annehmen kann, damit Sie fortfahren können, oder? Nein! Zusammenstellung gibt:

./de.codecentric.addresschecker/de/[..]/internal/AddressCheckerImpl.java:5: 
error: ZipCodeValidationResult is not visible because package
de.codecentric.zipvalidator.model.api is not visible

Beim Kompilieren des Adresscheckers ist ein Fehler aufgetreten. Der zipvalidator verwendet die exportierten Typen aus dem zipvalidator-Modell in seiner öffentlichen API. Da der Adresschecker dieses Modul nicht liest, kann er nicht auf diesen Typ zugreifen.

Es gibt zwei Lösungen für dieses Problem. Offensichtlich: Fügen Sie dem zipvalidator-Modell eine Lesekante aus dem Adresschecker hinzu. Dies ist jedoch ein schlüpfriger Hang: Warum müssen wir diese Abhängigkeit deklarieren, wenn sie nur für die Arbeit mit zipvalidator benötigt wird? Sollte der zipvalidator nicht garantieren, dass wir auf alle erforderlichen Module zugreifen können? Sollte und darf - hier kommen wir zur impliziten Lesbarkeit. Durch Hinzufügen des öffentlichen Schlüsselworts zur erforderlichen Definition teilen wir allen Client-Modulen mit, dass sie auch ein anderes Modul lesen müssen. Betrachten Sie als Beispiel eine aktualisierte Klassemodule-info.java
zipvalidator:

module de.codecentric.zipvalidator{
       exports de.codecentric.zipvalidator.api;
       requires public de.codecentric.zipvalidator.model;
}


Das Schlüsselwort teilt allen Modulen, die den zipvalidator lesen, mit, dass sie auch sein Modell lesen sollen. Es war notwendig, mit dem Klassenpfad anders zu arbeiten: Sie konnten sich beispielsweise nicht auf Maven POM verlassen, wenn Sie sicherstellen wollten, dass alle Ihre Abhängigkeiten auch für jeden Client zugänglich waren. Um dies zu erreichen, mussten Sie sie explizit angeben, wenn sie Teil Ihrer öffentlichen API waren. Dies ist ein sehr schönes Modell: Wenn Sie Abhängigkeiten nur innerhalb der Klasse verwenden, was ist dann für Ihre Kunden wichtig? Und wenn Sie sie außerhalb der Klasse verwenden, müssen Sie sie auch direkt melden. Zusammenfassungpublic




So ging der erste Teil zu Ende. Wir haben drei Fragen besprochen, die für jedes Modul beantwortet werden müssen, sowie die Modularisierung der Java-Laufzeit. Als Nächstes haben wir uns ein Beispiel angesehen, in dem wir eine einfache Java-Anwendung kompiliert, gestartet und gepackt haben, die aus zwei Modulen besteht. Anschließend haben wir anhand eines Arbeitsbeispiels untersucht, wie das Modulsystem auf einen Verstoß gegen festgelegte Regeln reagiert. Um die Funktionalität zu erweitern, haben wir uns mit dem dritten Modul befasst und über das Konzept der impliziten Lesbarkeit gesprochen.

In den folgenden Abschnitten werden die folgenden Probleme behandelt:

  • Wie funktioniert Jigsaw, wenn der Modulpfad mehrere Module mit demselben Namen enthält?
  • Was passiert, wenn sich im Pfad zu den Modulen andere Module befinden, die jedoch dieselben Pakete exportieren?
  • Was tun mit vererbten Abhängigkeiten, die nicht modularisiert sind?
  • Wie erstelle ich eine eigene abgeschnittene Version der Laufzeit?

Jetzt auch beliebt: