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.
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.
| Dimension | Legacy | Modern | Migrationskonsequenz |
|---|---|---|---|
| Programmiermodell | Override | Around, Aspect, Component, Override, Retention, Service, SpringBootApplication, Target, Tracked, annotation, interface | Annotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen. |
| Abhängigkeiten | 1 direkte Dependencies | 2 direkte Dependencies | 2 neu, 1 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen. |
| Öffentliche API | 4 erkannte Methoden | 4 erkannte Methoden | Methoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag. |
| Datenmodell | class AopLegacyApplication, class LoggingProxy, class SimpleCalculator, interface Calculator | class AopModernApplication, class CalculatorRunner, class TrackedCalculator, class TrackingAspect, interface Calculator, interface Tracked | 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 | Startup, Lifecycle, Security Context und Remote-/Containerfehler separat 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. |
proxy limitations and cross-cutting coupling
aspect activation, exception and ordering tests
Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.
| Legacy-Rolle / Datei | Modern-Rolle / Datei | Bedeutung |
|---|---|---|
| LoggingProxy.java | TrackingAspect.java + Tracked.java | Manueller Proxy wird durch deklarativen Interceptor ersetzt. |
| SimpleCalculator.java | TrackedCalculator.java | Zielobjekt bleibt fachlich, erhält nur explizite Markierung. |
| AopLegacyApplication.java | AopModernApplication.java + CalculatorRunner.java | Composition Root wird vom Runner getrennt. |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| AopLegacyApplication.java | class AopLegacyApplication | — | void main(String[] args) | — | 11 |
| Calculator.java | interface Calculator | — | — | — | 5 |
| LoggingProxy.java | class LoggingProxy | Override | T wrap(Class<T> contract, T target) Object invoke(Object proxy, Method method, Object[] args) | Proxy | 33 |
| SimpleCalculator.java | class SimpleCalculator | Override | int add(int left, int right) | — | 8 |
| Datei | Typ | Annotationen | öffentliche API | Pattern | Zeilen |
|---|---|---|---|---|---|
| AopModernApplication.java | class AopModernApplication | SpringBootApplication | void main(String[] args) | — | 11 |
| Calculator.java | interface Calculator | — | — | — | 5 |
| CalculatorRunner.java | class CalculatorRunner | Component, Override | void run(String ... args) | — | 17 |
| Tracked.java | interface Tracked | Retention, Target, interface | — | — | 13 |
| TrackedCalculator.java | class TrackedCalculator | Override, Service, Tracked | int add(int left, int right) | — | 12 |
| TrackingAspect.java | class TrackingAspect | Around, Aspect, Component, annotation | Object measure(ProceedingJoinPoint joinPoint) | Interceptor / Aspect | 23 |
| Status | Dependency | Version | Scope | Prüfung |
|---|---|---|---|---|
| ENTFERNT | org.junit.jupiter:junit-jupiter | BOM/Parent | test | Legacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt. |
| NEU | org.springframework.boot:spring-boot-starter-aop | BOM/Parent | compile | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
| NEU | org.springframework.boot:spring-boot-starter-test | BOM/Parent | test | Neue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen. |
Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.
Migrationsbedeutung: Manueller Proxy wird durch deklarativen Interceptor ersetzt.
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);
}
}
}
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);
}
}
}
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 {
}
Migrationsbedeutung: Zielobjekt bleibt fachlich, erhält nur explizite Markierung.
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;
}
}
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;
}
}
--- 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;
}
Migrationsbedeutung: Composition Root wird vom Runner getrennt.
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));
}
}
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);
}
}
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));
}
}
| ID | Ebene | Prüfung | erforderlicher Nachweis |
|---|---|---|---|
| 02-AOP-A01 | Integration/Contract | Markierte Methoden werden genau einmal gemessen. | Automatisierter Test und CI-Protokoll |
| 02-AOP-A02 | Integration/Contract | Nicht markierte Methoden bleiben unbeeinflusst. | Automatisierter Test und CI-Protokoll |
| 02-AOP-A03 | Integration/Contract | Originalexception und Stacktrace bleiben erhalten. | Automatisierter Test und CI-Protokoll |
| 02-AOP-A04 | Integration/Contract | Keine rekursive Aspect-Ausführung. | Automatisierter Test und CI-Protokoll |
| 02-AOP-A05 | Integration/Contract | Pointcut- und Reihenfolgetests sind grün. | Automatisierter Test und CI-Protokoll |
| 02-AOP-T01 | Unit | Fachlogik ohne Container oder externen Dienst testen. | Unit-Test |
| 02-AOP-T02 | Regression | Legacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen. | Vergleichsreport |
| 02-AOP-T03 | Negative | Fehlerhafte, leere und grenzwertige Eingaben prüfen. | Negativtest |
| 02-AOP-T04 | Operations | Start, Health, Shutdown und Konfigurationsfehler prüfen. | Deployment-/Startprotokoll |
| 02-AOP-T05 | Lifecycle | Container-/Service-Lifecycle, Startup-Fehler und Recovery prüfen. | Runtime-Test |
| 02-AOP-F01 | Fokus | aspect activation, exception and ordering tests | Modulspezifischer Testreport |
| Risiko | Auswirkung | Gegenmaßnahme | Gate |
|---|---|---|---|
| proxy limitations and cross-cutting coupling | NIEDRIG | aspect activation, exception and ordering tests | vor Cutover |
| Neue Framework-/Library-Laufzeitabhängigkeiten | MITTEL | SBOM, Lizenz-, CVE- und transitive Dependency-Prüfung im Online-Build | vor Release |
| Legacy-Verhalten war implizit an entfernte Library gekoppelt | MITTEL | Contract-/Golden-Master-Tests und gezielte Fehlerpfade | vor Abschaltung |
Keine zusätzlichen Ressourcen.
Die Annotation kann deaktiviert und der bisherige Proxy als äußerer Adapter weiterverwendet werden, ohne den Calculator-Vertrag zu ändern.