Java Fortgeschrittenen Schulung – Kursbezug
Ziel dieses Kapitels: Sie können module-info.java lesen/schreiben, Pakete gezielt exportieren,
Abhängigkeiten sauber modellieren und (optional) ein Service-Provider-Muster mit JPMS umsetzen.
Worum geht’s?
- module-info.java beschreibt Grenzen: was wird exportiert, was wird benötigt.
- Strong Encapsulation: nicht exportierte Pakete sind von außen nicht erreichbar.
- requires / exports / requires transitive für saubere Abhängigkeiten.
- Services (
uses/provides) für plug-in-fähige Architektur (z.B. Formatter/Regeln als Plugins).
Lehr-/Lernziele
- (LZ1) ein Modul definieren (
module,requires,exports) und bewusst entscheiden, was öffentlich ist. - (LZ2) Abhängigkeiten sauber gestalten (
requires transitive, API vs. Implementation) und typische Fehler (z.B. Split Packages) vermeiden. - (LZ3) (optional) ein Service-Pattern mit JPMS umsetzen (
uses/provides) und Provider austauschbar perServiceLoaderladen.
1) Minimal: Modul deklarieren
// Datei: src/module-info.java
module de.sematrain.training.core {
exports de.sematrain.training.api; // öffentlich (API)
// de.sematrain.training.internal wird NICHT exportiert → intern
}2) Abhängigkeiten: requires & requires transitive
// Core nutzt ein Logging-Modul
module de.sematrain.training.core {
requires java.logging;
exports de.sematrain.training.api;
}// API-Modul „zieht“ eine Abhängigkeit für Konsumenten mit (Beispiel)
module de.sematrain.training.api {
requires transitive java.logging;
exports de.sematrain.training.api;
}3) Strong Encapsulation: API vs. Internal
- exports = von außen nutzbar (öffentliche API).
- nicht exportierte Packages = nur intern (Schutz vor „Implementierungs-Leaks“).
- Für Reflection (z.B. JSON-Mapper) gibt es
opens/open module– gezielt einsetzen.
module de.sematrain.training.core {
exports de.sematrain.training.api;
// Reflection nur für ein internes Paket und nur für ein Zielmodul:
opens de.sematrain.training.internal.model to com.fasterxml.jackson.databind;
}4) Services: uses / provides (Plugin-Architektur)
// Service-Interface (SPI) – im API-Modul
package de.sematrain.format.spi;
public interface Formatter {
String name();
String format(String text);
}// module-info im API-Modul
module de.sematrain.format.api {
exports de.sematrain.format.spi;
uses de.sematrain.format.spi.Formatter;
}// Provider-Implementierung (in separatem Modul)
package de.sematrain.format.upper;
import de.sematrain.format.spi.Formatter;
public class UppercaseFormatter implements Formatter {
public String name() { return "GROSSBUCHSTABEN"; }
public String format(String text) { return text.toUpperCase(); }
}// module-info im Provider-Modul
module de.sematrain.format.upper {
requires de.sematrain.format.api;
provides de.sematrain.format.spi.Formatter
with de.sematrain.format.upper.UppercaseFormatter;
}// App lädt Provider per ServiceLoader (kein new UppercaseFormatter())
import java.util.ServiceLoader;
import de.sematrain.format.spi.Formatter;
public class FormatDemo {
public static void main(String[] args) {
String text = "SemaTrain: Java Fortgeschritten";
for (Formatter f : ServiceLoader.load(Formatter.class)) {
System.out.println("Formatter: " + f.name() + " => " + f.format(text));
}
}
}Praxisaufgabe (Mini)
- Erstellen Sie 2 Module:
de.sematrain.training.apiundde.sematrain.training.core. ...apiexportiert nur sein API-Paket....coreexportiert nichts (intern).- Erstellen Sie im API ein Interface
CourseLabelerund im Core eine Implementierung. - Testen Sie: Der Konsument kann nur das API nutzen – interne Klassen aus Core sind nicht sichtbar.
- Optional: Bauen Sie das Ganze als Service (uses/provides), sodass weitere Provider später ergänzt werden können.
Lösung anzeigen
// Modul: de.sematrain.training.api
// Datei: de.sematrain.training.api/module-info.java
module de.sematrain.training.api {
exports de.sematrain.training.api;
}
// Datei: de.sematrain.training.api/de/sematrain/training/api/CourseLabeler.java
package de.sematrain.training.api;
public interface CourseLabeler {
String label(String kursTitel, String ort);
}
// Modul: de.sematrain.training.core
// Datei: de.sematrain.training.core/module-info.java
module de.sematrain.training.core {
requires de.sematrain.training.api;
// Nichts exportieren → Implementierung bleibt intern
}
// Datei: de.sematrain.training.core/de/sematrain/training/core/DefaultCourseLabeler.java
package de.sematrain.training.core;
import de.sematrain.training.api.CourseLabeler;
public class DefaultCourseLabeler implements CourseLabeler {
public String label(String kursTitel, String ort) {
String o = (ort == null || ort.isBlank()) ? "remote" : ort;
return "Kurs: " + kursTitel + " | Ort: " + o + " | Anbieter: SemaTrain";
}
}Optional: typische Stolperfallen
- Split Packages: das gleiche Package in mehreren Modulen → vermeiden.
- Zu viel exports: nur API-Pakete exportieren, interne Details intern lassen.
- Reflection: Frameworks brauchen ggf.
opens– gezielt statt „open module“. - Classpath vs. Modulepath: JPMS wirkt nur zuverlässig auf dem Modulepath.
Kurz-Takeaways
module-info.javamacht Architekturgrenzen explizit.- Exports minimal halten: API raus, Implementierung rein.
- Services (uses/provides) = austauschbare Provider für Plugin-Architektur.