Aspektorientiertes Programmieren

Mit der Funktionalen Programmierung haben wir ja vor Kurzem bereits ein Programmierparadigma vorgestellt, welches den meisten Programmierern, die zumeist mit imparativen Programmiersprachen arbeiten. Heute möchten wir eine weiteres Paradigma präsentieren, welches erst vor ca. zehn Jahren von der Forschung auf die Welt der Programmierer losgelassen wurde und dennoch den meisten nicht ganz so befremdlich erscheint: Aspektorientiertes Programmieren, kurz AOP. Grund hierfür ist die Tatsache, dass es sich hierbei im Prinzip um kein eigenständiges Paradigma handelt, sondern vielmehr um eine Erweiterung für imparative Programmiersprachen.
Wofür der Programmierer AOP nun nutzt und wie das Ganze funktioniert, soll nun der folgende Artikel kurz beleuchten.

Wofür brauch ich das?
Oftmals ist es notwendig den Kern eines Programms (d.h., das was das Programm tatsächlich leisten soll) um eine Funktion zu erweitern, die jedoch nicht zwingend direkt etwas mit dem Programm an sich gemeinsam hat und die vielleicht auch nicht alle Nutzer benötigen. Manchmal muss man auch einfach an unzähligen Stellen einen Stück Quellcode via Copy&Paste einfügen. Für Debugging-Zwecke werden hier beispielsweise häufig an Tausenden Stellen Ausgaben nach dem Muster “Funktion xyz gestartet/beendet” erstellt. Das macht dem Entwickler nicht nur eine riesige Menge Arbeit, um dies einzufügen, sondern auch noch mehr Arbeit dies an allen Stellen wieder zu entfernen, damit der Otto-Normal-Nutzer damit nicht weiter belästigt wird.
Eine Abhilfe für den Programmierer haben dann glücklicherweise die Entwickler bei Xerox geschaffen, indem sie im Jahre 2001 eine Erweiterung für die Programmiersprache JAVA der Öffentlichkeit vorgestellten: AspectJ – die erste Sprache, die AOP unterstützt.

Nun erzähl schon! Wie wird das jetzt gelöst?
Grundlage von AspectJ ist eine Erweiterung um ein neues Sprachkonstrukt, welches allein vom Aufbau her stark an die üblichen Objekte aus Java erinnert: sogenannte Aspekte. Diese haben gleich mehrere mögliche Funktionen: Aspekte können u.a. Methoden und Eigenschaften von Objekten manipulieren, hinzufügen oder sogar komplett ersetzen – und all das, ohne den urspünglichen Programm-Code überhaupt anzufassen. Hierzu werden mittels sogenannten Pointcuts an vom Entwickler bestimmten Join-Points (bestimmte Ereignisse im urspünglichen Programm) Advices ausgeführt.

Hä?
Zugegebenermaßen klingt das auf den ersten Blick erst einmal verwirrend. Deswegen noch einmal etwas langsam:
Ein Join-Point ist ein bestimmtes Ereignis im Programm-Code. Dies können sein: Aufruf oder Ausführen einer Methode, das Lesen oder Schreiben einer Objekt-Eigenschaft, Instanziieren einer Klasse und noch einiges mehr.
Ein Pointcut ist nun ein Selektor für bestimmte Join-Points. Und ein Advice ist ein Programm-Code, der vor, nach oder anstelle eines über den Pointcut ausgewählten Join-Point ausgeführt wird.
Dies geschieht alles, ohne den eigentlichen Programm-Code zu verändern. In Compiler-Sprachen wie Java wird ganz einfach beim Kompilieren automatisch der Code aus den Advices an die entsprechenden Stellen im Programm-Code eingefügt, sodass das Programm danach ganz normal ausgeführt werden kann, so als seien die neuen Funktionen schon immer dort gewesen.

Ok, super. Und jetzt?
Ja, damit wären die theoretischen Grundlagen der AOP schon einmal geschafft. Kommen wir nun also zum praktischen Teil: Der Umsetzung im Programmcode. Hierfür bleiben wir weiterhin bei AspectJ – auch wenn es AOP bereits für deutlich mehr Programmier-Sprachen gibt (z.B. für Python, C++, PHP, Ruby).
Zunächst einmal der Grundaufbau:

aspect MeinAspekt [extends AndererAspekt] //optional
{
     pointcut MeinPointcut1(): <pointcuts>;
     //weitere Pointcuts
     before(): MeinPointcut1() {
          //hier der Advice
     }
     after(): MeinPointcutX() {
           //anderer Advice
     }
     Typ around(): AndererPointcut() {
          //Advice
          return bla; //bla ist Instanz von Klasse "Typ"
     }
     //noch viel mehr Advices
}

<pointcuts> ist an dieser Stelle als ein Platzhalter zu verstehen. Mögliche Werte können hierfür sein:

  • execution(EinTyp MeinObjekt.meineMethode(<parameter>)): Dieser Pointcut matcht auf das Ausführen der Methode “meineMethode” des Objektes “MeinObjekt” mit Rückgabetyp “EinTyp” und einer Parameterliste <parameter>, wobei dies wiederum hier nur ein Platzhalter darstellen soll. Stattdessen, werden hier die Typen der Parameter aufgelistet. Konstruktoren von Objekten matchen im Übrigen auf die Methode new().
  • call(AndererTyp AnderesObjekt.andereMethode(<parameter>)): Dies funktioniert praktisch genauso wie execution, wird allerdings beim Aufruf einer Methode ausgeführt, anstatt dem Ausführen der Methode.
  • set(EinTyp EinObjekt.einFeld) / get(EinTyp EinObjekt.einFeld): Dieser Pointcut wird beim Schreiben (set) bzw. Lesen (get) eines Objektfeldes aufgerufen.
  • args(<parameter>): Matcht auf die Parameter einer Mehode. Genauer gesagt, wird dieser Pointcut beim Aufrufen und Ausführen einer Methode mit der angegebenen Parameterliste gematcht.
  • within(EinObjekt) / withincode(EinTyp EinObjekt.eineMethode(<parameter>)): Hiermit lassen sich Pointcuts nach dem Ort ihres Vorkommens einschränken. Das soll heißen, dass ein Advice nur dann ausgeführt wird, wenn ein Pointcut innerhalb des Objektes “EinObjekt” bzw. innerhalb der Methode “eineMethode”.

Natürlich gibt es noch Unmengen weiterer Pointcuts. Zudem können auch mehrere Pointcuts miteinander kombiniert werden. Dies geschieht mit den bekannten logischen Kombinatoren &&, || sowie !. Außerdem gibt es auch noch sogenannte Wildcards. Hierbei kann mittels * ein einzelner Wert beliebig sein, mit .. mehrere Werte. So kann beispielsweise eine beliebige Methode von einem beliebigen Objekt mit unbestimmter Parameterliste als * *.*(..) abgekürzt werden. Es können auch Klassen ausgewählt werden, die nur von einer bestimmten Klasse erben. Die sieht so aus: EinTyp EineKlasse+.methode(..), wobei hier alle Methoden “methode” von “EineKlasse” abgeleiteten Klassen gematcht werden.

Um schließlich einen Advice einem Pointcut zuzuordnen verwendet man, wie oben schon gezeigt, around(), before() oder after(), wobei around() anstelle der aufgerufenen Methode ausgeführt wird (um die eigentliche Methode darin auszuführen gibt es die Funktion proceed()), before() unmittelbar vor dem Aufruf und after() direkt nach dem Aufruf der Funktion.

Etwas komplizierter ist es im Übrigen, innerhalb eines Advice die Funktionsparameter zu verwenden. Dies sieht dann in etwa wie folgt aus:

aspect MeinAspekt {
     pointcut meinPointcut(EinTyp variable): execution(AndererTyp Objekt.methode(EinTyp)) && args(variable);
     before(EinTyp variable): meinPointcut(variable) {
          //der Advice...
     }
}

Natürlich funktioniert dies genauso mit after und around.

Das sollte soweit als kleine Einführung in die Aspektorientierte Programmierung und in AspectJ im Speziellen. Wir hoffen, dass wir euch hiermit AOP ein wenig näher bringen konnten und sich vielleicht der eine oder andere etwas genauer mit dieser Thematik beschäftigen möchten. Wer sich noch genauer über AspectJ informieren möchte, kann beispielsweise in der Dokumentation Genaueres nachlesen.

Quelle: Teile dieses Artikels wurden inspiriert von Prof. Sattlers Vorlesung “Programmierparadigmen” an der TU Ilmenau.

Keine Kommentare

Es sind noch keine Kommentare vorhanden. Schreibe doch den Ersten!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*