Sunday, 5. July 2015
SW-Archäologie mit AspectJ (2)
javatux, 00:43h
1. Die klassische Herangehensweise
Dies ist ein sehr einfaches Modell einer Konto-Klasse, bei dem die Businesslogik (Einzahlung, Auszahlung, Überweisung) noch sehr gut erkennbar ist. Eine Warnung vorneweg: für Real-World-Implementierung bitte nie double als Datentyp verwenden, sonst kann es böse Überraschungen wegen Rundungsfehlern kommen (s. Ich bin reich!)
So langsam tritt damit die eigentliche Businesslogik immer mehr in den Hintergrund. Und es warten noch jede Menge weitere Anforderungen (oft auch als „Concerns“ bezeichnet), die technischer Natur sind und mit der eigentlichen Fachlichkeit nichts zu tun haben: Authorisierung, Sicherheit, GUI, Transaktionen, …).
Ein Vertreter aspekt-orientierter Sprachen ist AspectJ, die Java um einige Sprachmittel erweitert und damit das Herauslösen der Logging-Funktionalität aus der Konto-Klasse ermöglicht:
Um den Code besser zu verstehen, muss man erst einige Begrifflichkeiten verinnerlichen, die im nächsten Abschnitt vorgestellt werden. Übersetzt bedeutet dieser Code: Wenn sich der Kontostand ändert, gib eine Log-Meldung aus.
Aufruf (call) oder Ausführung (execution) einer Methode oder eines Konstruktors
Initialisierung einer Klasse oder eines Objekts (initialization, preinitialization, staticinitialization)
Zugriff auf eine Instanz-Variable (set, get)
Exception-Handling (handler)
Über Pointcuts lassen sich diese Joinpoints dann adressieren:
Den ersten Pointcut (setKontostand) kennen wir schon aus dem obigen Beispiel des LogAspects (Listing 3:) - er addressiert den schreibenden Zugriff auf das kontostand-Attribut. Interessanter ist der zweite Pointcut (callBankMethods): er adressiert alle Methoden der verschiedenen Konto-Klassen (genauer: die mit „...Konto“ im Namen aufhören und im bank-Paket liegen) und beliebige viele Argumente haben. Möglich wird dies durch die Unterstützung verschiedener Wildcards, die der Schlüssel für das Herausziehen der Crosscutting Concerns sind.
Das Ganze lässt sich auch mit boolschen Operatoren verknüpfen und mit weiteren Bedingungen kombinieren, sodass sich damit (fast) alle erdenklichen Szenarien realisieren lässt. Seit AspectJ 5 können auch noch Annotations zur Selektion herangezogen werden, was nicht nur für die Lesbarkeit und Wartbarkeit große Vorteile bietet.
vor (Before-Advice),
nach (After-Advice) oder
anstatt (Around-Advice)
des Pointcuts ausgeführt wird.
Hier wird nach dem Pointcut „setKontostand“ die entsprechende Log-Meldung ausgegeben. Die Verknüpfung mit dem args-Schlüsselwort von AspectJ dient in diesem Beispiel nur dazu, um auf das Argument (sprich: dem Attribut) des Pointcuts zugreifen zu können. Alternativ erlaubt AspectJ auch den Zugriff auf den Kontext des Joinpoints.
Beim Load-Time Weaving (LTW) werden die Aspekte während des Ladens der Java-Klassen „eingewebt“. Dies Technik wird z.B. vom Spring-AOP-Framework verwendet, das intern auf AspectJ aufsetzt. Vorteil hierbei ist, dass man mit den Aspekten eine größere Reichweite hat (prinzipiell jede Klasse, die geladen wird). Als Nachteil erkauft man sich hier Syntax-Fehler, die erst zur Laufzeit bemerkt werden.
AspectJ beherrscht beides und das AJDT-Plugin für Eclipse3 unterstützt neben der inkrementellen Kompilierung die Visualisierung von Pointcuts und das Debuggen von Advices.
3. Unterstützung durch AOP
2. Abstecher in die Welt von AOP
Um in die Begriffs- und Gedankenwelt von AOP (Aspekt-Orientierte Programmierung) einzutauchen, betrachten wir ein Beispiel aus dem Bank-Bereich:
public class Konto {
private double kontostand = 0.0;
public double abfragen() {
return kontostand;
}
public void einzahlen(double betrag) {
kontostand = kontostand + betrag;
}
public void abheben(double betrag) {
kontostand = kontostand - betrag;
}
public void ueberweisen(double betrag, Konto anderesKonto) {
abheben(betrag);
anderesKonto.einzahlen(betrag);
}
}
Listing 1: Einfache Konto-KlasseDies ist ein sehr einfaches Modell einer Konto-Klasse, bei dem die Businesslogik (Einzahlung, Auszahlung, Überweisung) noch sehr gut erkennbar ist. Eine Warnung vorneweg: für Real-World-Implementierung bitte nie double als Datentyp verwenden, sonst kann es böse Überraschungen wegen Rundungsfehlern kommen (s. Ich bin reich!)
2.1. Code-Verschmutzung
„Alle Kontobewegungen müssen protokolliert werden.“Diese gesetzliche Anforderung sollen jetzt umgesetzt werden. Dazu wird die Konto-Klasse wie folgt erweitert:
public class Konto {
private static Logger log = Logger.getLogger(Konto.class);
private double kontostand = 0.0;
public double abfragen() {
return kontostand;
}
public void einzahlen(double betrag) {
kontostand = kontostand + betrag;
log.info("neuer Kontostand: " + kontostand);
}
public void abheben(double betrag) {
kontostand = kontostand - betrag;
}
public void ueberweisen(double betrag, Konto anderesKonto) {
abheben(betrag);
anderesKonto.einzahlen(betrag);
log.info("neuer Kontostand: " + kontostand);
}
}
Listing 2: Konto-Klasse mit LoggingSo langsam tritt damit die eigentliche Businesslogik immer mehr in den Hintergrund. Und es warten noch jede Menge weitere Anforderungen (oft auch als „Concerns“ bezeichnet), die technischer Natur sind und mit der eigentlichen Fachlichkeit nichts zu tun haben: Authorisierung, Sicherheit, GUI, Transaktionen, …).
Separations of Concerns?
Während des Information-Studiums bekommt man mit Dijkstras „Separations of Concerns“ die Empfehlung mit auf den Weg, jeden „Concern“ in einer eigenem Modul oder Klasse zu kapseln, aber bereits dieses einfache Logging-Beispiel zeigt, dass das mit den Mitteln der Objekt-Orientierung gar nicht so einfach ist.2.2. Separation of Concerns
Ein Ausweg aus diesem Dilemma bietet die Aspekt-Orientierung. Sie bietet Konzepte an, um solche meist technischen Anforderungen (im AOP-Jargon als „Crosscutting-Conerns“ bezeichnet), in eigene „Aspekte“ kapseln zu können:Separations of Concerns!
AspectJ als Aspekt-orientierte Erweiterung von Java bietet dazu Sprachmittel, umEin Vertreter aspekt-orientierter Sprachen ist AspectJ, die Java um einige Sprachmittel erweitert und damit das Herauslösen der Logging-Funktionalität aus der Konto-Klasse ermöglicht:
public aspect LogAspect {
private static Logger log = Logger.getLogger(LogAspect.class);
pointcut setKontostand() :
set(double bank.Konto.kontostand);
after(double neu) : setKontostand() && args(neu) {
log.info("neuer Kontostand: " + neu);
}
}
Listing 3: LogAspectUm den Code besser zu verstehen, muss man erst einige Begrifflichkeiten verinnerlichen, die im nächsten Abschnitt vorgestellt werden. Übersetzt bedeutet dieser Code: Wenn sich der Kontostand ändert, gib eine Log-Meldung aus.
2.3. Do You Speak AOP?
Genauso wie SQL mit eigenen Begriffen wie Relationen und Normalform daherkommt, grenzt sich die Aspekt-Orientierung mit Ihrer eigenen Begriffswelt von anderen Techniken ab. Die wichtigsten davon sind:- Aspekt
- Joinpoints
- Pointcuts
- Advice
public aspect LogAspect {
// ...
}
Listing 4: Leerer LogAspect
2.3.1 Joinpoints und Pointcuts
Als Joinpoints werden im AOP-Jargon all die Punkte im Programm bezeichnet, auf die Einfluss genommen werden kann. Im Falle von AspectJ (und den meisten anderen AOP-Sprachen) sind dies:Aufruf (call) oder Ausführung (execution) einer Methode oder eines Konstruktors
Initialisierung einer Klasse oder eines Objekts (initialization, preinitialization, staticinitialization)
Zugriff auf eine Instanz-Variable (set, get)
Exception-Handling (handler)
Über Pointcuts lassen sich diese Joinpoints dann adressieren:
pointcut setKontostand() :
set(double bank.Konto.kontostand);
pointcut callBankMethods() :
call(* bank.*Konto.*(..));
Listing 5: Definition zweier PointcutsDen ersten Pointcut (setKontostand) kennen wir schon aus dem obigen Beispiel des LogAspects (Listing 3:) - er addressiert den schreibenden Zugriff auf das kontostand-Attribut. Interessanter ist der zweite Pointcut (callBankMethods): er adressiert alle Methoden der verschiedenen Konto-Klassen (genauer: die mit „...Konto“ im Namen aufhören und im bank-Paket liegen) und beliebige viele Argumente haben. Möglich wird dies durch die Unterstützung verschiedener Wildcards, die der Schlüssel für das Herausziehen der Crosscutting Concerns sind.
Das Ganze lässt sich auch mit boolschen Operatoren verknüpfen und mit weiteren Bedingungen kombinieren, sodass sich damit (fast) alle erdenklichen Szenarien realisieren lässt. Seit AspectJ 5 können auch noch Annotations zur Selektion herangezogen werden, was nicht nur für die Lesbarkeit und Wartbarkeit große Vorteile bietet.
2.3.2 Advices
Advices sind die Methoden der Aspekt-Orientierung: mit Ihnen kann ich die Punkte im Programm, die ich über die Pointcuts adressiert haben, um zusätzliche Funktionalität anreichern (oder einfacher: manipulieren). Dabei habe ich die Wahl, ob der Advicevor (Before-Advice),
nach (After-Advice) oder
anstatt (Around-Advice)
des Pointcuts ausgeführt wird.
after(double neu) : setKontostand() && args(neu) {
log.info("neuer Kontostand: " + neu);
}
Listing 6: After-AdviceHier wird nach dem Pointcut „setKontostand“ die entsprechende Log-Meldung ausgegeben. Die Verknüpfung mit dem args-Schlüsselwort von AspectJ dient in diesem Beispiel nur dazu, um auf das Argument (sprich: dem Attribut) des Pointcuts zugreifen zu können. Alternativ erlaubt AspectJ auch den Zugriff auf den Kontext des Joinpoints.
2.3.3 Der Webe-Vorgang
Wie kommen die Aspekte in das fertige Programm? Dazu gibt es Techniken wie:- Compile-Time Weaving (CTW)
- Load-Time Weaving (LTW)
Beim Load-Time Weaving (LTW) werden die Aspekte während des Ladens der Java-Klassen „eingewebt“. Dies Technik wird z.B. vom Spring-AOP-Framework verwendet, das intern auf AspectJ aufsetzt. Vorteil hierbei ist, dass man mit den Aspekten eine größere Reichweite hat (prinzipiell jede Klasse, die geladen wird). Als Nachteil erkauft man sich hier Syntax-Fehler, die erst zur Laufzeit bemerkt werden.
AspectJ beherrscht beides und das AJDT-Plugin für Eclipse3 unterstützt neben der inkrementellen Kompilierung die Visualisierung von Pointcuts und das Debuggen von Advices.
3. Unterstützung durch AOP
... comment