Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 14 Java Platform Module System
Pfeil 14.1 Klassenlader (Class Loader) und Modul-/Klassenpfad
Pfeil 14.1.1 Klassenladen auf Abruf
Pfeil 14.1.2 Klassenlader bei der Arbeit zusehen
Pfeil 14.1.3 JMOD-Dateien und JAR-Dateien
Pfeil 14.1.4 Woher die kleinen Klassen kommen: Die Suchorte und spezielle Klassenlader
Pfeil 14.1.5 Setzen des Modulpfades
Pfeil 14.2 Module entwickeln und einbinden
Pfeil 14.2.1 Wer sieht wen?
Pfeil 14.2.2 Plattform-Module und ein JMOD-Beispiel
Pfeil 14.2.3 Interne Plattformeigenschaften nutzen, --add-exports
Pfeil 14.2.4 Neue Module einbinden, --add-modules und --add-opens
Pfeil 14.2.5 Projektabhängigkeiten in Eclipse
Pfeil 14.2.6 Benannte Module und module-info.java
Pfeil 14.2.7 Automatische Module
Pfeil 14.2.8 Unbenanntes Modul
Pfeil 14.2.9 Lesbarkeit und Zugreifbarkeit
Pfeil 14.2.10 Modul-Migration
Pfeil 14.3 Zum Weiterlesen
 

Zum Seitenanfang

14.2    Module entwickeln und einbinden Zur vorigen ÜberschriftZur nächsten Überschrift

Das JPMS (Java Platform Module System), auch unter dem Projektnamen Jigsaw bekannt, ist eine der größten Neuerungen seit Java 9. Im Mittelpunkt steht die starke Kapselung: Implementierungsdetails kann ein Modul geheim gehalten. Selbst Hilfscode innerhalb des Moduls, auch wenn er öffentlich ist, darf nicht nach außen dringen. Zweitens kommt eine Abstraktion von Verhalten über Schnittstellen hinzu, die interne Klassen aus dem Modul implementieren können, wobei dem Nutzer die konkreten Klassen nicht bekannt sind. Als dritten Punkt machen explizite Abhängigkeiten die Interaktion mit anderen Modulen klar. Eine grafische Darstellung hilft auch bei großen Architekturen, die Übersicht über Nutzungsbeziehungen zu behalten.

 

Zum Seitenanfang

14.2.1    Wer sieht wen? Zur vorigen ÜberschriftZur nächsten Überschrift

Klassen, Pakete und Module lassen sich als Container mit unterschiedlichen Sichtbarkeiten betrachten:

  • Ein Typ, sei es Klasse oder Schnittstelle, enthält Attribute und Methoden.

  • Ein Paket enthält Typen.

  • Ein Modul enthält Pakete.

  • Private Eigenschaften in einem Typ sind nicht in anderen Typen sichtbar.

  • Nicht öffentliche Typen sind in anderen Paketen nicht sichtbar.

  • Nicht exportierte Pakete sind außerhalb eines Moduls nicht sichtbar.

Ein Modul ist definiert

  1. durch einen Namen,

  2. durch die Angabe, was es exportiert, und

  3. durch die Angabe, welches Modul es zur Arbeit selbst benötigt.

Interessant ist der zweite Aspekt, also dass ein Modul etwas exportiert. Wenn nichts exportiert wird, ist auch nichts nach außen sichtbar. Alles, was Außenstehende sehen sollen, muss in der Modulbeschreibung aufgeführt sein, denn nicht alle öffentlichen Typen des Moduls sind standardmäßig öffentlich, ansonsten wäre das kein Fortschritt zu JAR-Dateien. Mit dem neuen Modulsystem haben wir also eine ganz andere Sichtbarkeit. Aus der Viererbande public, private, paketsichtbar und protected bekommt public eine viel feinere Abstufung. Denn was public ist, bestimmt das Modul, und das sind:

  • Typen, die das Modul für alle exportiert

  • Typen für explizit aufgezählte Module

  • alle Typen im gleichen Modul

Der Compiler und die JVM achten auf die Einhaltung der Sichtbarkeit, und auch Tricks mit Reflection sind nicht mehr möglich, wenn ein Modul keine Freigabe erteilt hat.

Modultypen

Wir wollen uns in dem Abschnitt intensiver mit drei Modultypen beschäftigen. Wenn wir neue Module schreiben, dann sind das benannte Module. Daneben gibt es aus Kompatibilitätsgründen automatische Module und unbenannte Module, mit denen wir vorhandene JAR-Dateien einbringen können. Die Bibliothek der Java SE ist selbst in Module unterteilt, wir nennen sie Plattform-Module. Die Laufzeitumgebung zeigt mit einem Schalter --list-modules alle Plattform-Module an. Der Einsatz des Schalters vor Java 9 führt zu einem Fehler.

[zB]  Beispiel

Liste die ca. 70 Module auf:

$ java --list-modules

java.base@14

java.compiler@14

java.datatransfer@14

java.desktop@14



jdk.unsupported@14

jdk.unsupported.desktop@14

jdk.xml.dom@14

jdk.zipfs@14

Im Ordner D:\Program Files\Java\jdk-14\bin>java --list-modules liegen JMOD-Dateien.

 

Zum Seitenanfang

14.2.2    Plattform-Module und ein JMOD-Beispiel Zur vorigen ÜberschriftZur nächsten Überschrift

Das Kommandozeilenwerkzeug jmod zeigt an, was ein Modul exportiert und benötigt. Nehmen wir die JDBC-API für Datenbankverbindungen als Beispiel: Die Typen liegen in einem eigenen Modul mit dem Namen java.sql:

C:\Program Files\Java\jdk-14\bin>jmod describe ..\jmods\java.sql.jmod

java.sql@14

exports java.sql

exports javax.sql

requires java.base mandated

requires java.logging transitive

requires java.transaction.xa transitive

requires java.xml transitive

uses java.sql.Driver

platform windows-amd64

Wir können daraus Folgendes ablesen:

  • den Namen

  • die Pakete, die das Modul exportiert: java.sql und javax.sql

  • die Module, die java.sql benötigt: java.base ist hier immer drin; hinzu kommen weitere.

  • Die Meldung mit uses steht im Zusammenhang mit dem Service-Locator – wir können das vorerst ignorieren.

  • Die Information über die Plattform (windows-amd64) schreibt jmod mit hinein, es ist die Belegung der System-Property os.arch auf dem Build-Server.

 

Zum Seitenanfang

14.2.3    Interne Plattformeigenschaften nutzen, --add-exports Zur vorigen ÜberschriftZur nächsten Überschrift

Als Sun im letzten Jahrhundert mit der Entwicklung der Java-Bibliotheken begann, kamen eine Reihe interner Hilfsklassen mit in die Bibliothek. Viele beginnen mit den Paketpräfixen com.sun und sun. Die Typen wurden immer als interne Typen kommuniziert, doch bei einigen Entwicklern waren die Neugierde und das Interesse so groß, dass die Warnungen von Sun/Oracle ignoriert wurden. In Java 9 gab es den großen Knall, da public seitdem nicht mehr automatisch public für alle Klassen außerhalb des Moduls ist: Die internen Klassen werden nicht mehr exportiert, sind also nicht mehr benutzbar.

Akt 1: Der Quellcode

Es kommt zu einem Compilerfehler, wie in folgendem Beispiel:

Listing 14.1    com/tutego/insel/tools/ShowRuntimeArguments.java

package com.tutego.insel.tool;



public class ShowRuntimeArguments {

public static void main( String[] args ) throws Exception {

System.out.println( java.util.Arrays.toString(

jdk.internal.misc.VM.getRuntimeArguments() ) );

}

}

Unser Programm greift auf die VM-Klasse zurück, um die eigentliche Belegung der Kommandozeile zu erfragen. Was wir in der main(String[] args)-Methode über args empfangen, enthält keine JVM-Argumente.

Akt 2: Der Compilerfehler

Ab Java 9 lässt sich das Programm nicht mehr ohne Compilerfehler übersetzen:

$ javac com/tutego/insel/tool/ShowRuntimeArguments.java

com\tutego\insel\tool\ShowRuntimeArguments.java:6: error: package jdk.internal.misc is not visible

jdk.internal.misc.VM.getRuntimeArguments() ) );

^

(package jdk.internal.misc is declared in module java.base, which does not export

it to the unnamed module)

1 error

Das Problem dokumentiert der Compiler: jdk.internal.misc ist nicht für unser Programm zugänglich.

Akt 3: Der magische Compiler-Schalter

Zwar ist die Klasse VM selbst public und die Methode getRuntimeArguments() ebenfalls public, aber jdk.internal.misc wurde nicht exportiert, also ist der Zugriff von unserem Programm nicht möglich, denn die JVM realisiert eine Zugriffskontrolle. Allerdings können wir diese abschalten. Mit dem Schalter --add-exports stellen wir aus dem Modul java.base das Paket jdk.internal.misc unserer Klasse bereit. Die allgemeine Syntax ist so:

--add-exports <source-module>/<package>=<target-module>(,<target-module>)*

Die Angabe ist für den Compiler und für die Laufzeitumgebung zu setzen. Für unser Beispiel bedeutet das:

$ javac --add-exports java.base/jdk.internal.misc=ALL-UNNAMED com/tutego/insel/tool/ShowRuntimeArguments.java

Akt 4: Die zickige JVM

Das Programm ist compiliert, führen wir es aus:

$ java com/tutego/insel/tool/ShowRuntimeArguments

Exception in thread "main" java.lang.IllegalAccessError: class com.tutego.insel.tool.ShowRuntimeArguments (in unnamed module @0x4d591d15) cannot access class jdk.internal.misc.VM (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @0x4d591d15

at com.tutego.insel.tool.ShowRuntimeArguments.main(ShowRuntimeArguments.java:6)

Es funktioniert nicht! Aber die Fehlermeldung kommt uns bekannt vor, und wir wissen, warum …

Akt 5: Die JVM will das, was der Compiler will. Schluss

Wir müssen den gleichen Schalter wie für den Compiler setzen:

$ java --add-exports java.base/jdk.internal.misc=ALL-UNNAMED ShowRuntimeArguments

[--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED]

Wir sehen die Ausgabe – das Programm funktioniert.

Eine Angabe wie java.base/jdk.internal.misc, bei der vorne das Modul steht und hinter dem / der Paketname, ist oft ab Java 9 anzutreffen. Hinter dem Gleichheitszeichen steht entweder unser Paket, das die Typen in jdk.internal.misc sehen kann, oder – wie in unserem Fall – ALL-UNNAMED.

jdeps

Hätten wir das Programm schon erfolgreich ab Java 9 übersetzt, würde es zur Laufzeit ebenfalls knallen. Da es nun sehr viel Programmcode gibt, haben die Java-Entwickler bei Oracle das Kommandozeilenprogramm jdeps entwickelt. Es meldet, wenn interne Typen im Programm vorkommen:

$ jdeps com/tutego/insel/tool/ShowRuntimeArguments.class

ShowRuntimeArguments.class -> java.base

com.tutego.insel.tool -> java.io java.base

com.tutego.insel.tool -> java.lang java.base

com.tutego.insel.tool -> java.util java.base

com.tutego.insel.tool -> jdk.internal.misc JDK internal API (java.base)

Anders als beim Java-Compiler ist der volle Dateiname, also mit .class, nötig. Die Meldung »JDK internal API« bereitet uns darauf vor, dass es gleich Ärger geben wird.

So kann relativ leicht eine große Codebasis untersucht werden, und Entwickler können proaktiv den Stellen auf den Grund gehen, die problematische Abhängigkeiten haben.

 

Zum Seitenanfang

14.2.4    Neue Module einbinden, --add-modules und --add-opens Zur vorigen ÜberschriftZur nächsten Überschrift

Jedes Java SE-Projekt basiert auf dem Modul java.se, was diverse Modulabhängigkeiten nach sich zieht (siehe Abbildung 14.1).

Modulabhängigkeiten von »java.se«

Abbildung 14.1    Modulabhängigkeiten von »java.se«

[»]  Hinweis

Nicht alle installierten Module sind im java.se-Modul enthalten. Dazu zählen die JDK-Module und java.smartcardio. Sie befinden sich jedoch standardmäßig im Modulpfad, wie folgender »Einzeiler« zeigt:

ModuleLayer.boot().modules().stream()

.map( Module::getName )

.sorted()

.reduce( ( s1, s2 ) -> s1 + ", " + s2 )

.ifPresent( System.out::println );

Er listet folgende Module auf:

java.base, java.compiler, java.datatransfer, java.desktop, java.instrument, java.logging, java.management, java.management.rmi, java.naming, java.net.http, java.prefs, java.rmi, java.scripting, java.security.jgss, java.security.sasl, java.smartcardio, java.sql, java.sql.rowset, java.transaction.xa, java.xml, java.xml.crypto, jdk.accessibility, jdk.attach, jdk.charsets, jdk.compiler, jdk.crypto.cryptoki, jdk.crypto.ec, jdk.crypto.mscapi, jdk.dynalink, jdk.editpad, jdk.httpserver, jdk.internal.ed, jdk.internal.jvmstat, jdk.internal.le, jdk.internal.opt, jdk.jartool, jdk.javadoc, jdk.jconsole, jdk.jdeps, jdk.jdi, jdk.jdwp.agent, jdk.jfr, jdk.jlink, jdk.jshell, jdk.jsobject, jdk.jstatd, jdk.localedata, jdk.management, jdk.management.agent, jdk.management.jfr, jdk.naming.dns, jdk.naming.rmi, jdk.net, jdk.scripting.nashorn, jdk.sctp, jdk.security.auth, jdk.security.jgss, jdk.unsupported, jdk.unsupported.desktop, jdk.xml.dom, jdk.zipfs

Alle diese Module können ohne Schalter direkt verwendet werden.

Neue Module zu den Kernmodulen hinzufügen und öffnen

Sollen externe Module zu den Kernmodulen hinzugenommen werden, so geschieht das mit dem Schalter --add-modules. Ein weiterer Schalter ist --add-opens, der ein Paket für Reflection öffnet. Neben --add-opens gibt es das ähnliche --add-exports, das alle öffentlichen Typen und Eigenschaften zur Übersetzungs- bzw. Laufzeit öffnet; --add-opens geht für Reflection einen Schritt weiter.

 

Zum Seitenanfang

14.2.5    Projektabhängigkeiten in Eclipse Zur vorigen ÜberschriftZur nächsten Überschrift

Um Module praktisch umzusetzen, wollen wir in Eclipse zwei neue Java-Projekte aufbauen: com.tutego.greeter und com.tutego.main. Wir legen im Projekt com.tutego.greeter eine Klasse com.tutego.insel.greeter.Greeter an und in com.tutego.main die Klasse com.tutego.insel.main.Main. Im Package-Explorer sieht das so aus wie in Abbildung 14.2:

Die Java-Projekte »com.tutego.greeter« und »com.tutego.main« im Package-Explorer

Abbildung 14.2    Die Java-Projekte »com.tutego.greeter« und »com.tutego.main« im Package-Explorer

Jetzt ist eine wichtige Vorbereitung in Eclipse nötig: Wir müssen einstellen, dass com.tutego.main das Java-Projekt com.tutego.greeter benötigt. Dazu gehen wir in das Projekt com.tutego.main und rufen im Kontextmenü Project auf, alternativ im Menüpunkt Project • Properties oder über die Tastenkombination (Alt)+(¢). Im Dialog navigieren wir links auf Java Build Path und aktivieren den Reiter Projects. Wir wählen nun Add…, und im Dialog wählen wir aus der Liste com.tutego.greeter. Ok schließt den kleinen Dialog, und unter Required projects on the build path taucht eine Abhängigkeit auf (siehe Abbildung 14.3).

Wir können jetzt zwei einfache Klassen implementieren. Wir beginnen mit der Klasse für das Projekt com.tutego.greeter:

Listing 14.2    com/tutego/insel/greeter/Greeter.java

package com.tutego.insel.greeter;



public class Greeter {



private Greeter() { }



public static Greeter instance() {

return new Greeter();

}



public void greet( String name ) {

System.out.println( "Hey "+ name );

}

}
Abhängigkeit des Eclipse-Projektes »com.tutego.main« von »com.tutego.greeter«

Abbildung 14.3    Abhängigkeit des Eclipse-Projektes »com.tutego.main« von »com.tutego.greeter«

Dann folgt die Hauptklasse im Projekt com.tutego.main:

Listing 14.3    com/tutego/insel/main/Main

package com.tutego.insel.main;



import com.tutego.insel.greeter.Greeter;



public class Main {



public static void main( String[] args ) {

Greeter.instance().greet( "Chris" );

}

}

Da wir in Eclipse vorher die Abhängigkeit gesetzt haben, gibt es keinen Compilerfehler.

 

Zum Seitenanfang

14.2.6    Benannte Module und module-info.java Zur vorigen ÜberschriftZur nächsten Überschrift

Die Modulinformationen werden über eine Datei module-info.java (kurz Modulinfodatei) deklariert, Annotationen kommen nicht zum Einsatz. Diese zentrale Datei ist der Hauptunterschied zwischen einem Modul und einer einfachen JAR-Datei. In dem Moment, in dem die spezielle Klassendatei module-info.class im Modulpfad vorhanden ist, beginnt die Laufzeitumgebung, das Projekt als Modul zu interpretieren.

inline image  Testen wir das, indem wir in unseren Projekten com.tutego.greeter und com.tutego.main eine Modulinfodatei anlegen. Das kann Eclipse über das Kontextmenü Configure • Create module-info.java für uns machen (siehe Abbildung 14.4).

Die Datei »module-info« anlegen

Abbildung 14.4    Die Datei »module-info« anlegen

Für das erste Modul com.tutego.greeter entsteht:

Listing 14.4    module-info.java

/**

*

*/


/**

* @author Christian

*

*/


module com.tutego.greeter {

exports com.tutego.insel.greeter;

requires java.base;

}

Die zweite Modulinfodatei sieht so aus (die Kommentare sind ausgeblendet):

Listing 14.5    module-info.java

module com.tutego.main {

exports com.tutego.insel.main;

requires com.tutego.greeter;

requires java.base;

}

Hinter dem Schlüsselwort module steht der Name des Moduls, den Eclipse automatisch so wählt, wie das Eclipse-Projekt heißt.[ 233 ](Zur Benennung von Modulen gibt es Empfehlungen in dem englischsprachigen Beitrag http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2017-May/000687.html. ) Darauf folgt ein Block in geschweiften Klammern.

Zwei Schlüsselwörter fallen ins Auge, die wir schon vorher bemerkt haben: exports und requires.

  • Das Projekt/Modul com.tutego.greeter exportiert ausschließlich das Paket com.tutego.insel.greeter, andere Pakete nicht. Es benötigt (requires) java.base, wobei das Modul Standard ist und die Zeile gelöscht werden kann.

  • Das Projekt/Modul com.tutego.main exportiert das Paket com.tutego.insel.main, und es benötigt com.tutego.greeter – diese Information nimmt sich Eclipse selbstständig aus den Projektabhängigkeiten.

[»]  Info

Ein Modul requires ein anderes Modul, aber exports ein Paket.

Beginnen wir mit den Experimenten in den beiden module-info.java-Dateien:

Modul

Aktion

Ergebnis

com.tutego.greeter

com.tutego.main

// requires java.base;

Auskommentieren führt zu keiner Änderung, da java.base immer benötigt (required) wird.

com.tutego.greeter

// exports com.tutego. insel.greeter;

Compilerfehler im main-Modul: »The type com.tutego. insel.greeter.Greeter is not accessible«

com.tutego.greeter

exports com.tutego. insel. greeter to god;

Nur das Modul god bekommt Zugriff auf com.tutego. insel.greeter. Das main-Modul meldet »The type com.tutego.insel.greeter. Greeter is not accessible«.

com.tutego.greeter

exports com.tutego. insel.closer;

Das Hinzufügen führt zum Compilerfehler »The package com.tutego.insel.closer does not exist or is empty«.

com.tutego.main

// requires com.tutego. greeter;

Compilerfehler »The import com.tutego.insel.greeter cannot be resolved«

com.tutego.main

// exports com.tutego. insel.main;

Kein Ergebnis, denn c.t.i.m wird von keinem Modul benötigt (required).

Tabelle 14.1    Die Modulsyntax und ihre Effekte

Die Zeile mit exports com.tutego.insel.greeter to god zeigt einen qualifizierten Export.

Übersetzen und Packen von der Kommandozeile

Setzen wir ins Wurzelverzeichnis des Moduls com.tutego.greeter ein Batch-Skript compile.bat; wir nehmen an, dass das JDK-bin-Verzeichnis im PATH ist.

Listing 14.6    compile.bat

rmdir /s /q lib

mkdir lib

javac -d bin src\module-info.java src\com\tutego\insel\greeter\Greeter.java

jar --create --file=lib/com.tutego.greeter@1.0.jar --module-version=1.0 -C bin .

jar --describe-module --file=lib/com.tutego.greeter@1.0.jar

Das Skript führt folgende Schritte aus:

  1. Löschen eines vielleicht schon angelegten lib-Ordners

  2. Anlegen eines neuen lib-Ordners für die JAR-Datei

  3. Übersetzen der zwei Java-Dateien in den Zielordner bin

  4. Anlegen einer JAR-Datei. --create (abkürzbar zu –c) instruiert das Werkzeug, eine neue JAR-Datei anzulegen. –file (oder kurz –f) bestimmt den Zielnamen, –module-version unsere Versionsnummer, und –C wechselt das Verzeichnis und beginnt ab dort, die Dateien einzusammeln. Die Kommandozeilensyntax beschreibt Oracle auf der Webseite http://docs.oracle.com/en/java/javase/14/docs/specs/man/jar.html.

  5. Die Option --describe-module (oder kurz –d) zeigt die Modulinformation und führt zu folgender (vereinfachten) Ausgabe: com.tutego.greeter@1.0 jar:file:///C:/…/com.tutego.greeter/lib/com.tutego.greeter@1.0.jar/!module-info.class exports com.tutego.insel.greeter requires java.base.

Für das zweite Projekt ist die compile.bat sehr ähnlich, dazu kommt ein Aufruf der JVM, um das Programm zu starten.

Listing 14.7    compile.bat

rmdir /s /q lib

mkdir lib

javac -d bin --module-path ..\com.tutego.greeter\lib src\module-info.java inline image

src\com\tutego\insel\main\Main.java

jar -c -f=lib/com.tutego.main@1.0.jar --main-class=com.tutego.insel.main.Main inline image

--module-version=1.0 -C bin .

java -p lib;..\com.tutego.greeter\lib -m com.tutego.main

Änderungen gegenüber dem ersten Skript sind:

  1. Beim Compilieren müssen wir den Modulpfad mit --module-path (oder kürzer mit -p) angeben, weil ja das Modul com.tutego.greeter required ist.

  2. Beim Anlegen der JAR-Datei geben wir über –main-class die Klasse mit der main(…)-Methode an.

  3. Startet die JVM das Programm, lädt sie das Hauptmodul und alle abhängigen Module. Wir geben beide lib-Ordner mit den JAR-Dateien an und mit –m das sogenannte initiale Modul für die Hauptklasse.

 

Zum Seitenanfang

14.2.7    Automatische Module Zur vorigen ÜberschriftZur nächsten Überschrift

JAR-Dateien spielen seit 20 Jahren eine zentrale Rolle im Java-System; sie vom einen zum anderen Tag abzuschaffen, würde große Probleme bereiten. Ein Blick auf http://mvnrepository.com/repos offenbart über 7,7 Millionen Artefakte. Es gehen zwar auch Dokumentationen und andere Dateien in die Statistik ein, doch diese Zahl verdeutlicht, wie viele JAR-Dateien im Umlauf sind.

Damit JAR-Dateien ab Java 9 eingebracht werden können, gibt es zwei Lösungen: Wir können das JAR entweder in den Klassenpfad oder in den Modulpfad setzen. Kommt ein JAR in den Modulpfad und hat es keine Modulinfodatei, entsteht ein automatisches Modul. Bis auf eine kleine Einschränkung funktioniert das für die meisten existierenden Java-Bibliotheken.

Ein automatisches Modul hat gewisse Eigenschaften für den Modulnamen und Konsequenzen in den Abhängigkeiten:

  • Ohne Modulinfo haben die automatischen Module keinen selbst gewählten Namen, sondern sie bekommen vom System einen Namen zugewiesen, der sich aus dem Dateinamen ergibt.[ 234 ](Automatic-Module-Name in die META-INF-Datei zu setzen ist eine Alternative, dazu später mehr. ) Vereinfacht gesagt: Angehängte Versionsnummern und die Dateiendung werden entfernt und alle nichtalphanumerischen Zeichen werden durch Punkte ersetzt, jedoch nicht zwei Punkte hintereinander.[ 235 ](http://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/module/ModuleFinder.html#of(java.nio.file.Path...). ) Die Version wird erkannt. Die Dokumentation gibt das Beispiel foo-bar-1.2.3-SNAPSHOT.jar an, was zum Modulnamen foo.bar und der Version 1.2.3-SNAPSHOT führt.

  • Automatische Module exportieren immer alle ihre Pakete. Wenn es also eine Abhängigkeit zu diesem automatischen Modul gibt, kann der Bezieher alle sichtbare Typen und Eigenschaften verwenden.

  • Automatische Module können alle anderen Module lesen, auch die unbenannten.

Auf den ersten Blick scheint eine Migration für das Modulsystem einfach: Alle JARs kommen in den Modulpfad, egal ob es Modulinfodateien gibt oder nicht. Allerdings gibt es JAR-Dateien, die von der JVM als automatisches Modul abgelehnt werden – nämlich wenn sie Typen eines Pakets enthalten und dieses Paket sich schon in einem anderen aufgenommenen Modul befindet. Module dürfen keine split packages enthalten, also das gleiche Paket noch einmal enthalten. Die Migration erfordert dann

  • das Zusammenlegen der Pakete zu einem Modul,

  • die Verschiebung in unterschiedliche Pakete oder

  • die Nutzung des Klassenpfades.

 

Zum Seitenanfang

14.2.8    Unbenanntes Modul Zur vorigen ÜberschriftZur nächsten Überschrift

Eine Migration auf eine neue Java-Version sieht in der Regel so aus, dass zuerst die JVM gewechselt und dann geprüft wird, ob die vorhandene Software weiterhin funktioniert. Laufen die Testfälle durch und es gibt keine Auffälligkeiten im Testbetrieb, kann der Produktivbetrieb unter der neuen Version erfolgen. Gibt es keine Probleme, können nach einiger Zeit die neuen Sprachmittel und Bibliotheken verwendet werden.

Eine vorhandene Java-8-Software muss inklusive aller Einstellungen und Einträge im Klassenpfad weiterhin laufen. Das heißt, eine Laufzeitumgebung ab Java 9 kann den Klassenpfad nicht ignorieren. Da es intern nur einen Modulpfad gibt, müssen auch diese JAR-Dateien zu Modulen werden. Die Lösung ist das unbenannte Modul (engl. unnamed module): Jedes JAR im Klassenpfad – dabei spielt es keine Rolle, ob es eine modul-info.class enthält – kommt in das unbenannte Modul. Davon gibt es nur eines, wir sprechen also im Singular, nicht Plural.

»Unbenannt« sagt schon, dass das Modul keinen Namen hat und folglich auch keine Abhängigkeit zu den JAR-Dateien im unbenannten Modul existieren kann. Das ist der Unterschied zu einem automatischen Modul. Ein unbenanntes Modul hat die gleiche Eigenschaft wie ein automatisches Modul, dass es alle Pakete exportiert. Und weil es zur Migration gehört, hat ein unbenanntes Modul auch Zugriff auf alle anderen Module.

 

Zum Seitenanfang

14.2.9    Lesbarkeit und Zugreifbarkeit Zur vorigen ÜberschriftZur nächsten Überschrift

Die Laufzeitumgebung sortiert Module in einen Graphen ein. Die Abhängigkeit der Module führt dabei zur sogenannten Lesbarkeit (engl. readability): Benötigt Modul A Modul B, so liest A Modul B, und B wird von A gelesen. Für die Funktionsweise des Modulsystems ist dies elementar, denn so werden zur Übersetzungszeit schon Fehler ausgeschlossen, wie Zyklen oder gleiche Pakete in unterschiedlichen Modulen. Die Lesbarkeit ist zentral für eine zuverlässige Konfiguration (engl. reliable configuration).

Einen Schritt weiter geht der Begriff der Erreichbarkeit bzw. Zugänglichkeit (engl. accessibility). Wenn ein Modul ein anderes Modul grundsätzlich lesen kann, bedeutet das noch nicht, dass es an alle Pakete und Typen kommt, denn nur diejenigen Typen sind sichtbar, die exportiert worden sind. Lesbare und erreichbare Typen nennen sich erreichbar.

Die nächste Frage ist, welcher Modultyp auf welchen anderen Modultyp Zugriff hat. Tabelle 14.2 fasst die Lesbarkeit zusammen.

Modultyp

Ursprung

Exportiert Pakete

Hat Zugriff auf

Plattformmodul

JDK

Explizit

Benannte Module

Container mit Modulinfo im Modulpfad

Explizit

Plattformmodule, andere benannte Module, automatische Module

Automatische Module

Container ohne Modulinfo im Modulpfad

Alle

Plattformmodule, andere benannte Module, automatische Module, unbenanntes Modul

Unbenanntes Modul

Klassendateien und JARs im Klassenpfad

Alle

Plattformmodule, benannte Module, automatische Module

Tabelle 14.2    Lesbarkeit der Module

Der Modulinfodatei kommt dabei die größte Bedeutung zu, denn sie macht aus einem JAR ein Modular JAR. Fehlt die Modulinformation, bleibt es ein normales JAR, wie Java-Entwickler es seit 20 Jahren kennen. Die JAR-Datei kann neu in den Modulpfad kommen oder in den bekannten Klassenpfad. Das ergibt vier Kombinationen:

Modulpfad

Klassenpfad

JAR mit Modulinformation

Wird benanntes Modul.

Wird unbenanntes Modul.

JAR ohne Modulinformation

Wird automatisches Modul.

Wird unbenanntes Modul.

Tabelle 14.3    JARs im Pfad

JAR-Archive im Klassenpfad sind das bekannte Verhalten, weswegen auch ein Wechsel von Java 8 auf Java 11 möglich sein sollte.

 

Zum Seitenanfang

14.2.10    Modul-Migration Zur vorigen ÜberschriftZur nächsten Überschrift

Nehmen wir an, unsere monolithische Applikation hat keine Abhängigkeiten zu externen Bibliotheken und soll modularisiert werden. Dann besteht der erste Schritt darin, die gesamte Applikation in ein großes benanntes Modul zu setzen. Als Nächstes müssen die einzelnen Bereiche identifiziert werden, damit nach und nach die Bausteine in einzelne Module wandern. Das ist nicht immer einfach, zumal zyklische Abhängigkeiten nicht unwahrscheinlich sind. Bei der Modularisierung des JDK hatten die Oracle-Entwickler viel Mühe.

Das Problem mit automatischen Modulen

Traditionell generieren Build-Werkzeuge wie Maven oder Gradle JAR-Dateien, und ein Dateiname hat sich irgendwie ergeben. Werden jedoch diese JAR-Dateien zu automatischen Modulen, spielt der Dateiname plötzlich eine große Rolle. Doch bewusst wurde der Dateiname vermutlich nie gewählt. Referenziert ein benanntes Modul ein automatisches Modul, bringt das zwei Probleme mit sich: Ändert sich der Dateiname – lassen wir die Versionsnummer einmal außen vor –, heißt auch das automatische Modul anders, und die Abhängigkeit kann nicht mehr aufgelöst werden. Das zweite Problem ist größer: Viele Java-Bibliotheken haben noch keine Modulinformationen, und folglich werden Entwickler eine Abhängigkeit zu diesem automatischen Modul über den abgeleiteten Namen ausdrücken.

Nehmen wir zum Beispiel die beliebte Open-Source-Bibliothek Google Guava. Die JAR-Datei hat den Dateinamen guava-27.0-jre.jar – das automatische Modul heißt folglich guava. Ein benanntes Modul (nennen wir es M1) kann über required guava eine Abhängigkeit ausdrücken. Konvertiert Google die Bibliothek in ein echtes Java 9-Modul, dann wird sich der Name ändern – geplant ist com.google.guava. Und ändert sich der Name, führen alle Referenzierungen in Projekten zu einem Compilerfehler. Ein Alias wäre eine tolle Idee, das gibt es jedoch nicht. Und das Problem besteht ja nicht nur im eigenen Code, der Guava referenziert: Referenziert das eigene Modul M1 ein Modul M2, das wiederum Guava referenziert, so gibt es das gleiche Problem – wir sprechen von einer transitiven Abhängigkeit. Die Änderung des Modulnamens von Guava wird zum Problem, denn wir müssen warten, bis M2 den Namen korrigiert, damit M1 wieder gültig ist.

Eine Lösung mildert das Problem ab: In der JAR-Manifest-Datei kann ein Eintrag Automatic-Module-Name gesetzt werden – das »überschreibt« den automatischen Modulnamen.

[zB]  Beispiel

Apache Commons setzt den Namen so:

Automatic-Module-Name: org.apache.commons.lang3

Benannte Module, die Abhängigkeiten zu automatischen Modulen besitzen, sind also ein Problem. Es ist zu hoffen, dass die zentralen Java-Bibliotheken, auf die sich so viele Lösungen stützen, schnell Modulinformationen einführen. Das wäre eine Lösung von unten nach oben, englisch bottom-up. Das ist das Einzige, was erfolgversprechend ist, aber wohl auch eine lange Zeit benötigen wird. Auch jetzt, einige Zeit nach dem Release von Java 9, haben nur wenige Java-Bibliotheken eine Modulinformation; Automatic-Module-Name kommt häufiger vor.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern