Hacking Jigsaw! – Die Kapselung von Java 9 Modulen brechen

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

Abb.1: Struktur des gesamten Projektes.
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.
Abb.2: Inhalte der vier beteiligten Java-Dateien.
Hier kommt nun die Kommandozeilenoption --add-exports ins Spiel. Das Projekt lässt sich mit folgendem Kommando kompilieren:
javac -d classes --module-source-path src $(dir src -r -i "*.java") 
 --add-exports de.ordix.modul2/de.ordix.modul2.pck2=de.ordix.modul1 
Um die Applikation nun zu starten, muss ebenfalls ein --add-exports hinzugefügt werden:
java --add-exports de.ordix.modul2/de.ordix.modul2.pck2=de.ordix.modul1 
 -p classes -m de.ordix.modul1/de.ordix.modul1.pck1.AbhaengigeKlasse 
In der Konsole ist die Ausgabe „GekapselteKlasse aus module de.ordix.modul2" zu sehen (Abb. 3).
Abb.3: Zugriff auf die geschütze Klasse wurde ermöglicht.
Ohne die zusätzliche Option würde die AbhaengigeKlasse die GekapselteKlasse nicht finden können und es würde eine ClassNotFoundException geworfen (Abb. 4).
Abb.4: ClassNotFoundException
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.

Abb.5: Neuer Inhalt der beiden beteiligten Java-Klassen.
Die beiden Module werden mit folgender Anweisung kompiliert:
javac -d classes --module-source-path src $(dir src -r -i "*.java") 
 --add-exports de.ordix.modul2/de.ordix.modul2.pck2=de.ordix.modul1 
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:
java --add-exports de.ordix.modul2/de.ordix.modul2.pck2=de.ordix.modul1 
 --add-opens de.ordix.modul2/de.ordix.modul2.pck2=de.ordix.modul1 
 -p classes -m de.ordix.modul1/de.ordix.modul1.pck1.AbhaengigeKlasse 
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.
0
Service Provider & Consumer - lose gekoppelt ein s...
Die neue ORDIX® news 1/2018 erscheint bald! Hier ...

Related Posts

Unsere Autoren

Technologie Blogs

Tutorials

4 members

Webentwicklung

3 members

Java

3 members

Archiv | Blog-Beiträge

Login