|
|||||||||||||||||||
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | | | | |||||||||||||||||||
|
Effective Java
|
||||||||||||||||||
Project Jigsaw aka JPMS (Java Platform Module System)
Mit dem Release 9 bekommt Java nun endlich
das schon lange angekündigte Modul-System (siehe /
MOSY
/).
Oracle hat bereits vor ca. 5 Jahren damit begonnen, die Modularisierung
von Java-Anwendungen zu unterstützen. In diesem Zuge wurde zunächst einmal
der JDK selbst restrukturiert und in Module aufgeteilt. Die Modularisierung
des JDK ist nun fertig und damit hat sich das Deployment des JDK geändert:
es gibt in Java 9 nun kein
rt.jar
mehr,
sondern der JDK besteht jetzt aus Modulen. Diesem Beispiel soll die Java--Community
folgen. Die Idee ist, dass mit Java 9 eine Transitionsphase beginnt,
in der insbesondere Third-Party-Provider und schließlich alle Java-Anwendungen
ihre Java-Software modularisieren.
Was ist ein Modul?
Module sind Artefakte, die eine Einheit
von miteinander zusammenhängenden Klassen, native Code, Properties, etc.
bilden. Zu jedem Modul gehören Metadaten, die den Modul und seine Beziehungen
zu anderen Modulen beschreiben. Das Hauptziel der Modularisierung ist
die Kapselung von zusammenhängenden Klassen und die saubere Beschreibung
der Beziehungen zwischen den Modulen.
Ein Modul in Java beschreibt: - Readability : welche anderen Module dieser Modul benutzt - Accessibility : welche Packages dieser Modul anderen Modulen zur Verfügung stellt. Der Zweck des Modul-Systems ist es, diese Beziehungen der Module untereinander von der Übersetzung bis zur Laufzeit zu überprüfen und sicher zu stellen. Wozu braucht Java ein Modul-System?
Die Möglichkeit, zusammenhängende Klassen
zusammen zu fassen, gibt es bereits mit dem Java Archiven, d.h. den
.jar
-Dateien.
Außerdem gibt es in Java Kapselungsmechanismen mit Hilfe von Packages
und den Visibility-Deklarationen über
private
,
protected
und
public
. Wozu also braucht es Modul-System
für Java? Schauen wir uns einige der Gründe an, die dazu geführt haben,
dass ein Modul-System für Java definiert wurde.
Die Visibility-Deklarationen beispielsweise
haben sich als unzureichend heraus gestellt. Ein gutes Beispiel für
die Defizite sind die JDK-Klassen in den
sun.*
-Packages
wie zum Beispiel die Klasse
sun.misc.Unsafe
.
Alle Klassen in den
sun.*
-Packages waren
ursprünglich als interne Klassen für die Implementierung des JDK gedacht
und sollten auf keinen Fall allen beliebigen Java-Benutzern zur Verfügung
stehen. Solche internen Klassen deklariert man mit den traditionellen
Mitteln als package-visible und legt sie zusammen mit den Klassen, die
sie benutzen dürfen, in ein gemeinsames Package. Nun werden die internen
sun.*-
Klassen
aber von so vielen JDK-Klassen verwendet, dass man riesengroße Packages
bekommen hätte. Das wollte man natürlich auch nicht. Also hat man
Klassen wie
sun.misc.Unsafe
notgedrungen
public
gemacht und sich andere Schutzmechanismen überlegt, um sie der allgemeinen
Benutzung zu entziehen. Aber all diese Schutzmechanismen lassen sich
aushebeln, so dass Klassen wie
sun.misc.Undafe
heute de facto keine internen JDK-Klassen mehr sind, sondern in allen möglichen
Anwendungen und Bibliotheken benutzt werden, die sie eigentlich gar nicht
benutzen dürfen. Mit anderen Worten, die traditionellen Kapselungsmechanismen
bieten keinen ausreichenden Schutz. Das wird sich mit dem Modul-System
ändern.
Ein anderes Problem mit den traditionellen
Mechanismen ist die sogenannte "
jar hell
". In größeren Applikationen
kommt es immer wieder vor, dass eine bestimmte Third-Party-Bibliothek von
verschiedenen Teilen der Applikation in verschiedenen Versionen benötigt
wird. Nun kann man zwar die jar-Dateien für die beiden Versionen der
Bibliothek auf den Classpath legen, aber es wird beim Classloading später
die Klasse aus der jar-Datei genommen, die als erste auf dem Classpath
gefunden wurde. Es ist mit den traditionellen Mitteln nicht möglich
zu spezifizieren, welche Komponenten in welcher Version von wem gebraucht
werden. Auch das wird sich mit den Modulen ändern.
Ein anderes Problem ist die Größe der Anwendungen.
Der JDK zum Beispiel ist von Release zu Release immer größer geworden
Mit den traditionellen Mechanismen gibt es jedoch keine Möglichkeit, partielle
JDK-Deployments zu machen.
Diese und andere Gründe haben dazu geführt, dass ein Modul-System für Java mit besseren Kapselungsmechnismen für erforderlich gehalten wurde. Der modularisierte JDK
Als erstes hat man bei Oracle den JDK reorganisiert
und in Module zerlegt. Dafür hat man ca. 5 Jahre gebraucht und der Graph
der Modulbeziehungen sieht in vereinfachter Form nun so aus, wie unten
gezeigt. Die tatsächlichen Beziehungen sind noch deutlich komplexer;
man kann sich das komplette Diagramm im JEP 200: "The Modular JDK" anschauen
(siehe /
MJDK
/).
Copyright © 2015, Oracle and/or its affiliates. All rights reserved.
Abb. 1: Ausschnitt aus den Plattform-Modulen
des JDK 9
Welche Auswirkungen hat die Modularisierung
des JDK für die Java-Entwickler? Im günstigsten Falle merken wir als
Entwickler fast gar nichts davon und unsere Anwendung läuft mit dem modularisierten
JDK 9 genauso wie mit dem alten JDK 8.
Es kann aber auch sein, dass beim Umstieg auf Java 9 Anpassungen an der eigenen Applikation gemacht werden müssen. Das ist beispielsweise in folgenden Fällen nötig: - Die Anwendung verwendet JDK-interne APIs aus den sun.* -Packages. Diese internen Klassen sind im modularisierten JDK nun wirklich intern und nicht mehr zugänglich. - Die Anwendung ist in irgendeiner Form abhängig vom Deployment des JDK oder des JRE, z.B. wenn direkt auf die rt.jar -Datei zugegriffen wird. Diese Datei gibt es nicht mehr. - Die Anwendung wertet den Java-Version-String aus. Das wird wahrscheinlich nicht mehr funktionieren, denn die Syntax der Versionsbezeichnungen hat sich geändert. - Die Anwendung verwendet sogenannte " Split Packages ". Das kommt vor, wenn die Anwendung APIs benutzt, die in einer Third-Party-Software nicht public , sondern nur package-visible sind. Um an die package-visible APIs heran zu kommen, muss man die benutzenden eigenen Klassen in dem betreffenden fremden Package definieren. So etwas geht mit Modulen nicht mehr. - usw. Die Liste ist nicht vollständig. Es gibt noch mehr Szenarien, in denen Anpassungen der Applikation an den modularisierten JDK 9 erforderlich sein können. Abhängigkeiten zu JDK-internen APIs aus den sun.*-Packages
Ob die eigene Anwendung
interne APIs verwendet, kann man mit dem
Java Class Dependency Analyzer
ü
berprüfen.
Dieses
jdeps
-Werkzeug gibt es schon seit
Java 8 als Hilfe bei der Modularisierung der eigenen Anwendungen: es analysiert
die Abhängigkeiten der Packages voneinander. Wenn man das
jdeps
-Tool
mit der Option
-jdkinternals
aufruft,
dann sucht es auch nach der Verwendung von JDK-internen APIs und meldet
sie. Die Meldung sieht dann z.B. so aus:
......
-> sun.security.x509.X500Name JDK internal
API (java.base)
Warning: JDK internal APIs are unsupported and private
to JDK implementation that are
subject to be removed or changed incompatibly and
could break your application.
Please modify your code to eliminate dependency on any JDK internal APIs. For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
JDK Internal API Suggested Replacement ---------------- ---------------------
sun.security.x509.X500Name Use javax.security.auth.x500.X500Principal
@since 1.4
Man bekommt auch gleich Hinweise auf mögliche
alternative APIs, die man anstelle der internen JDK APIs verwenden soll.
Wenn man solche Verwendungen im eigenen Code findet, dann soll man die
betreffenden Stellen entsprechend ändern, damit die Anwendung mit dem
JDK 9 funktioniert.
Nun kann es aber auch passieren, dass nicht
der eigene Code die internen JDK APIs verwendet, sondern dass benutzte
Third-Party-Komponenten solche Abhängigkeiten aufweisen. Den fremden
Code kann und will man nicht ändern. Prinzipiell sollen die Bibliotheks-
und Framework-Hersteller sukzessive ihre Komponenten modularisieren und
dabei die Abhängigkeiten zu irgendwelchen JDK internen APIs eliminieren.
Aber was macht man in der Übergangszeit?
Für diese Übergangszeit gibt es einen Workaround,
der die Kapselungsmechnismen des Modul-Systems teilweise aushebelt: den
sogenannten
Unnamed Module
. In diesem unbenannten Modul landen
alle Typen, die vom Classpath geholt werden. Dort befinden sich in Java
9 alte, nicht-modularisierte Komponenten, wie z.B. die fragliche Third-Party-Komponente
mit ihrer Abhängigkeit zu den internen JDK APIs. Für bereits modularisierte
Komponenten gibt es nämlich in Java 9 einen separaten Modulepath. Module
- wie z.B. diejenigen aus dem mdularen JDK 9 - werden auf dem Modulepath
und nicht auf dem Classpath gesucht.
Nehmen wir als Beispiel an, die alte, noch
nicht modularisierte Third-Party-Komponente verwendet die Klasse
X500Name
aus dem Package
sun.security.x509
, so
wie es das
jdeps
-Tool in dem Beispiel-Output
oben gemeldet hat. Dieses Package gehört im JDK 9 zum Modul
java.base
und dieser Modul exportiert das Package
sun.security.x509
nicht;
es ist also nicht nach außen sichtbar. Die Verwendung der Third-Party-Komponente
führt deshalb sowohl beim Übersetzen als auch beim Ablauf zu Fehlern.
Mit der
--add-exports
Option kann man
das interne Package
sun.security.x509
aus
dem JDK-Modul
java.base
in den unbenannten
Modul hinein exportieren:
...... --add-exports java.base/sun.security.x509=ALL-UNNAMED
...
Diese Option muss sowohl dem
javac
-Compiler
als auch der JVM bei Aufruf mitgegeben werden. Damit wird das interne Package
sun.security.x509
für alle Klassen im unbenannten Modul zugänglich. Da die fragliche
Third-Party-Komponente im unbenannten Modul landet, hat sie dann den gewünschten
Zugriff auf das Package
sun.security.x509
.
Dieses Vorgehen ist aber wirklich nur ein Workaround und keine langfristige Lösung, denn es ist nicht garantiert, dass es das Package sun.security.x509 im nächsten JDK überhaupt noch geben wird. Wenn es gar nicht mehr existiert, dann kann es auch nicht mehr in den Unnamed Module exportiert werden. Außerdem funktioniert der Workaround auch nur so lange, wie die Third-Party-Komponente über den Classpath geladen wird. Sobald sie modularisiert ist, wird sie vom Modulepath geladen. Dann besteht sie aus Modulen mit Namen und solche "richtigen" Module können nichts aus dem Unnamed Module verwenden. Wie definiert man einen Modul?
Nehmen wir an, wir wollen unsere Applikation
modularisieren. Zunächst müssen wir überlegen, wie wir die Packages
unserer Applikation zu Modulen zusammenfassen wollen. Anschließend müssen
wir die Module beschreiben und sie von der Übersetzung bis zum Ablauf
verwenden.
Für den Source-Code bedeutet die Modularisierung
zunächst einmal, dass es oberhalb von den Package-Directories ein Modul-Directory
gibt, in dem alle Packages und Source-Dateien liegen, die zu dem betreffenden
Modul gehören. In diesem Modul-Directory liegt zusätzlich eine Datei
mit dem Namen
module-info.java
. Sie
enthält den sogenannten
Modul-Deskriptor
. Hier ein Beispiel für
die Directory-Struktur der Sourcen:
src\myapp.mymodule\ module-info.java de\mycompany\mypackage1\ClassA.java ... de\mycompany\mypackage2\ClassB.java
...
Der
Modul-Deskriptor
wird zusammen
mit allen anderen
.java
-Dateien vom Compiler
übersetzt und es gibt nach der Übersetzung eine Datei Namen
module-info.
class
.
Der Modul-Deskriptor spezifiziert u. a. den Namen des Moduls. Zum Beispiel
könnte in der Datei
src\myapp.mymodule\module-info.java
stehen:
module myapp.mymodule { …
}
Der Modulname
myapp.mymodule
muss mit dem Namen des Modul-Directory übereinstimmen, so wie es auch
bei den Packages ist. Desweiteren enthält der Modul-Deskriptor
export
s
-Anweisungen,
in denen der Modul die Accessibility spezifiziert, d.h. welche Packages
er anderen Modulen zur Verfügung stellt. Hier ist ein Beispiel:
module myapp.mymodule { exports de.mycompany.mypackage1; ... exports de.mycompany.mypackage2 to myapp.myabmodule; …
}
Die erste
export
s
-Anweisung
macht das genannte Package allen anderen Modulen zugänglich, die es benötigen.
Die zweite, qualifizierte
export
s
-Anweisung
ist eine
qualifizierte
exports
-Anweisung
:
sie macht das betreffende Package nur einem einzigen Modul
myapp.myabmodule
zugänglich.
Die Module, an die exportiert wurde, können die exportierten Packages aber nur benutzen, wenn sie ihrerseits korrespondierende requires -Anweisungen in ihrem Modul-Deskriptor haben. Das sind die Readabilty-Spezifikationen des Moduls. Hier ist zum Beispiel der Deskritptor des Moduls myapp.myabmodule :
Last Update: 16.08.2017 , 09:29:34 Page 23
Copyright
@ 2017
by Angelika Langer & Klaus Kreft.
All
rights reserved.
Jav
a
9 -
Überblick
Klaus
Kreft & Angelika Langer
module myapp.myabmodule { ... requires myapp.mymodule; ... }
Abb. 2: Accessibility / Readability - Beziehung
Last Update: 16.08.2017 , 09:29:34 Page 23 Copyright @ 2017 by Angelika Langer & Klaus Kreft. All rights reserved.
Jav
a
9 -
Überblick
Klaus
Kreft & Angelika Langer
Man beachte, dass
requires
und
exports
asymmetrisch sind: es werden
Packages
exportiert
, aber ganze
Module importiert
.
Abb. 3: Accessibility / Readability - Beziehung
im Detail
Diese in den Modul--Deskriptoren spezifizierten
export
s
-
und
requires
-Beziehungen werden sowohl
bei der Übersetzung als auch zur Laufzeit (beim Classloading, bei der
Reflection, etc.) überprüft, so dass Klassen aus dem Module
myapp.myabmodule
nur dann Klassen aus dem Module
myapp.mymodule
verwenden können, wenn die
export
s
-
und
requires
-Spezifikationen zusammen
passen.
In der Realität wird es nicht nur bilaterale
Beziehungen zwischen genau zwei beteiligten Modulen geben, sondern es werden
deutlich mehr Module vorkommen. Betrachten wir deshalb z.B. einen dritten
Modul
myapp.myxymodule
, der von unserem
myapp.mymodule
verwendet wird.
Abb. 4: Transitive
Readability
Nehmen wir mal an, der dritte Modul exportiert
an den zweiten Modul ein Package
de.mycompany.myxypackage
.
Höchstwahrscheinlich werden Typen aus dem Package des dritten Moduls im
public API des zweiten Moduls vorkommen, wie z.B. hier im Returntyp einer
Methode:
package de.mycompany.mypackage2;
public MyClass { public de.mycompany.myxypackage.XyClass method() { … }
}
Der erste Modul
myapp.my
abmodule
verwendet die APIs aus dem zweiten Modul
myapp.mymodule
und die APIs des zweiten Moduls benutzen Typen aus dem dritten Modul
mayapp.myxymodule
.
Folglich benötigt nun auch der erste Modul den dritten Modul. Damit
der erste Modul nicht den dritten - und womöglich noch weitere Module
- in seinem Modul-Deskriptor auflisten muss, gibt es eine
transitive
requires
-Anweisung
.
Die Modul-Deskriptoren der drei Module sehen dann so aus:
module myapp.myabmodule { requires myapp.mymodule;
}
module myapp.mymodule { exports de.mycompany.mypackage1; requires transitive myapp.myxymodule;
}
module myapp.myxymodule { exports de.mycompany.myxypackage;
}
Der zweite Modul verwendet den dritten Modul per " requires transitive " und damit braucht der erste Modul den dritten Modul nicht in seinen requires -Anweisungen nennen. Modulare Anwendungen übersetzen und ausführen
Die Modularisierung der Applikation hat
Auswirkungen auf den gesamten Entwicklungsprozess von der Compilierung
bis zum Ablauf. Dafür gibt es eine Reihe neuer Flags für den Compiler,
den Archiver und die JVM. Beipielsweise gibt es den oben schon erwähnten
Modulepath und entsprechende Compiler-Optionen
-
-
module-source-path
und
-
-
module-path
.
Diese Optionen sind das Gegenstück zu den alten Optionen
-srcpath
und
-classpath
.
Beim Start der Applikation wird ebenfalls
der Modulepath angeben mit der JVM-Option
--module-path
(oder kurz
-p)
. Mit der JVM-Option
--module
(oder kurz
-m
) wird der Modul spezifiziert,
der die Klasse mit der
main()-
Methode
enthält. Neu ist auch das JVM-Flag
-Xdiag:resolver
.
Es ist das Gegenstück zum
-verbose:class
Flag und liefert Informationen darüber, wo welche Module gefunden wurden.
Das
jar
-Tool
ist erweitert worden, so dass man damit modulare JAR-Dateien erzeugen kann.
Man packt den Modul-Deskriptor, d.h. die Datei
module-info.class
,
zusammen mit allen zugehörigen Klassen und Ressourcen in eine solche modulare
JAR-Datei. Das heißt, jeder Modul entspricht einer modularen JAR-Datei.
Beim Erzeugen der JAR-Datei kann man mit der Option
--main-class
(oder kurz
-e
) die Klasse spezifizieren,
die die Klasse mit der
main()-
Methode
enthält, falls es eine ausführbare, modulare JAR-Datei sein soll.
Mit dem neuen
jlink
-Tool
kann man einen Modul und alles, was er braucht (d.h. seine transitiven
Abhängigkeiten) zu einem sogenannten
Modular Runtime Image
zusammenfassen.
Das Resultat dieses Linkings ist keine ausführbare Datei, wie sie z.B.
Linker in Sprachen wir C oder C++ erzeugen. Vielmehr ist ein Modular
Runtime Image eine Directory-Struktur ähnlich der des JDK oder JRE.
Darin sind alle Module und Ressourcen abgelegt, von denen der Root-Module
abhängt. Mit diesem Werkzeug kann man Deployments machen, in denen wirklich
nur das enthalten ist, was gebraucht wird. Da ist dann u.U. nicht das
gesamte JRE dabei, sondern möglicherweise nur ein Subset der JRE-Module,
wenn die Applikation nicht alles aus dem JRE braucht.
Die Migrationsphase
Mit der Einführung des Modul-Systems ist
beabsichtigt, dass langfristig alle Java-Anwendungen modular sein sollen.
Oracle ist voran gegangen und hat den JDK für Java 9 bereits modularisiert.
Populäre Bibliotheken und Frameworks werden wahrscheinlich dem Beispiel
von Oracle folgen und irgendwann in modularer Form vorliegen. Für die
eigenen Anwendungen ist man ebenfalls aufgefordert, diese zu modularisieren.
Wenn man überlegt, dass die Modularisierung des JDK etwa fünf Jahre lang
gedauert hat, dann ist absehbar, dass es eine längere Transitionsphase
geben wird, während der nicht alle Komponenten gleichzeitig in modularer
Form vorliegen werden. Man wird in dieser Migrationsphase mit einem Mix
von traditionellen und modularen Komponenten umgehen müssen. Was ist
dafür zu beachten?
Im Moment ist nur der JDK modularisiert:
Abb. 5: Nicht-modulare Anwendung mit modularem
JDK 9
Wie oben beschrieben, kann man nun das
jdeps
-Tool
hernehmen, sich die Abhängigkeiten zwischen den Packages der eigenen Applikation
ansehen, ein Konzept für die Aufteilung in Module machen, die Modul-Deskriptoren
schreiben und am Ende ist die eigene Anwendung modularisiert. Möglicherweise
liegen aber die benutzten Third-Party-Bibliotheken oder Framework noch
nicht in modularer Form vor.
Abb. 6: Modulare Anwendung mit nicht-modularen
Komponenten
Dann hat man das Problem, dass man in den
eigenen Modul-Deskriptoren die Abhängigkeiten zu den nicht-modularen Third-Party-Bibliotheken
nicht beschreiben kann. Man kann nur "
requires
<module>
" sagen und wenn die Bibliothek nicht modular ist, dann
gibt es keine Module, von denen man lesen könnte.
Die Lösung für dieses Problem sind die
sogenannten
Automatic Modules
. Sie entstehen, wenn die JAR-Dateien
von nicht-modularen Komponenten auf den Modulepath gelegt werden. Das
Modul-System erzeugt für jede nicht-modulare JAR-Datei auf dem Modulepath
einen Modul, dessen Name aus dem Namen der JAR-Datei abgeleitet wird.
Diese automatischen Module haben dann einen Namen, der in den
requires
-Anweisungen
der eigenen Modul-Deskriptoren verwendet werden kann. Das ist möglicherweise
nicht der Name, den die Third-Party-Bibliothek in ihrer finalen, modularen
Form verwenden wird, so dass man die eigenen Modul-Deskriptoren u.U. später
noch einmal anpassen muss. Aber für die Übergangszeit kann man mit
den automatischen Modulen arbeiten.
Automatische Module haben die besondere Eigenschaft,
dass sie implizit alle ihre Packages exportieren und alle anderen Module
benötigen. Man kann also in der eigenen modularen Applikation alles
aus der Third-Party-Bibliothek benutzen. Gleichzeitig hat ein automatischer
Modul implizite
requires
-Beziehungen
zu sämtlichen anderen Modulen, inklusive dem unbenannten Modul.
Abb. 7: Modulare Anwendung mit automatischen
und unbenannten Modulen
Nach der Migrationsphase sollten alle Komponenten modular sein, so dass es den unbenannten und die automatischen Module nicht mehr geben sollte. Die Verwendung von automatischen und unbenannten Modules ist lediglich für die Übergangsphase gedacht. Vorbehalte gegen das Modul-System
Versuchen wir abschließend, das Modul-System
einzuordnen und zu bewerten. Es ist lange und heftig diskutiert worden
und es gibt eine Reihe von Kritikpunkten. Schauen wir uns einige davon
an.
Kritikpunkt #1:
public
bedeutet nicht mehr
public
.
Bislang war es so, dass man einen Typ, der
in einem Package
public
deklariert war,
in allen anderen Packages benutzen konnte. Man konnte an der
public
-Deklaration
sehen, dass etwas allgemein verfügbar ist. Das ist in Java 9 nicht mehr
so.
Mit dem Modul-System gibt es nun zwei Stellen,
an denen Aussagen über die Verfügbarkeit von Typen, Methoden, etc. gemacht
werden: es gibt immer noch die alten Deklarationen als
private
,
protected
,
package-visible und
public
und dazu gibt
es die
exports
- und
requires
-Anweisungen
in den Modul-Deskriptoren. Wenn eine Klasse
public
deklariert ist, dann heißt es noch lange nicht, dass sie in einem anderen
Modul verwendet werden kann. Nur wenn der Modul, der die Klasse verwenden
will, eine
requires
-Anweisung auf den
Modul hat, zu dem das Package der Klasse gehört, und es eine umgekehrte
exports
-Anweisung
gibt, kann die
public
-deklarierte Klasse
benutzt werden. Man muss also an mehreren Stellen nachschauen, um heraus
zu finden, welche Teile welcher APIs wem zugänglich sind. Das ist verwirrend
und unübersichtlich und trägt nicht unbedingt zur Vereinfachung der Zugriffskontrolle
bei.
Kritikpunkt #2: Erheblicher Aufwand.
Es gibt Bedenken den Aufwand betreffend,
den es kosten wird, größere Anwendungen zu modularisieren. Die Modularisierung
ist an sich keine triviale Aufgabe, weil es eine projektweite Angelegenheit
ist, die die Struktur der gesamten Anwendung betrifft. Wie aufwändig
es sein kann, hat man an der Modularisierung des JDK gesehen: es hat fünf
Jahre gedauert. Wie viel Aufwand es für die eigene Anwendung sein wird,
hängt davon ab, ob JDK-interne APIs aus den
sun.*
-packages
verwendet werden, ob Split Package vorkommen, ob direkt auf das Deployment
des JDK (z.B. auf die Datei
rt.jar
) zugegriffen
wird, etc. Eine gut strukturierte Anwendung, die keine JDK-Interna
benutzt, lässt sich leichter modularisieren als eine, die jeden denkbaren
Trick und Kniff ausnutzt.
Kritikpunkt #3: Schwer lesbare Modul-Deskriptoren.
Für die
exports
-Direktiven
gibt es keine Package-Wildcards, d.h. man muss alle Packages, die exportiert
werden sollen, einzeln in der
module-info.java
-Datei
auflisten. Das könnte zu umfangreichen, schwer lesbaren Modul-Deskriptoren
führen. Man kann sich zwar durch das
jdeps
-Werkzeug
mit der Option
--generate-module-info
aus einer nicht-modularen JAR-Datei eine erste Vorlage für den Modul-Deskriptor
generieren lassen, in dem alle Packages explizit exportiert werden. Damit
spart man am Anfang eine Menge Tipparbeit. Aber bei jedem späteren Refactoring
muss u. U. auch der Modul-Deskriptor angepasst werden, was je nach Komplexität
der Module-Deskriptoren mühselig und fehleranfällig sein könnte. Wie
weit IDEs und Build-Tools dabei helfen werden, bleibt anzuwarten.
Kritikpunkt #4: Keine Modul-Versionen.
Es ist im Java-Modul-System nicht vorgesehen,
dass die Module Versionen haben. Insbesondere ist es nicht ohne weiteres
möglich, mehrere Versionen eines Moduls gleichzeitig zu verwenden. Mit
anderen Worten, eine einfache Lösung für das "jar hell"-Problem bietet
das Modul-System nicht. Es gibt zwar sogenannte Laufzeit-Layer, mit denen
man eine Lösung bauen kann, aber das geht weit über die "normale" Benutzung
des Modul-Systems hinaus. Solche Lösungen sollen auch nicht die Java-Entwickler
bauen, sondern es wird von Oracle als Aufgabe für die Build-Tools angesehen.
Der Grund für das Fehlen von Modul-Versionen liegt darin, dass schon relativ
früh in den Diskussion um das Modul-Konzept erkennbar war, dass man sich
weder auf die Syntax noch auf die Semantik eines Versions-Schemas würde
einigen können. Deshalb ist die Versionierung ausdrücklich als Non-Requirement
in der Spezifikation genannt.
Damit belassen wir es bei diesem kurzen Einblick
in das Java Modul-System. Es hat zahlreiche weitere Features, die ganze
Bücher füllen werden. Es sind bereits drei Buchveröffentlichung zum
Modul-System in Arbeit (siehe /
OREIL
/,
/
APRES
/
und /
MANNG
/).
|
|||||||||||||||||||
© Copyright 1995-2018 by Angelika Langer. All Rights Reserved. URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/91.Java9.What-is-new-in-Java-9/90.java-9.1.overview.ready_6.html> last update: 26 Oct 2018 |