Modul 02 · Migration Deep Dive · Stufe 3 Application Layer

AOP

JDK Dynamic Proxy wird nachvollziehbar in Spring AOP mit @Aspect überführt. Die Akte zeigt nicht nur das Ziel, sondern die tatsächlichen Dateien, APIs, Dependencies, Codebelege, Tests, Risiken und Cutover-Schritte.

ProxyInterceptor
4 → 6Produktionsdateien
57 → 81Java-Zeilen
1 → 1Testdateien
1 / 2Dependencies entfernt / neu
NIEDRIGRisiko · Score 3

Was sich konkret ändert

DimensionLegacyModernMigrationskonsequenz
ProgrammiermodellOverrideAround, Aspect, Component, Override, Retention, Service, SpringBootApplication, Target, Tracked, annotation, interfaceAnnotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen.
Abhängigkeiten1 direkte Dependencies2 direkte Dependencies2 neu, 1 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen.
Öffentliche API4 erkannte Methoden4 erkannte MethodenMethoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag.
Datenmodellclass AopLegacyApplication, class LoggingProxy, class SimpleCalculator, interface Calculatorclass AopModernApplication, class CalculatorRunner, class TrackedCalculator, class TrackingAspect, interface Calculator, interface TrackedFeldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet.
FehlerverhaltenLegacy-Exceptions und Rückgabewerteexplizitere Fach-/Framework-FehlerabbildungFehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden.
TestsBestands- und Golden-Master-TestsUnit-, Slice-, Contract- und IntegrationstestsDer Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt.
BetriebLegacy-Start/Lifecyclemodernes Packaging, Health und externe KonfigurationStartup, Lifecycle, Security Context und Remote-/Containerfehler separat testen.
RollbackLegacy-Artefakt bleibt unverändertModern-Artefakt getrennt deploybarKein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein.

Fachlicher Vertrag: unverändert zu erhalten

  • Rechenergebnisse und Exceptions des Zielobjekts bleiben unverändert.
  • Tracking darf keine fachliche Transaktion starten oder beenden.
  • Sensitive Parameter werden nicht ungefiltert protokolliert.

Hauptrisiko und Testfokus

proxy limitations and cross-cutting coupling

aspect activation, exception and ordering tests

Die Modernisierung gilt erst als abgeschlossen, wenn der fachliche Vertrag automatisiert belegt ist.

Verbindliches Code- und Rollenmapping

Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.

Legacy-Rolle / DateiModern-Rolle / DateiBedeutung
LoggingProxy.javaTrackingAspect.java + Tracked.javaManueller Proxy wird durch deklarativen Interceptor ersetzt.
SimpleCalculator.javaTrackedCalculator.javaZielobjekt bleibt fachlich, erhält nur explizite Markierung.
AopLegacyApplication.javaAopModernApplication.java + CalculatorRunner.javaComposition Root wird vom Runner getrennt.

Legacy-Quellinventar

DateiTypAnnotationenöffentliche APIPatternZeilen
AopLegacyApplication.javaclass AopLegacyApplicationvoid main(String[] args)11
Calculator.javainterface Calculator5
LoggingProxy.javaclass LoggingProxyOverrideT wrap(Class<T> contract, T target)
Object invoke(Object proxy, Method method, Object[] args)
Proxy33
SimpleCalculator.javaclass SimpleCalculatorOverrideint add(int left, int right)8

Modern-Quellinventar

DateiTypAnnotationenöffentliche APIPatternZeilen
AopModernApplication.javaclass AopModernApplicationSpringBootApplicationvoid main(String[] args)11
Calculator.javainterface Calculator5
CalculatorRunner.javaclass CalculatorRunnerComponent, Overridevoid run(String ... args)17
Tracked.javainterface TrackedRetention, Target, interface13
TrackedCalculator.javaclass TrackedCalculatorOverride, Service, Trackedint add(int left, int right)12
TrackingAspect.javaclass TrackingAspectAround, Aspect, Component, annotationObject measure(ProceedingJoinPoint joinPoint)Interceptor / Aspect23

Dependency- und Laufzeitmigration

StatusDependencyVersionScopePrüfung
ENTFERNTorg.junit.jupiter:junit-jupiterBOM/ParenttestLegacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt.
NEUorg.springframework.boot:spring-boot-starter-aopBOM/ParentcompileNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.boot:spring-boot-starter-testBOM/ParenttestNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
Direkte POM-Daten ersetzen keinen erfolgreichen Online-Build mit transitiver SBOM- und CVE-Prüfung.

Vorher-/Nachher-Codebelege

Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.

Legacy
LoggingProxy.java
Modern
TrackingAspect.java + Tracked.java

Migrationsbedeutung: Manueller Proxy wird durch deklarativen Interceptor ersetzt.

Legacy-Code

projects/02-aop/legacy/src/main/java/at/aydin/lab/aop/legacy/LoggingProxy.java

package at.aydin.lab.aop.legacy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

// Design Pattern: Proxy
// Zweck: Querschnittslogik wird vor und nach einem Methodenaufruf ausgeführt.
public final class LoggingProxy implements InvocationHandler {
    private final Object target;
    private LoggingProxy(Object target) {
        this.target = target;
    }

    public static <T> T wrap(Class<T> contract, T target) {
        Object proxy = Proxy.newProxyInstance(contract.getClassLoader(), new Class<?>[] {
            contract
        }, new LoggingProxy(target));
        return contract.cast(proxy);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long started = System.nanoTime();
        System.out.printf("START %s %s%n", method.getName(), Arrays.toString(args));
        try {
            return method.invoke(target, args);
        } finally {
            System.out.printf("ENDE %s %d µs%n", method.getName(), (System.nanoTime() - started) / 1_000);
        }
    }
}

Modern-Code

projects/02-aop/modern/src/main/java/at/aydin/lab/aop/modern/TrackingAspect.java

package at.aydin.lab.aop.modern;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
// Design Pattern: Interceptor / Aspect
// Zweck: Monitoring bleibt getrennt von der Geschäftslogik.
public class TrackingAspect {
    @Around("@annotation(Tracked)")
    public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
        long started = System.nanoTime();
        try {
            return joinPoint.proceed();
        } finally {
            long micros = (System.nanoTime() - started) / 1_000;
            System.out.printf("%s dauerte %d µs%n", joinPoint.getSignature().toShortString(), micros);
        }
    }
}

Modern-Code

projects/02-aop/modern/src/main/java/at/aydin/lab/aop/modern/Tracked.java

package at.aydin.lab.aop.modern;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public
@interface
Tracked {
}
Legacy
SimpleCalculator.java
Modern
TrackedCalculator.java

Migrationsbedeutung: Zielobjekt bleibt fachlich, erhält nur explizite Markierung.

Legacy-Code

projects/02-aop/legacy/src/main/java/at/aydin/lab/aop/legacy/SimpleCalculator.java

package at.aydin.lab.aop.legacy;

public final class SimpleCalculator implements Calculator {
    @Override
    public int add(int left, int right) {
        return left + right;
    }
}

Modern-Code

projects/02-aop/modern/src/main/java/at/aydin/lab/aop/modern/TrackedCalculator.java

package at.aydin.lab.aop.modern;

import org.springframework.stereotype.Service;

@Service
public class TrackedCalculator implements Calculator {
    @Override
    @Tracked
    public int add(int left, int right) {
        return left + right;
    }
}
Technischen Unified Diff öffnen
--- SimpleCalculator.java
+++ TrackedCalculator.java
@@ -1,7 +1,11 @@
-package at.aydin.lab.aop.legacy;
+package at.aydin.lab.aop.modern;
 
-public final class SimpleCalculator implements Calculator {
+import org.springframework.stereotype.Service;
+
+@Service
+public class TrackedCalculator implements Calculator {
     @Override
+    @Tracked
     public int add(int left, int right) {
         return left + right;
     }
Legacy
AopLegacyApplication.java
Modern
AopModernApplication.java + CalculatorRunner.java

Migrationsbedeutung: Composition Root wird vom Runner getrennt.

Legacy-Code

projects/02-aop/legacy/src/main/java/at/aydin/lab/aop/legacy/AopLegacyApplication.java

package at.aydin.lab.aop.legacy;

public final class AopLegacyApplication {
    private AopLegacyApplication() {
    }

    public static void main(String[] args) {
        Calculator calculator = LoggingProxy.wrap(Calculator.class, new SimpleCalculator());
        System.out.println("Ergebnis: " + calculator.add(4, 7));
    }
}

Modern-Code

projects/02-aop/modern/src/main/java/at/aydin/lab/aop/modern/AopModernApplication.java

package at.aydin.lab.aop.modern;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AopModernApplication {
    public static void main(String[] args) {
        SpringApplication.run(AopModernApplication.class, args);
    }
}

Modern-Code

projects/02-aop/modern/src/main/java/at/aydin/lab/aop/modern/CalculatorRunner.java

package at.aydin.lab.aop.modern;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class CalculatorRunner implements CommandLineRunner {
    private final Calculator calculator;
    public CalculatorRunner(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public void run(String ... args) {
        System.out.println("Ergebnis: " + calculator.add(4, 7));
    }
}

Umsetzungsplan mit Qualitäts-Gates

  1. Arbeitspaket 1
    Alle über LoggingProxy abgefangenen Methoden und Nebenwirkungen inventarisieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  2. Arbeitspaket 2
    Tracked als explizites Pointcut-Merkmal definieren und unbeabsichtigte Treffer vermeiden. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  3. Arbeitspaket 3
    LoggingProxy-Verhalten in Around-Advice nachbilden, einschließlich Exception-Pfad. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  4. Arbeitspaket 4
    Self-invocation und nur über Proxy erreichbare Methoden explizit testen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  5. Arbeitspaket 5
    Reihenfolge zu Transaktions-, Security- und Retry-Aspekten festlegen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  6. Arbeitspaket 6
    Proxy-Implementierung erst entfernen, wenn Ergebnis-, Exception- und Log-Verträge übereinstimmen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.

Konkreter Test- und Abnahmekatalog

IDEbenePrüfungerforderlicher Nachweis
02-AOP-A01Integration/ContractMarkierte Methoden werden genau einmal gemessen.Automatisierter Test und CI-Protokoll
02-AOP-A02Integration/ContractNicht markierte Methoden bleiben unbeeinflusst.Automatisierter Test und CI-Protokoll
02-AOP-A03Integration/ContractOriginalexception und Stacktrace bleiben erhalten.Automatisierter Test und CI-Protokoll
02-AOP-A04Integration/ContractKeine rekursive Aspect-Ausführung.Automatisierter Test und CI-Protokoll
02-AOP-A05Integration/ContractPointcut- und Reihenfolgetests sind grün.Automatisierter Test und CI-Protokoll
02-AOP-T01UnitFachlogik ohne Container oder externen Dienst testen.Unit-Test
02-AOP-T02RegressionLegacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen.Vergleichsreport
02-AOP-T03NegativeFehlerhafte, leere und grenzwertige Eingaben prüfen.Negativtest
02-AOP-T04OperationsStart, Health, Shutdown und Konfigurationsfehler prüfen.Deployment-/Startprotokoll
02-AOP-T05LifecycleContainer-/Service-Lifecycle, Startup-Fehler und Recovery prüfen.Runtime-Test
02-AOP-F01Fokusaspect activation, exception and ordering testsModulspezifischer Testreport

Risikoregister des Moduls

RisikoAuswirkungGegenmaßnahmeGate
proxy limitations and cross-cutting couplingNIEDRIGaspect activation, exception and ordering testsvor Cutover
Neue Framework-/Library-LaufzeitabhängigkeitenMITTELSBOM, Lizenz-, CVE- und transitive Dependency-Prüfung im Online-Buildvor Release
Legacy-Verhalten war implizit an entfernte Library gekoppeltMITTELContract-/Golden-Master-Tests und gezielte Fehlerpfadevor Abschaltung

Konfiguration und Ressourcen

Keine zusätzlichen Ressourcen.

Rollback und Koexistenz

Die Annotation kann deaktiviert und der bisherige Proxy als äußerer Adapter weiterverwendet werden, ohne den Calculator-Vertrag zu ändern.

Abbruchkriterien

  • Fachlicher Golden-Master weicht ab.
  • Daten-, Nachrichten- oder API-Kompatibilität ist ungeklärt.
  • Fehlerquote, Latenz oder Ressourcenverbrauch überschreiten das vereinbarte Limit.
  • Monitoring oder Rückfallpfad ist nicht funktionsfähig.

Definition of Done

Direkte Arbeitslinks

⌂ Cockpit