Module verwenden v.9
Motivation
Mit der Version 9 wurde in Java ein Modulkonzept eingeführt, das es erlaubt, den über zwanzig Jahre monolithisch gewachsenen JDK-Bibliotheken oberhalb der package-Ebene eine weitere Gliederungsmöglichkeit hinzuzufügen. Der Nutzen ist vielfältig. So können die jeweils benötigten Bibliothekselemente vergleichsweise differenziert gewählt werden. Die Vorteile liegen z.B.
- in einer sichereren Steuerung der Zugriffsrechte
- übersichtlicherer und besser zu gliedernder Entwicklungarbeit
- in der besseren Skalierbarkeit einer Anwendung
- und damit gekoppelt in der Möglichkeit zur besseren Anpassbarkeit der Größe ausführbarer Dateien, da nicht mehr die gesamte JDK-Bibliothek verwendet werden muss.
Es liegt nahe, dass die Nutzung des modularen Konzeptes hauptsächlich bei der Erstellung von Bibliotheken oder eher umfangreicher, differenzierter Anwendungs-Software ihre Vorteile ausspielen wird. Dennoch soll hier ein mögliches Verfahren bei der Erstellung einer modularisierten Anwendung mit Hilfe der IDE Eclipse anhand eines Minimalbeispiels gezeigt werden.
Das Beispielprogramm
Der prinzipielle Aufbaus eines modularisierten
Projektes wird anhand der Erstellung eines sehr
einfachen Programms gezeigt, das ein von JFrame
abgeleitetes Fenster mit einem Label zeigt. Der
Programmaufruf in main()
und die
Erzeugung des GUI sollen dabei in zwei
unterschiedlichen Modulen erfolgen. Zur
Demonstration der Einbindung von Interfaces wird
zusätzlich ein drittes Modul erzeugt, dessen
darin enthaltenes Interface von der abgeleiteten
Fensterklasse implementiert wird.
Jedes Modul wird in einem gesonderten Eclipse-Projekt deklariert.
Anlegen der Projekte
Bei der Anlage eines neuen Projektes wird im zweiten
Fenster abgefragt, ob die Anlage einer Datei module-info.java
gewünscht wird. Sie dient als Moduldeklaration,
in der durch spezielle
Direktiven angegeben wird, welche
Bestandteile das Modul enthält und auf welche
davon und auf welche Weise von außen darauf
zugegriffen werden kann.
Im darauf folgenden Fenster wird der Modulname
angelegt, bei dem nach den Code-Konventionen darauf
geachtet werden sollte, dass er mit einem
Kleinbuchstaben beginnt und keine Leerzeichen
enthält. Wir geben hier 'hello' ein.
Nach
Abschluss der Projekterzeugung findet sich module-info.java
auf oberster Ebene, also direkt im Verzeichnis src
.
Sie kann auch auf einer höheren package-Ebene
angelegt werden, muss jedoch zwingend im
Wurzelverzeichnis des Moduls liegen. Die Datei
enthält einen durch das Schlüsselwort module
und den Modulnamen eingeleiteten, zunächst noch
leeren Block in geschweiften Klammern:
module hello { }
Ein Modul kann eine beliebige package-Struktur
enthalten, deren Zugriff detailliert gesteuert
werden kann.
Im Beispielprojekt wird
zunächst das package de.javabeginners.hello
und darin eine Klasse HelloWorld
in
einer Datei HelloWorld.java
erzeugt.
Auf die gleiche Weise werden nun zwei weitere
Projekte angelegt: HelloGUI
mit einem
Modul helloGUI
, einem package de.javabeginners.helloGui
und der darin deklarierten GUI-Klasse HelloFrame
,
sowie ein Projekt HelloInt
mit dem
Modul helloInt
, einem package de.javabeginners.helloInt
und einem Interface HelloInt
. Zur
besseren Übersicht lassen sich die drei
Projekte in Eclipse bei Bedarf zu einem Working Set
zusammenfassen, hier mit der Bezeichnung Module
Test Projects
:
Die Struktur im package
manager sieht nun folgendermaßen aus:
Die drei Quelltextdateien mit den
Klassendeklarationen werden nun wie folgt
ergänzt. Man beachte, dass HelloFrame
von JFrame
abgeleitet wird und HelloInt
implementiert, somit zwei verschiedene Formen der
Abhängigkeit vorliegen:
package de.javabeginners.hello; import de.javabeginners.helloGui.HelloFrame; public class HelloWorld { public static void main(String[] args) { HelloFrame frame = new HelloFrame(); frame.setSize(300, 300); frame.setDefaultCloseOperation(HelloFrame.EXIT_ON_CLOSE); var txt = "Text"; frame.setText(txt); frame.sprich(); frame.setVisible(true); } }
HelloWorld.java
package de.javabeginners.helloGui; import java.awt.GridBagLayout; import javax.swing.JFrame; import javax.swing.JLabel; import de.javabeginners.helloInt.HelloInt; public class HelloFrame extends JFrame implements HelloInt { private JLabel label; public HelloFrame() { label = new JLabel(); this.add(label); this.setLayout(new GridBagLayout()); } public void setText(String txt) { label.setText(txt); } public void sprich() { System.out.println("Hallo Welt!"); } }
HelloFrame.java
package de.javabeginners.helloInt; public interface HelloInt { public void sprich(); }
HelloInt.java
Zusätzlich müssen alle Importe eigener
Projekte Eclipse gesondert mitgeteilt werden. Dies
geschieht in den jeweiligen Projekteigenschaften
unter der Rubrik Java Build Path und dem
Registerreiter Projects im Abschnitt Modulepath.
Im Projekt Hello
müssen somit dort
HelloGUI
und im Projekt HelloGUI
HelloInt
eingetragen werden.
Die Dateien module-info.java
Nach entsprechender Ergänzung werden in den
Quelltexten eine Reihe an Fehlern durch rote
Schlangenlinien angezeigt. Es fällt auf, dass
selbst die korrekt angegebenen import
-Anweisungen
derartig gekennzeichnet sind.
Die Ursache liegt
darin, dass in modularisierten Projekten die
benötigten Zugriffe in der zugehörigen
Datei module-info.java
geregelt werden
müssen. Dies gilt auch für die Bausteine
der JDK-Bibliothek.
Ergänzt man die Datei module-info.java
des Projektes HelloGUI
folgendermaßen
module helloGUI { requires java.desktop; }
so verschwindet diese Fehleranzeige in der Klasse HelloFrame
.
Der Zugriff auf die Elemente des Moduls java.desktop
,
hier die benötigten AWT- und
Swing-Bibliotheken, wird durch die Angabe des Moduls
nach dem Schlüsselwort requires
in
module-info.java
auf diese Weise
erlaubt. Das Schlüsselwort requires
kennzeichnet die Abhängigkeit eines Moduls von
einem anderen.
Ein Überblick über die hier möglichen
Schlüsselworte und deren Funktionen
findet sich im Abschnitt Schlüsselworte
der Moduldirektiven.
In der Klasse HelloWorld
kennzeichnet
der Compiler alle Einträge als fehlerhaft, die
die Klasse HelloFrame
referenzieren.
Man vermutet zurecht, dass ein entsprechender
Eintrag in module-info.java
des Moduls
hello
benötigt wird, um Zugriff
auf die Klasse zu erhalten:
module hello { requires helloGUI; }
Erstaunlicherweise behebt dieser Eintrag jedoch
nicht alle Fehleranzeigen. Das ist erst dann der
Fall, wenn das package de.javabeginners.helloGui
explizit in der zugehörigen Moduldeklaration
zum Export vorgesehen wird:
module helloGUI { requires java.desktop; exports de.javabeginners.helloGui; }
Nun fehlt noch die Behebung eines Fehlers in der
Klasse HelloFrame
. Es verwundert nun
nicht mehr, dass der Import des Interfaces noch
markiert ist: Das package muss noch exportiert
werden:
module helloInt { exports de.javabeginners.helloInt; }
Der Fehler wird hierdurch jedoch immer noch nicht
vollständig behoben, da die Nutzung des
Interfaces durch das Modul helloGUI
noch angezeigt werden muss:
module helloGUI { exports de.javabeginners.helloGui; requires java.desktop; requires helloInt; uses de.javabeginners.helloInt.HelloInt; }
Bei der Nutzung des Interface muss sowohl eine requires
-,
als auch eine uses
-Direktive angegeben
werden. requires
kennzeichnet hier
wiederum die Abhängigkeit des einen Moduls von
dem anderen, uses
bezeichnet den
'Service', hier also das Implementieren des
Interface HelloInt
. Statt der uses
-Direktive
könnte hier übrigens auch
provides de.javabeginners.helloInt.HelloInt with de.javabeginners.helloGui.HelloFrame;
angegeben werde.
Die JDK-Module
Auch wenn Eclipse mit dem Content-Assistenten beim Fehlen einer Modul-Direktive die notwendigen Vorschläge macht, kann es nicht schaden, gelegentlich einen Überblick über die zur Verfügung stehenden JDK-Module zu erhalten.
Fanden sich in den Java-Versionen vor der Version 9
die Bibliotheken innerhalb des Verzeichnisses $JAVA_HOME/jre/lib
,
so wurde, nach Einführung der Modularisierung
mit Java 9, das Verzeichnis jre
durch jmods
ersetzt. In diesem befinden sich die JDK-Module.
Deren Auflistung kann über die Kommandozeile
durch Aufruf der ausführbaren Datei java
im bin
-Verzeichnis der Installation mit
der Option --list-modules
erfolgen:
# java --list-modules
Eine ausführliche Erläuterung der einzelnen Module findet sich dann in der Java-Dokumentation der verwendeten JDK-Version.
Schlüsselworte der Moduldirektiven
Die im Folgenden gelisteten Schlüsselworte sind im Kontext einer Moduldeklaration für die jeweilige Funktion reserviert und dürfen nicht in einem anderen Kontext verwendet werden.
requires <modulname>
|
Gibt die Abhängigkeit von einem anderen Modul an. Jedes Modul muss seine Abhängigkeiten explizit benennen. |
requires transitive <modulname>
|
Gibt an, dass Module, die das aktuelle Modul lesen, auch die genannte Abhängigkeit lesen. |
exports <modulname>
|
Gibt an, dass ein oder mehrere enthaltene Typen für alle anderen Module sichtbar sein soll. Selbstverständlich müssen die Zugriffsmodifizierer der Klassen und Interfaces dies auch erlauben. |
uses <modulname> |
Gibt an, dass ein Objekt des Moduls das genannte nutzt, etwa im Sinne des Implementierens eines Interfaces. |
provides <type> with <class>
|
Definiert einen "Service", also die
Abhängigkeiten zwischen einer Klasse
einerseits und einem von dieser
implementierten Interface oder einer
abstrakten Klasse andererseits. Bsp.: Implementiert die Klasse A aus
Modul ModA das Interface B
aus Modul ModB , so wird in der
Moduldeklaration des Moduls A
angegeben provides B with A; .
Man beachte, dass hier die Typen
(natürlich mit package-Angabe) und
nicht die Module genannt werden.
|
opens <package> |
Erlaubt den Zugriff auf öffentliche
Typen des package zur Laufzeit(!).
Zudem sind alle Typen des genannten package
durch reflection erreichbar.
|
opens <package> to <modulliste>
|
Erlaubt den Zugriff auf öffentliche
Typen des package zur Laufzeit(!)
durch die aufgelisteten Module. Zudem sind
alle Typen des genannten package durch reflection
für die genannten Module erreichbar.
Die Liste der Module muss kommasepariert
sein.
|
open module <modulname>
|
Erlaubt den Zugriff auf alle Typen des
moduls zur Laufzeit(!) und via reflection .
|
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.