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 die public Typen des Pakets, sodass es andere von außen nutzen können
  • exports PACKAGE to MODULE, ...;
    Exportiert die public Typen des Pakets, sodass es nur die aufgelisteten Module von außen nutzen können
  • provides SERVICE with CLASS;
    Bietet eine Implementierung des Service mit der gegebenen Klasse an
  • uses SERVICE;
    Nutzt einen Service
  • opens PACKAGE;
    Erlaubt Zugriff auf das Paket zur Laufzeit per Reflection
  • opens PACKAGE to MODULE, ...
    Erlaubt den angegebenen Modulen Zugriff auf das Paket zur Laufzeit per Reflection
Beachten Sie, dass requires ein Modul angibt, exports aber ein Paket.

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

  1. Ein Service Interface
  2. 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-info.java der Service-Schnittstelle
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:

  1. Das Dienstschnittstellenmodul in seinem eigenen Moduldeskriptor per require deklarieren.
  2. Das Service-Interface mit einer Java-Klasse implementieren.
  3. Die Implementierung der Dienstschnittstelle in seinem Moduldeskriptor per provides deklarieren.
module-info.java der Service-Implementierung
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-info.java der Service-Implementierung
module com.client.theclient {
    requires de.hs_mannheim.pr2module.service;

    uses de.hs_mannheim.pr2module.service.TheService;
}

Copyright © 2025 Thomas Smits