---
title: 'Module entwickeln und einbinden'
url: 'https://tutego.de/blog/javainsel/2017/10/module-entwickeln-und-einbinden'
markdown: 'https://tutego.de/blog/javainsel/2017/10/module-entwickeln-und-einbinden.md'
date: '2017-10-23'
description: 'Das JPMS (Java Platform Module System), auch unter dem Projektnamen Jigsaw bekannt, ist eine der größten Neuerungen in 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 …'
taxonomy:
  category:
    - Insel
    - 'Java 9'
---

# Module entwickeln und einbinden

![Illustration einer tropischen Insel mit Palmen und einem hohen Berg](https://tutego.de/blog/javainsel/user/themes/tutego/images/insel-cover.jpg)# Java Blog für Programmierer

[Java Blog für Programmierer](https://tutego.de/blog/javainsel/)

## Module entwickeln und einbinden

23.10.2017 · [Christian Ullenboom](https://tutego.de/blog/javainsel/author/admin/)

Das JPMS (Java Platform Module System), auch unter dem Projektnamen Jigsaw bekannt, ist eine der größten Neuerungen in 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.

### Wer sieht wen

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

- 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 einem Namen,

2\. durch die Angabe, was es exportiert möchte und

3\. 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 sichtbar nach außen. Alles, was Außenstehende sehen sollen, muss in der Modulbeschreibung aufgeführt sein – nicht alle öffentlichen Typen des Moduls sind standardmäßig öffentlich, dann 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, 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.

Beispiel: Liste die ca. 70 Module auf:

$ **java –list-modules**

`java.activation`@9

`java.base`@9

`java.compiler`@9

…

oracle.desktop@9

oracle.net@9

Im Ordner C:\\Program `Files`\\Java\\jdk-9\\jmods liegen JMOD-Dateien.

### Plattform-Module und JMOD-Beispiel

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

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

`java.sql`@9

exports `java.sql`

exports `javax.sql`

exports `javax.transaction.xa`

requires `java.base` mandated

requires `java.logging` transitive

requires `java.xml` transitive

uses `java.sql.Driver`

platform windows-amd64

Wir können ablesen:

- den Namen
- die Pakete, die das Modul exportiert: `java.sql`, `javax.sql` und `javax.transation.xa`
- die Module, die `java.sql` benötigt: `java.base` ist hier immer drin, dazu kommen `java.logging` und `java.xml`

Die Meldung mit „uses“ steht im Zusammenhang mit dem Service-Locator – wir können das vorerst ignorieren. Die Kennung über die Plattform (windows-amd64) schreibt jmod mit hinein, es ist die Belegung der System-Property os.arch auf dem Build-Server.

### Verbotene Plattformeigenschaften nutzen, –add-exports

Als Sun von vielen Jahren mit der Entwicklung der Java-Bibliotheken begann, kamen viele interne 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 war die Neugierde und das Interesse so groß, dass die Warnungen von Sun/Oracle ignoriert wurden. In Java 9 kommt der große Knall, da public 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. Es kommt zu einem Compilerfehler, wie in folgendem Beispiel:

```java
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. In der main(`String`\[\] args)-Methode sind in args keine VM-Argumente enthalten.

Übersetzen wir das Programm, gibt es einen Compilerfehler (nicht bei einem Java 8-Compiler):

$ **javac `ShowRuntimeArguments`.java**

`ShowRuntimeArguments`.java:3: error: package jdk.internal.misc is not visible

```java
System.out.println( java.util.Arrays.toString( 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

The module `java.base` does not export the package jdk.internal.misc., so the type jdk.internal.misc.Unsafe is not accessible – as a consequence compilation fails.

Das Problem dokumentiert der Compiler. Es ist dadurch zu lösen, indem mit dem Schalter –add-exports aus dem Modul `java.base` das Paket jdk.internal.misc unserer Klasse bereitgestellt wird. Die Angabe ist für den Compiler und für die Laufzeitumgebung zu setzen:

$ **javac –add-exports java.base/jdk.internal.misc=ALL-UNNAMED `ShowRuntimeArguments`.java**

$ **java `ShowRuntimeArguments`**

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

at `ShowRuntimeArguments`.main(`ShowRuntimeArguments`.java:3)

$ **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, welches die Typen in jdk.internal.misc sehen kann, oder – wie in unserem Fall – ALL-UNNAMED.

### jdeps

Hätten wir das Programm schon erfolgreich unter Java 8 ü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 `ShowRuntimeArguments`.class

`ShowRuntimeArguments`.class -> `java.base`

<unnamed> -> `java.io` `java.base`

<unnamed> -> `java.lang` `java.base`

<unnamed> -> `java.util` `java.base`

<unnamed> -> jdk.internal.misc JDK internal API (`java.base`)

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 Entwicker können proaktiv den Stellen auf den Grund gehen, die problematische Abhängigkeiten haben.

### Plattformmodule einbinden, –add-modules und –add-opens

Jedes Java SE-Projekt basiert auf dem Modul `java.se`, was diverse Modulabhängigkeiten nach sich zieht.

```
<pic: java.se-graph.png, "Modulabhängigkeiten von java.se">
```

Diverse Module sind nicht Teil vom Modul Java SE, unter anderem sind das das Java Activation Framework, CORBA, Transaction-API, JAXB, Web-Services und interne Module, die mit jdk beginnen.

Starten wir ein Programm mit Bezug zu einem dieser Bibliotheken gibt es einen Fehler. Zunächst zum Programm, das ein Objekt automatisch in XML konvertieren soll:

```java
public class Person {

public String name = "Chris";

public static void main( String[] args ) {

javax.xml.bind.JAXB.marshal( new Person(), System.out );

}

}
```

Ausgeführt auf der Kommandozeile folgt ein Fehler:

$ **java Person**

```
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXB
```

at Person.main(Person.java:6)

Caused by: `java.lang.ClassNotFoundException`: `javax.xml.bind.JAXB`

at java.base/jdk.`internal.loader.BuiltinClassLoader.loadClass(Unknown Source)`

at java.base/jdk.`internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)`

at java.base/java.`lang.ClassLoader.loadClass(Unknown Source)`

… 1 more

Wir müssen das Modul `java.xml.bind` (oder auch das „Über“-Modul `java.se.ee`) mit angeben; dafür dient der Schalter –add-modules.

$ **java –add-modules `java.xml.bind` Person**

```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<person>

<name>Chris</name>

</person>
```

### Öffnen für Reflection

Jetzt gibt es allerdings ein anderes Problem, was auffällt, wenn wir einen anderen Typ in XML umwandeln wollen:

```java
public class Today {

public static void main( String[] args ) {

javax.xml.bind.JAXB.marshal( new java.util.Date(), System.out );

}

}
```

Auf der Kommandozeile zeigt sich:

$ **java –add-modules `java.xml.bind` `Today`**

`Exception` in thread "main" `javax.xml.bind.DataBindingException`: `javax.xml.bind.JAXBException`: Package `java.util` with JAXB class `java.util.Date` defined in a module `java.base` must be open to at least `java.xml.bind` module.

at `java.xml.bind`@9/javax.xml.bind.JAXB.`\_marshal(Unknown Source)`

at `java.xml.bind`@9/javax.`xml.bind.JAXB.marshal(Unknown Source)`

at `Today`.main(`Today`.java:4)

Caused by: `javax.xml.bind.JAXBException`: Package `java.util` with JAXB class `java.util.Date` defined in a module `java.base` must be open to at least `java.xml.bind` module.

at `java.xml.bind`@9/javax.`xml.bind.ModuleUtil.delegateAddOpensToImplModule(Unknown Source)`

at `java.xml.bind`@9/javax.`xml.bind.ContextFinder.newInstance(Unknown Source)`

at `java.xml.bind`@9/javax.`xml.bind.ContextFinder.newInstance(Unknown Source)`

…

Die zentrale Information ist „Package `java.util` with JAXB class `java.util.Date` defined in a module `java.base` must be open to at least `java.xml.bind` module“. Wir müssen etwas öffnen; dazu verwenden wir den Schalter –add-opens:

$ **java –add-modules `java.xml.bind` –add-opens java.base/java.util=`java.xml.bind` Box**

```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<date>2017-09-02T23:20:52.170+02:00</date>
```

Die Option öffnet für Reflection das Paket `java.util` aus dem Modul `java.base` für `java.xml.bind`. Neben –add-opens gibt es das ähnliche –add-exports, was alle öffentlichen Typen und Eigenschaften zur Übersetzungs-/Laufzeit öffnet; –add-opens geht für Reflection einen Schritt weiter.

### Projektabhängigkeiten in Eclipse

Um Module praktisch umzusetzen wollen wir in Eclipse zwei neue Java-Projekte aufbauen: „`com.tutego.greeter`“ und „`com.tutego.main`“. Wir legen in das Projekt „`com.tutego.greeter`“ eine Klasse `com.tutego.insel.greeter.Greeter` an und in „`com.tutego.main`“ die Klasse `com.tutego.insel.main.Main`.

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 auf das Projekt „`com.tutego.main`“ und rufen im Kontextmenü Project auf; alternativ im Menüpunkt Project > Properties oder über die Tastenkombination Alt + Return. Im Dialog navigiere links auf Java Build Path und aktiviere den Reiter Projects. Wähle Add… und im Dialog wähle aus der Liste `com.tutego.greeter`. Ok schließt den kleinen Dialog und unter Required projects in build path taucht eine Abhängigkeit auf.

Wir können jetzt zwei einfache Klassen implementieren. Zunächst für das Projekt „`com.tutego.greeter`“:

com/tutego/insel/greeter/Greeter.java

```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 );

}

}
```

Und die Hauptklasse im Projekt „`com.tutego.main`“:

com/tutego/insel/main/Main

```java
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.

### Benannte Module und `module-info.java`

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 ist, beginnt die Laufzeitumgung das Projekt als Modul zu interpretieren.

Testen wir das, indem wir in unsere Projekte „`com.tutego.greeter`“ und „`com.tutego.main`“ eine Modulinfodatei anlegen. Das kann Eclipse über das Kontextmenü Configue > Create `module-info.java` für uns machen.

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

`module-info.java`

/\*\*

\*

\*/

/\*\*

\* @author Christian

\*

\*/

```java
module com.tutego.greeter {

exports com.tutego.insel.greeter;

requires java.base;

}
```

Und für die zweite Modulinfodatei – Kommentare ausgeblendet:

`module-info.java`

```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.\[\[1\]\]() Es folgt ein Block in geschweiften Klammern.

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

- Das Projekt/Modul `com.tutego.greeter` exportiert 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 required 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`

```java
// requires java.base;
```

Auskommentieren führt zu keiner Änderung, da `java.base` immer required wird

`com.tutego.greeter`

```java
// exports com.tutego.insel.greeter;
```

Compilerfehler im main-Modul “The type `com.tutego.insel.greeter.Greeter` is not accessible”

`com.tutego.greeter`

```java
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`

```java
exports com.tutego.insel.closer;
```

Hinzufügen führt zum Compilerfehler “The package `com.tutego.insel.closer` does not exist or is empty”

`com.tutego.main`

```java
// requires com.tutego.greeter;
```

Compilerfehler “The import `com.tutego.insel.greeter` cannot be resolved”

`com.tutego.main`

```java
// exports com.tutego.insel.main;
```

Keins, denn c.t.i.m wird von keinem Modul required

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

### Übersetzen und Packen von der Kommandozeile

Setzen wir in der Wurzelverzeichnis vom Modul `com.tutego.greeter` ein Batch-Skript compile.bat:

compile.bat

set PATH=%PATH%;C:\\Program `Files`\\Java\\jdk-9\\bin

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

Folgende Schritte führt das Skript aus:

1\. Setzen der PATH-Variable für die JDK-Tools

2\. Löschen eines vielleicht schon angelegten lib-Ordners

3\. Anlegen eines neuen lib-Orders für die JAR-Datei

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

5\. 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 <https://docs.oracle.com/javase/9/tools/jar.htm>.

6\. 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.

compile.bat

set PATH=%PATH%;C:\\Program `Files`\\Java\\jdk-9\\bin

rmdir /s /q lib

mkdir lib

javac -d bin –module-path ..\\`com.tutego.greeter`\\lib src\\`module-info.java` src\\com\\tutego\\insel\\main\\`Main`.java

jar -c -f=lib/com.tutego.main@1.0.jar –main-class=`com.tutego.insel.main.Main` –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-Order mit den JAR-Dateien an und mit –m das sogenannte initiale Modul für die Hauptklasse.

### Automatische Module

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 <https://mvnrepository.com/repos> offenbart über 7,7 Millionen Artefakte; es gehen auch Dokumentationen und andere Dateien in die Statistik ein, doch es gibt eine Größenordnung, wie viele JAR-Dateien im Umlauf sind.

Damit JAR-Dateien unter Java 9 eingebracht werden können gibt es zwei Lösungen: das JAR in den Klassenpfad oder in den neuen Modulpfad zu 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 selbstgewählten Namen, sondern sie bekommen vom System einen Namen zugewiesen, der sich aus dem Dateinamen ergibt.\[\[2\]\]() Vereinfacht gesagt: Angehängte Versionsnummern und die Dateiendung werden entfernt und alle nicht-alphanummerischen Zeichen durch Punkte ersetzt, jedoch nicht zwei Punkte hintereinander.\[\[3\]\]() 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 in Richtung Java 9 einfach: Alle JARs auf den Modulpfad und nacheinander Modulinfodateien anlegen. Allerdings gibt es JAR-Dateien, die von der JVM als automatisches Modul abgelehnt werden, wenn sie nämlich Typen eines Paketes enthalten, und dieses Paket sich schon in einem anderen aufgenommen Modul befindet. Module dürfen keine „split packages“ enthalten, also das gleiche Paket noch einmal enthalten. Die Migration erfordert dann entweder a) das Zusammenlegen der Pakete zu einem Modul, b) die Verschiebung in unterschiedliche Pakete oder c) die Nutzung des Klassenpfades

### Unbenanntes Modul

Eine Migration auf eine neue Java-Version sieht in der Regel so aus, dass zuerst die JVM gewechselt und geprüft wird, ob die vorhandene Software weiterhin funktioniert. Laufen die Testfälle durch und gibt es 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.

Übertragen wir das auf den Wechsel von Java 8 auf Java 9: Eine vorhandene Java-Software muss inklusive aller Einstellungen und Einträge im Klassenpfad weiterhin laufen. Das heißt, eine Java 9 Laufzeitumgebung 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 (eng. 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.

### Lesbarkeit und Zugreifbarkeit

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

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

Die nächste Frage ist, welcher Modultyp auf welchen anderen Modultyp Zugriff hat. Eine Tabelle fasst die Lesbarkeit am Besten 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

### Lesbarkeit der Module

Der Modulinfodatei kommt dabei die größte Bedeutung zu, denn sie macht aus einer JAR ein modular-JAR; fehlt die Modulinfomation bleibt es eine normale JAR, wie sie Java-Entwickler 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 Modulinfomation

wird benanntes Module

wird ubenanntes Modul

JAR ohne Modulinfomation

wird automatisches Modul

wird unbenanntes Modul

JARs im Pfad

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

### Modul-Migration

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 Module 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 z. B. die beliebte Open-Source Bibliothek Google Guava. Die JAR-Datei hat den Dateinamen guava-23.0.jar – guava heißt folglich das automatische Modul. 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 vom Modulnamen 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.

**Beispiel: Apache Commons setzt den Namen so:**

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

Benannte Module, die Abhängigkeiten auf automatische Module 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. Im Monat vom Java 9-Release hat noch keine wichtige Java-Bibliothek eine Modulinformation, Automatic-Module-Name kommt häufiger vor.

---

\[\[1\]\]() Zur Benennung von Modulen gibt es Empfehlungen in dem englischsprachigen Beitrag <http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2017-May/000687.html>

\[\[2\]\]() Automatic-Module-Name in die META-INF-Datei zu setzen ist eine Alternative, dazu später mehr.

\[\[3\]\]() Für Details siehe <http://download.java.net/java/jigsaw/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path>…-

Kategorien: [Insel](https://tutego.de/blog/javainsel/category/insel/), [Java 9](https://tutego.de/blog/javainsel/category/java-9/)

[← Alle Beiträge](https://tutego.de/blog/javainsel/)

---

## Navigation

- Previous: [JMOD-Dateien und JAR-Dateien](https://tutego.de/blog/javainsel/2017/10/jmod-dateien-und-jar-dateien.md)
- Next: [Inselraus: Ein LowerCaseWriter](https://tutego.de/blog/javainsel/2017/10/inselraus-ein-lowercasewriter.md)
