Modul-System
Konzept
Das Java Platform Module System ist eine wichtige Neuerung seit Java 9. Es handelt sich um ein System zur Strukturierung von Java-Anwendungen in wiederverwendbare und unabhängige Module.
Das Modulsystem bietet eine bessere Kontrolle über den Sichtbarkeitsbereich und die Abhängigkeiten von Java-Klassen und ermöglicht eine klare Definition von Schnittstellen und Implementierungen.
Die gesamte Java-Laufzeitumgebung wird in Module unterteilt. Jedes Modul enthält eine Gruppe von zusammengehörigen Klassen und Schnittstellen, die von anderen Modulen wiederverwendet werden können. Jedes Modul hat einen Namen und eine Liste von Abhängigkeiten zu anderen Modulen.
Das Modulsystem definiert auch Regeln für den Zugriff auf Klassen und Schnittstellen innerhalb und zwischen Modulen. Dadurch wird sichergestellt, dass nur die notwendigen Klassen und Schnittstellen zugänglich sind und es wird verhindert, dass unerwünschte Klassen und Funktionen im Code verwendet werden.
Java Platform Module System (JPMS) führt Module ein.
Module
- erlauben, den Zugriff auf Pakete einzuschränken
⇒ Kapselung - deklarieren, welche anderen Module sie brauchen
⇒ Klare Abhängigkeiten, kein Exceptions zur Laufzeit mehr - erlauben, die Java-Klassenbibliothek selbst zu modularisieren
⇒ kleinere Programmpakete
Zugriff auf public
Typen kann eingeschränkt werden
- auf das eigene Modul
- auf explizit angegebene Module
- auf jeden Verwender
Die letzte Sichtbarkeitsstufe (für jeden Verwender) ist das Standard-Verhalten, wenn man keine Module verwendet. Bis zur Einführung des Modulkonzeptes mit Java 9, konnte jede Klasse jede andere Klasse verwenden, wenn sie public
deklariert war.
Umsetzung
Module
- haben eine Namen, analog zu Paketen, z. B.
de.hs_mannheim.pr2module
- werden über die Datei
module-info.java
im Hauptverzeichnis des Moduls gesteuert (Module Descriptor)
module modulename {
requires MODULE;
exports PACKAGE;
...
}
Deklaration
Direktiven in der module-info.java
requires MODULE;
Definiert eine Abhängigkeit zu einem anderen Modul
(module dependency)requires static MODULE;
Definiert eine (optionale) Abhängigkeit zu einem anderen Modul, die zwingend nur beim Compilieren benötigt wird (optional dependency)requires transitive MODULE;
Definiert eine Abhängigkeit, die Nutzer von diesem Modul erben (implied readability)
Eine transitive Abhängigkeit entsteht, wenn Modul modA
in seiner Schnittstelle einen Typ (z. B. T
) von Modul modX
zurückgibt. Benutzt nun ein anderes Modul modB
das Modul modA
, so muss es auch eine Abhängigkeit von Modul modX
haben, weil es sonst diesen Typ T
nicht kennen würde, bis es selbst eine Abhängigkeit zu modX
deklariert. Um diesen Aufwand zu sparen, erklärt modA
seine Abhängigkeit von modX
als transitive
, sodass modB
automatisch auch von modX
abhängt, sobald es modA
benutzt.
exports PACKAGE;
Exportiert diepublic
Typen des Pakets, sodass es andere von außen nutzen könnenexports PACKAGE to MODULE, ...;
Exportiert diepublic
Typen des Pakets, sodass es nur die aufgelisteten Module von außen nutzen könnenprovides SERVICE with CLASS;
Bietet eine Implementierung des Service mit der gegebenen Klasse anuses SERVICE;
Nutzt einen Service
opens PACKAGE;
Erlaubt Zugriff auf das Paket zur Laufzeit per Reflectionopens PACKAGE to MODULE, ...
Erlaubt den angegebenen Modulen Zugriff auf das Paket zur Laufzeit per Reflection
Services
Das Konzept der Services wurde mit Java 9 eingeführt und setzt das Prinzip der Trennung von API und Implementierung (siehe Kapitel zur Objektorientierung) auf der Basis von Modulen um.
Es gibt hier
- Ein Service Interface
- Eine oder mehrere Implementierungen des Services
Die Serviceschnittstelle befindet sich in der Regel in einem Serviceschnittstellen-Java-Modul, das nur die Serviceschnittstelle sowie alle mit der Serviceschnittstelle verbundenen Klassen und Interfaces enthält.
Die Service-Implementierungen werden von separaten Java-Modulen bereitgestellt – nicht vom Service-Interface-Modul. Normalerweise enthält ein Java-Modul zur Dienstimplementierung eine einzige Dienstimplementierung.
Ein Java-Modul oder eine Anwendung kann das Serviceschnittstellenmodul verwenden und gegen die Serviceschnittstelle kodieren, ohne genau zu wissen, welches andere Modul die Serviceimplementierung liefert. Die Dienstimplementierung wird zur Laufzeit ermittelt und hängt davon ab, welche Implementierungsmodule beim Start der Anwendung im Java-Modulpfad verfügbar sind.
Service-Schnittstelle
Das Modul mit dem Service-Interface erfordern keine spezielle Deklaration, sondern einfach ein normales Java-Modul.
module de.hs_mannheim.pr2module.service {
exports de.hs_mannheim.pr.service;
}
Service-Implementierung
Ein Java-Modul, das ein Interface von einem Serviceschnittstellenmodul implementieren möchte, muss:
- Das Dienstschnittstellenmodul in seinem eigenen Moduldeskriptor per
require
deklarieren. - Das Service-Interface mit einer Java-Klasse implementieren.
- Die Implementierung der Dienstschnittstelle in seinem Moduldeskriptor per
provides
deklarieren.
module de.hs_mannheim.pr2module.serviceimpl {
requires de.hs_mannheim.pr2module.service;
provides de.hs_mannheim.pr.service.TheSevice with
de.hs_mannheim.pr2.impl.TheServiceImpl;
}
Service-Verwender (Client)
Um den Dienst nutzen zu können, muss das Client-Modul in seinem Moduldeskriptor erklären, dass es den Dienst nutzt.
module com.client.theclient {
requires de.hs_mannheim.pr2module.service;
uses de.hs_mannheim.pr2module.service.TheService;
}