Seit Java 9 lassen sich mehrere Pakete und die darin enthaltenen Klassen zu Modulen zusammenfassen. Dies ermöglicht eine bessere Kontrolle, auf welche Klassen von außerhalb des Modules zugegriffen werden darf. Ein Modul wird durch einen sogenannten Modul-Deskriptor definiert, der sich auf oberster Paketebene im Modul befindet und per Definition „module-info.java" heißt. Der Modul-Deskriptor wird zusammen mit den Klassen kompiliert. Als Ergebnis erhält man die Datei module-info.class. Das bedeutet, dass der Modul-Deskriptor zur Compile-Zeit vollständig vorhanden sein muss. Doch was ist, wenn man diesen Modul-Deskriptor einmal erweitern möchte?
Wozu die Modulare Kapselung umgehen?
Es gibt Fälle, in denen man Zugriff auf geschützte Klassen eines Moduls benötigt. Das kann zum Beispiel der Fall sein, wenn man White-Box-Tests in einem Modul durchführen will. Ein anderes Szenario könnte sein, dass bestehender Code abhängig von einer alten API ist, aber diese API weiterhin benutzt werden soll. Mit der Kommandozeilenoption --add-exports kann sowohl zur Compile-Zeit als auch zur Laufzeit eine geschützte Klasse für andere Module freigegeben werden.
Das nachfolgende Beispiel veranschaulicht den Gebrauch von --add-exports.
Zugriff auf eine geschützte Klasse
In Abb. 1 wird die Struktur des src Ordners angezeigt. In Abb. 2 ist der Inhalt der Java-Dateien einzusehen. Der Ordner src enthält zwei Module
de.ordix.modul1 und
de.ordix.modul2. Der Modul-Deskriptor von
de.ordix.modul1 enthält die Abhängigkeit
requires de.ordix.modul2. Während der Modul-Deskriptor von
de.ordix.modul2 lediglich den Modulnamen beinhaltet und keine Pakete exportiert. Die Klasse
AbhaengigeKlasse greift aber auf
GekapselteKlasse zu, daher wird es beim Kompilieren zu einem Fehler kommen.
Hier kommt nun die Kommandozeilenoption
--add-exports ins Spiel. Das Projekt lässt sich mit folgendem Kommando kompilieren: Um die Applikation nun zu starten, muss ebenfalls ein
--add-exports hinzugefügt werden: In der Konsole ist die Ausgabe „GekapselteKlasse aus module de.ordix.modul2" zu sehen (Abb. 3).
Ohne die zusätzliche Option würde die
AbhaengigeKlasse die
GekapselteKlasse nicht finden können und es würde eine
ClassNotFoundException geworfen (Abb. 4).
Als Ziel-Modul muss nicht zwangsläufig ein konkretes Modul angegeben werden. Mit
ALL-UNNAMED kann ein Package für alle sogenannten Unnamed-Module freigeben werden. Dabei handelt es sich um alle Inhalte des Classpaths, dieser wird automatisch als Unnamed-Modul zusammengeführt.
Zugriff auf wirklich alles!
Mit der --add-exports Option kann der Zugriff auf alle öffentlichen Inhalte gewährt werden. Der Zugriff auf private Inhalte wird weiterhin verweigert. Diese privaten Inhalte können per Reflection und der Option --add-opens trotzdem zugänglich gemacht werden. Das Beispiel von oben wird ein wenig verändert. Die GekapselteKlassse wird um eine private Methode zeigeGeheimnis() erweitert. Die AbhaengigeKlasse versucht per Reflection darauf zu zugreifen. Die beiden Modul-Deskriptoren bleiben unverändert. Das bedeutet das Modul de.ordix.modul2 macht sein Package nicht öffentlich. In Abb. 5 ist der Inhalt der beiden geänderten Klassen einzusehen.
Die beiden Module werden mit folgender Anweisung kompiliert: Es ist zu beachten, dass hier nur das Package exportiert werden muss. Daher hat sich im Vergleich zum vorherigen Beispiel bei noch nichts verändert. Die Kommandozeilenoption
–add-opens wird erst bei der Ausführung durch den folgenden Befehl verwendet:
Als Ergebnis ist die Ausgabe „Ich bin ein Geheimnis!" in der Konsole zu sehen. Das heißt, aus Modul 1 konnte eine private Methode in Modul 2 aufgerufen werden, ohne dass Modul 2 irgendwelche Packages exportiert oder öffnet!
Aber Vorsicht! Auf diese Art und Weise können die privaten internen Inhalte einer API zwar zugänglich gemacht werden, aber wenn sich die API intern einmal ändern sollte, dann besteht die Gefahr, dass die eigene Anwendung plötzlich nicht mehr funktioniert
. Denn das ist genau der Grund, warum Jigsaw eingeführt wurde.