Fachlicher Vertrag: unverändert zu erhalten
- Öffentliche Pakete sind minimal und bewusst exportiert.
- Keine Split Packages.
- Service Provider funktionieren auf dem Module Path.
Classpath und globale Sichtbarkeit wird nachvollziehbar in module-info, exports und Service Provider überführt. Die Akte zeigt nicht nur das Ziel, sondern die tatsächlichen Dateien, APIs, Dependencies, Codebelege, Tests, Risiken und Cutover-Schritte.
| Dimension | Legacy | Modern | Migrationskonsequenz |
|---|---|---|---|
| Programmiermodell | imperativ / ohne Framework-Annotationen | explizite Java-Abstraktionen | Annotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen. |
| Abhängigkeiten | 1 direkte Dependencies | 1 direkte Dependencies | 0 neu, 0 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen. |
| Öffentliche API | 2 erkannte Methoden | 3 erkannte Methoden | Methoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag. |
| Datenmodell | class LegacyGreetingPlugin, class LegacyPluginLoader | class GermanGreetingProvider, class JpmsApplication, class JpmsSmokeCheck, interface GreetingProvider, module-info | Feldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet. |
| Fehlerverhalten | Legacy-Exceptions und Rückgabewerte | explizitere Fach-/Framework-Fehlerabbildung | Fehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden. |
| Tests | Bestands- und Golden-Master-Tests | Unit-, Slice-, Contract- und Integrationstests | Der Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt. |
| Betrieb | Legacy-Start/Lifecycle | modernes Packaging, Health und externe Konfiguration | Module Path/JAR-Inhalt und Service Discovery im finalen Artefakt testen. |
| Rollback | Legacy-Artefakt bleibt unverändert | Modern-Artefakt getrennt deploybar | Kein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein. |
split packages, exports and readability
module-path compile, forbidden access and service-loading tests
Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.
| Legacy-Rolle / Datei | Modern-Rolle / Datei | Bedeutung |
|---|---|---|
| LegacyPluginLoader.java + LegacyGreetingPlugin.java | GreetingProvider.java + GermanGreetingProvider.java | Classpath Plugin wird expliziter Service Provider. |
| globale Classpath-Sichtbarkeit | module-info.java | requires/exports/uses/provides werden deklarativ. |
| Legacy Start | JpmsApplication.java + JpmsSmokeCheck.java | Start und Module-Path-Smoke-Test werden explizit. |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| LegacyGreetingPlugin.java | class LegacyGreetingPlugin | — | String greet() | — | 7 |
| LegacyPluginLoader.java | class LegacyPluginLoader | — | Object load(String className) | Plugin Loader / Factory – erzeugt Plugins dynamisch über Reflection. | 12 |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| JpmsApplication.java | class JpmsApplication | — | void main(String[] args) | — | 14 |
| JpmsSmokeCheck.java | class JpmsSmokeCheck | — | boolean valid() | — | 10 |
| GreetingProvider.java | interface GreetingProvider | — | — | Service Provider Interface | 7 |
| GermanGreetingProvider.java | class GermanGreetingProvider | — | String greet(String name) | — | 9 |
| module-info.java | module-info | — | — | — | 5 |
| Status | Dependency | Version | Scope | Prüfung |
|---|---|---|---|---|
| BEIBEHALTEN | org.junit.jupiter:junit-jupiter | BOM/Parent → BOM/Parent | test → test | Gemeinsame Dependency; Version und Scope im effektiven POM prüfen. |
Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.
Migrationsbedeutung: Classpath Plugin wird expliziter Service Provider.
projects/28-jpms-jigsaw/legacy/src/main/java/at/aydin/lab/jpms/legacy/LegacyPluginLoader.java
package at.aydin.lab.jpms.legacy;
// Design Pattern: Plugin Loader / Factory – erzeugt Plugins dynamisch über Reflection.
public final class LegacyPluginLoader {
public Object load(String className) {
try {
return Class.forName(className).getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
}
projects/28-jpms-jigsaw/legacy/src/main/java/at/aydin/lab/jpms/legacy/LegacyGreetingPlugin.java
package at.aydin.lab.jpms.legacy;
public final class LegacyGreetingPlugin {
public String greet() {
return "Hallo aus dem Classpath";
}
}
projects/28-jpms-jigsaw/modern/src/main/java/at/aydin/lab/jpms/modern/api/GreetingProvider.java
package at.aydin.lab.jpms.modern.api;
// Design Pattern: Service Provider Interface
// Zweck: Consumer hängt nur von exportiertem API-Package ab.
public interface GreetingProvider {
String greet(String name);
}
package at.aydin.lab.jpms.modern.internal;
import at.aydin.lab.jpms.modern.api.GreetingProvider;
public final class GermanGreetingProvider implements GreetingProvider {
public String greet(String name) {
return "Hallo " + name;
}
}
Migrationsbedeutung: requires/exports/uses/provides werden deklarativ.
projects/28-jpms-jigsaw/legacy/src/main/java/at/aydin/lab/jpms/legacy/LegacyPluginLoader.java
package at.aydin.lab.jpms.legacy;
// Design Pattern: Plugin Loader / Factory – erzeugt Plugins dynamisch über Reflection.
public final class LegacyPluginLoader {
public Object load(String className) {
try {
return Class.forName(className).getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
}
projects/28-jpms-jigsaw/modern/src/main/java/at/aydin/lab/jpms/modern/JpmsSmokeCheck.java
package at.aydin.lab.jpms.modern;
import at.aydin.lab.jpms.modern.api.GreetingProvider;
import at.aydin.lab.jpms.modern.internal.GermanGreetingProvider;
public final class JpmsSmokeCheck {
public static boolean valid() {
return "Hallo Test".equals(new GermanGreetingProvider().greet("Test"));
}
}
--- LegacyPluginLoader.java
+++ JpmsSmokeCheck.java
@@ -1,12 +1,10 @@
-package at.aydin.lab.jpms.legacy;
+package at.aydin.lab.jpms.modern;
-// Design Pattern: Plugin Loader / Factory – erzeugt Plugins dynamisch über Reflection.
-public final class LegacyPluginLoader {
- public Object load(String className) {
- try {
- return Class.forName(className).getDeclaredConstructor().newInstance();
- } catch (ReflectiveOperationException ex) {
- throw new IllegalStateException(ex);
- }
+import at.aydin.lab.jpms.modern.api.GreetingProvider;
+import at.aydin.lab.jpms.modern.internal.GermanGreetingProvider;
+
+public final class JpmsSmokeCheck {
+ public static boolean valid() {
+ return "Hallo Test".equals(new GermanGreetingProvider().greet("Test"));
}
}
Migrationsbedeutung: Start und Module-Path-Smoke-Test werden explizit.
projects/28-jpms-jigsaw/legacy/src/main/java/at/aydin/lab/jpms/legacy/LegacyPluginLoader.java
package at.aydin.lab.jpms.legacy;
// Design Pattern: Plugin Loader / Factory – erzeugt Plugins dynamisch über Reflection.
public final class LegacyPluginLoader {
public Object load(String className) {
try {
return Class.forName(className).getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException ex) {
throw new IllegalStateException(ex);
}
}
}
projects/28-jpms-jigsaw/modern/src/main/java/at/aydin/lab/jpms/modern/JpmsApplication.java
package at.aydin.lab.jpms.modern;
import at.aydin.lab.jpms.modern.api.GreetingProvider;
import java.util.*;
public final class JpmsApplication {
private JpmsApplication() {
}
public static void main(String[] args) {
GreetingProvider provider = ServiceLoader.load(GreetingProvider.class).findFirst().orElseThrow();
System.out.println(provider.greet("Aydin"));
}
}
projects/28-jpms-jigsaw/modern/src/main/java/at/aydin/lab/jpms/modern/JpmsSmokeCheck.java
package at.aydin.lab.jpms.modern;
import at.aydin.lab.jpms.modern.api.GreetingProvider;
import at.aydin.lab.jpms.modern.internal.GermanGreetingProvider;
public final class JpmsSmokeCheck {
public static boolean valid() {
return "Hallo Test".equals(new GermanGreetingProvider().greet("Test"));
}
}
| ID | Ebene | Prüfung | erforderlicher Nachweis |
|---|---|---|---|
| 28-JPMS-JIGSAW-A01 | Integration/Contract | Modul kompiliert und startet auf dem Module Path. | Automatisierter Test und CI-Protokoll |
| 28-JPMS-JIGSAW-A02 | Integration/Contract | Interne Pakete sind von außen nicht lesbar. | Automatisierter Test und CI-Protokoll |
| 28-JPMS-JIGSAW-A03 | Integration/Contract | Keine automatischen Module mit instabilen Namen in kritischem Pfad. | Automatisierter Test und CI-Protokoll |
| 28-JPMS-JIGSAW-A04 | Integration/Contract | ServiceLoader findet Provider. | Automatisierter Test und CI-Protokoll |
| 28-JPMS-JIGSAW-A05 | Integration/Contract | Reflection-Zugriffe benötigen nur dokumentierte opens. | Automatisierter Test und CI-Protokoll |
| 28-JPMS-JIGSAW-T01 | Unit | Fachlogik ohne Container oder externen Dienst testen. | Unit-Test |
| 28-JPMS-JIGSAW-T02 | Regression | Legacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen. | Vergleichsreport |
| 28-JPMS-JIGSAW-T03 | Negative | Fehlerhafte, leere und grenzwertige Eingaben prüfen. | Negativtest |
| 28-JPMS-JIGSAW-T04 | Operations | Start, Health, Shutdown und Konfigurationsfehler prüfen. | Deployment-/Startprotokoll |
| 28-JPMS-JIGSAW-T05 | Packaging | Module Path, JAR-Inhalt und Service Discovery aus finalem Artefakt prüfen. | Packaging-Smoke-Test |
| 28-JPMS-JIGSAW-F01 | Fokus | module-path compile, forbidden access and service-loading tests | Modulspezifischer Testreport |
| Risiko | Auswirkung | Gegenmaßnahme | Gate |
|---|---|---|---|
| split packages, exports and readability | MITTEL | module-path compile, forbidden access and service-loading tests | vor Cutover |
| Modern-Seite besitzt keinen Testquellcode | HOCH | Mindestens Unit- und Integrations-/Contract-Test ergänzen | Build blockieren |
Keine zusätzlichen Ressourcen.
Das Artefakt kann vorübergehend weiterhin auf dem Classpath laufen, solange Package- und Serviceverträge kompatibel bleiben.