Laufzeitanalysen mit Hilfe der Aspektorientierten ... - Feldschmid
Laufzeitanalysen mit Hilfe der Aspektorientierten ... - Feldschmid
Laufzeitanalysen mit Hilfe der Aspektorientierten ... - Feldschmid
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Abschlussdokument<br />
Aspektorientierte Programmierung<br />
Alexan<strong>der</strong> Burgess<br />
Andreas <strong>Feldschmid</strong><br />
Henrik Kemmesies<br />
Abdol Gholam<br />
Pierre Chavaroche<br />
21. Januar 2008
Name<br />
Arbeitspaket<br />
Pierre Chavaroche Kapitel 1 + 2<br />
Henrik Kemmesies Kapitel 3 + Abschlussdokument<br />
Andreas <strong>Feldschmid</strong> Kapitel 4 + 6.5<br />
Abdol Gholam Kapitel 5<br />
Alexan<strong>der</strong> Burgess Kapitel 6<br />
2
Inhaltsverzeichnis<br />
1 Was ist die Motivation AOP zu nutzen? 5<br />
2 Grundlagen AOP 7<br />
2.1 Begriffe, Grundkonzepte . . . . . . . . . . . . . . . . . . . . . . . . . . . 7<br />
2.1.1 Das allgemeine Prinzip von AOP . . . . . . . . . . . . . . . . . . 7<br />
2.1.2 Was genau ist ein Aspekt? . . . . . . . . . . . . . . . . . . . . . . 8<br />
2.1.3 Was ist ein Join-Point? . . . . . . . . . . . . . . . . . . . . . . . . 9<br />
2.1.4 Was ist ein Pointcut? . . . . . . . . . . . . . . . . . . . . . . . . . 9<br />
2.1.5 Wo kann ich den nun den zusätzlichen Code definieren? (Advice) 11<br />
2.1.6 Was ist eine Intertype-Declaration? . . . . . . . . . . . . . . . . . 12<br />
2.2 Vorstellung und Einrichtung <strong>der</strong> Entwicklungstools . . . . . . . . . . . . 12<br />
3 Laufzeitanalyse <strong>mit</strong> <strong>Hilfe</strong> von AOP 13<br />
3.1 Laufzeitinformationen über Programme . . . . . . . . . . . . . . . . . . . 14<br />
3.2 Erhebung von Laufzeitinformationen über Aspekte . . . . . . . . . . . . 16<br />
3.3 Grenzen <strong>der</strong> Laufzeitinformationsbeschaffung . . . . . . . . . . . . . . . . 18<br />
3.4 Anwendungsmöglichkeiten <strong>mit</strong> Laufzeitinformationen . . . . . . . . . . . 19<br />
4 Entwurf eines prototypischen Werkzeugs 20<br />
4.1 Architektur des Programms / Entscheidungen . . . . . . . . . . . . . . . 20<br />
4.2 Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23<br />
4.3 Implementierte Aspekte . . . . . . . . . . . . . . . . . . . . . . . . . . . 26<br />
5 Einbinden benutzerspezifischer Aspekte in den Prototyp 29<br />
6 Benutzung des Prototyps 34<br />
6.1 Was leistet das Programm? . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />
6.2 Wie benutzt man das Programm? . . . . . . . . . . . . . . . . . . . . . . 34<br />
6.3 Einbinden in ein Eclipseprojek . . . . . . . . . . . . . . . . . . . . . . . . 35<br />
6.4 Einbinden über den Codeweaver . . . . . . . . . . . . . . . . . . . . . . . 36<br />
6.5 Ausführen <strong>mit</strong>tels Ant-Task . . . . . . . . . . . . . . . . . . . . . . . . . 39<br />
6.6 Was wird Ausgegeben? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41<br />
Literaturverzeichnis 42<br />
Abbildungsverzeichnis 43<br />
3
Listings 44<br />
4
1 Was ist die Motivation AOP zu<br />
nutzen?<br />
Eine <strong>der</strong> großen Herausfor<strong>der</strong>ungen <strong>der</strong> heutigen Softwareentwicklung ist es, die verschiedenen<br />
Verantwortlichkeiten und Aufgaben einer Software und ihrer Komponenten sauber<br />
zu trennen (Das Prinzip des ”<br />
Seperation of Concerns“). Nur so können Abhängigkeiten<br />
minimiert und eine lange Lebensdauer <strong>der</strong> Software garantiert werden.<br />
Allerdings gibt es oft nicht-funktionale Anfor<strong>der</strong>ungen, die über den gesamten Code<br />
verteilt sind (Die sogenannten ”<br />
Crosscutting Concerns“).<br />
Abbildung 1.1: Logging in Apache Tomcat<br />
Wie man in Abbildung 1.1 sehen kann, ist das Logging über fast alle Module <strong>der</strong> Software<br />
verstreut, da es offensichtlich oft benötigt wird. Obwohl es nur eine einzige Anfor<strong>der</strong>ung<br />
(bzw. Aufgabe) ist, muss für die Realisierung Code in vielen Moduln geän<strong>der</strong>t werden.<br />
Weitere Beispiele für Crosscutting Concerns sind:<br />
• Exception-Handling<br />
• Performance<br />
• Security<br />
5
Abbildung 1.2: XML-Parsing in Apache Tomcat<br />
Das Gegenbeispiel stellt Abbildung 1.2 dar: Das XML-Parsing ist nur in einem einzigen<br />
Modul vertreten.<br />
Eine Verteilung über den gesamten Softwarecode (wie in Abbildung 1.1) hat zur Folge,<br />
dass<br />
1. eine Än<strong>der</strong>ung jener Anfor<strong>der</strong>ungen gleichzeitig eine Än<strong>der</strong>ung an mehreren Stellen<br />
im Code nach sich zieht (Fehlergefahr und geringe Produktivität).<br />
2. <strong>der</strong> eigentliche, fachliche Code kaum mehr zu lesen ist, da er völlig von nichtfunktionalen<br />
Anfor<strong>der</strong>ungen durchsetzt ist.<br />
Doch wie kann AOP hier jetzt helfen? Die Aspektorientierte Programmierung hat es sich<br />
zur Aufgabe gemacht, diese Crosscutting Concerns vom eigentlichen, fachlichen Code zu<br />
kapseln und so<strong>mit</strong> zu trennen.<br />
6
2 Grundlagen AOP<br />
AOP ist ein mächtiges Werkzeug. Wir möchten hier noch einmal kurz die wichtigsten<br />
Konzepte von AspectJ vorstellen. Für genauere Einblicke können wir unsere Einführungs-<br />
Präsentation zum Thema AOP bzw. die AspectJ-Dokumentation unter [Aspb] sowie die<br />
Bücher [Lad03] und [Böh06] empfehlen.<br />
2.1 Begriffe, Grundkonzepte<br />
Wie in den meisten an<strong>der</strong>en Techniken, gibt es auch in AOP einige Begriffe und Konzepte<br />
zu erklären, bevor man <strong>mit</strong> AOP wirklich arbeiten kann. Die Prinzipien sind aber, einmal<br />
verstanden, sehr schlüssig.<br />
2.1.1 Das allgemeine Prinzip von AOP<br />
Eine grobe Übersicht wie AOP funktioniert, sieht man in Abbildung 2.1. Links ist <strong>mit</strong><br />
Methode1 und Methode2 ein sequentieller Programmablauf vorzufinden, wie man es aus<br />
Programmen gewöhnt ist. Nun gibt es in diesem Programmablauf bestimmte Punkte<br />
(Die Join-Points) an denen jetzt zusätzlicher Code hinzugefügt werden könnte. Dieser<br />
zusätzliche Code ist in sogenannten Aspekten gekapselt. Der Pointcut wählt diejenigen<br />
Joinpoints aus, an welchen zusätzlicher Code hinzugefügt werden soll. Das Hinzufügen<br />
des Codes an die ausgewählten Joinpoints wird in AOP ”<br />
Weaving“ (Verweben) genannt.<br />
7
Abbildung 2.1: Wo AOP angreift<br />
2.1.2 Was genau ist ein Aspekt?<br />
Ein Aspekt ist einer Java-Klasse sehr ähnlich: Er besitzt genauso Methoden und Variablen<br />
wie man es von einer Java-Klasse gewöhnt ist. Ein Aspekt enthält aber zusätzliche<br />
Komponenten um die AOP-Techniken anwenden zu können. In AspectJ kann einem die<br />
Syntax für einen Aspekt sehr bekannt vorkommen:<br />
Listing 2.1: Ein Aspekt in AspectJ<br />
public aspect KontoAspekt<br />
[ implements ] [ extends ][ per−Klausel ]<br />
{<br />
//Code<br />
}<br />
Zusätzlich hat er die Endung .aj um sich von den normalen Java-Klassen zu unterscheiden.<br />
8
2.1.3 Was ist ein Join-Point?<br />
Join-Points sind über den gesamten Code verteilt. Es sind die Punkte, an denen <strong>der</strong><br />
Compiler die zusätzlichen Anweisungen aus den Aspekten ”<br />
einweben“ kann. Aufgrund<br />
dessen werden sie oft auch als ”<br />
Webepunkte“ bezeichnet. Ein Java-Beispiel:<br />
Abbildung 2.2: Eine Java-Klasse Konto <strong>mit</strong> Beispiel-Join-Points<br />
Abbildung 2.2 zeigt, wo ein Join-Point sein kann: Bei <strong>der</strong> Instanziierung einer Klasse,<br />
beim Ausführen einer Methode, beim Beschreiben einer Variablen, usw.<br />
2.1.4 Was ist ein Pointcut?<br />
Ein Pointcut fasst nun eine Menge von Join-Points zusammen und wird im jeweiligen<br />
Aspekt definiert. Er kann keinen Join-Point enthalten, alle Join-Points o<strong>der</strong> im Idealfall<br />
9
genau diejenigen, die man für seinen Aspekt benötigt. Sollte <strong>der</strong> Programmablauf zu<br />
einem Join-Point kommen und <strong>der</strong> Pointcut enthält diesen, wird <strong>der</strong> zusätzliche Code<br />
im Aspekt an dieser Stelle ausgeführt. Ein paar Java-Beispiele:<br />
Allgemeine Syntax:<br />
pointcut [ pointCutName ( ) ] : [ Auswahlmethode ] ;<br />
Ein konkreter Pointcut, <strong>der</strong> einen Join-Point aus <strong>der</strong> Java-Klasse ”<br />
Konto“ enthält:<br />
pointcut KontoSelect ( ) : c a l l ( void Konto . abheben ( int ) ) ;<br />
Sobald die Methode abheben aus <strong>der</strong> Klasse Konto ”<br />
gecallt“ (d.h. aufgerufen) wird, setzt<br />
<strong>der</strong> dafür vorgesehene zusätzliche Code ein.<br />
Der selbe Pointcut <strong>mit</strong> einem logischen Operator, um einen zusätzlichen Join-Point zu<br />
selektieren:<br />
pointcut KontoSelect ( ) : c a l l ( void Konto . abheben ( int ) )<br />
| | c a l l ( void Konto . e i n z a h l e n ( int ) ) ;<br />
Der Pointcut trifft dann zu, wenn entwe<strong>der</strong> die Methode abheben o<strong>der</strong> einzahlen aufgerufen<br />
wird.<br />
Diesmal <strong>mit</strong> Wildcards:<br />
pointcut KontoSelect ( ) : c a l l (∗ Konto . abheben ( . . ) ) ;<br />
Sollte es Überladungen <strong>der</strong> Methode abheben geben, sind diese durch den Pointcut ebenfalls<br />
betroffen.<br />
Anmerkung: Logische Operatoren und Wildcards können kombiniert werden.<br />
Neben <strong>der</strong> Pointcut-Methode call gibt es noch viele weitere Methoden:<br />
• execution(Methoden- o<strong>der</strong> Konstruktorname)<br />
• handler(Exception)<br />
• args(Parameter)<br />
• this(Instanzenname)<br />
• get(Variable)<br />
• set(Variable)<br />
Dies sind nur Einige unter vielen Weiteren, worauf hier aber nicht näher eingegangen<br />
wird.<br />
10
2.1.5 Wo kann ich den nun den zusätzlichen Code definieren?<br />
(Advice)<br />
Abbildung 2.3: Der Advice<br />
In Abbildung 2.3 sieht man die Rolle des Advice: Er befindet sich ebenfalls im Aspekt<br />
und enthält den Code, <strong>der</strong> an einem Join-Point ausgeführt werden soll.<br />
Beispiel für einen Advice:<br />
b e f o r e ( ) : KontoSelect ( )<br />
{<br />
System . out . p r i n t l n ( ”Es wird etwas von einem Konto abgehoben ! ” ) ;<br />
}<br />
Bevor die Methode Konto.abheben (definierter Join-Point im Pointcut KontoSelect() )<br />
aufgerufen wird, wird eine System.out.-Meldung ausgegeben. Neben before gibt es auch<br />
11
noch after (Ausführung nach einem Join-Point) und around (umschließt einen Join-<br />
Point, bzw. kann ihn auch ersetzen).<br />
2.1.6 Was ist eine Intertype-Declaration?<br />
Mit <strong>der</strong> Intertype-Declaration ist es sogar möglich, zusätzliche Variablen und Methoden<br />
bestehenden Klassen hinzuzufügen.<br />
Beispiel:<br />
public aspect ZusatzAttribut {<br />
private int Konto . z i n s s a t z = 2 ;<br />
}<br />
public int Konto . g e t Z i n s s a t z ( ) {<br />
return z i n s s a t z ;<br />
}<br />
Im Aspekt ZusatzAttribut wird <strong>der</strong> Klasse Konto ein weitere Variable hinzugefugt: Der<br />
Zinssatz. Zusätzlich erhält sie eine Get-Methode.<br />
2.2 Vorstellung und Einrichtung <strong>der</strong><br />
Entwicklungstools<br />
Wie auch schon für die Beispiele aus Kapitel 1, haben wir für unser Projekt den Standard<br />
AspectJ gewählt. Der Vorteil von AspectJ ist die Aufwärtskompatibilität: So<strong>mit</strong><br />
ist korrekter Java-Code auch korrekter AspectJ-Code. Zusätzlich erzeugt <strong>der</strong> AspectJ-<br />
Compiler ganz normalen Java-Bytecode, d.h. es läuft auf unverän<strong>der</strong>ter JVM und ist<br />
so<strong>mit</strong> auch Plattformkompatibel.<br />
Die Integration in Eclipse funktioniert wie folgt: Unter http://www.eclipse.org/ajdt/<br />
findet man die neusten AJDT (AspectJ Development Tools). Laden Sie sich hier die<br />
entsprechende .zip-Datei herunter. Anschließend kopieren Sie die Dateien aus dem Ordner<br />
”<br />
features“ und ”<br />
plugins“ in die gleichnamigen Ordner aus Ihrem Eclipse-Verzeichnis.<br />
Natürlich ist eine Installation auch automatisch über die Eclipse Update-Funktion möglich<br />
(die Update-Site URL ist auch auf <strong>der</strong> AJDT-Seite zu finden).<br />
12
3 Laufzeitanalyse <strong>mit</strong> <strong>Hilfe</strong> von<br />
AOP<br />
Wo gehobelt wird, da fallen Späne“ - und wo programmiert wird, da entstehen Fehler.<br />
”<br />
Dieser schlaue Satz dürfte jedem heranwachsenden Informatiker wohlbekannt sein. Bei<br />
Unachtsamkeit können Fehler zu finanziellen o<strong>der</strong> sogar im schlimmsten Fall zu menschlichen<br />
Verlusten führen. Allein im Jahr 1999 verlor die NASA 125 Millionen Dollar durch<br />
einen verpassten Mars-Landeanflug <strong>der</strong> Sonde Mars Climate Orbiter“. Der Grund: Eine<br />
”<br />
fehlerhafte Implementierung des Maßsystems [Mar].<br />
Zur Vermeidung solcher Programmierfehler kennt die Informatik eine Antwort: Softwaretests.<br />
Noch bevor eine Software den Unit-Tests unterzogen wird, sollte sie einer<br />
statischen Code-Analyse unterzogen werden. Die statische Code-Analyse überprüft den<br />
Quelltext eines Programms auf eine Reihe vorher festgelegter Regeln. Dadurch können<br />
beispielsweise folgende Fälle identifiziert und behoben werden:<br />
• fehlende Vorinitialisierung von Variablen<br />
• Identifikation von unerreichbaren Codestellen<br />
• Überschreitung <strong>der</strong> maximalen Lines of Code (LoC) einer Methode<br />
• fehlendes Implementieren eines Interfaces<br />
Teilweise wird die statische Analyse durch die Zuhilfenahme von Stylecheckern ergänzt,<br />
die den Quelltext auf vorher festgelegte Programmierstile überprüfen.<br />
Die statische Code-Analyse hat allerdings den Nachteil, dass Programmierfehler, die<br />
erst während <strong>der</strong> Programmausführung ”<br />
entstehen“, nicht als solche identifiziert werden<br />
können. Um diesem Problem entgegenzuwirken muss das Programm zur Laufzeit, durch<br />
eine dynamische Code-Analyse, analysiert werden. Hierbei sind nicht nur Informationen<br />
an einer ganz bestimmten Programmstelle von Interesse, son<strong>der</strong>n auch <strong>der</strong> allgemeine<br />
Zustand und <strong>der</strong> Ablauf des Programms können eine wichtige Rolle spielen.<br />
Dynamische Informationen über ein laufendes Programm nennt man Laufzeitinformationen.<br />
Sie können <strong>mit</strong> <strong>Hilfe</strong> <strong>der</strong> <strong>Aspektorientierten</strong> Programmierung aus dem Programmfluss<br />
herausgeholt werden. Bevor dies allerdings durchgeführt werden kann, muss<br />
zunächst überlegt werden, welche Laufzeitinformationen eines Programms überhaupt<br />
von Interesse sind.<br />
13
3.1 Laufzeitinformationen über Programme<br />
Generell muss bei dem Begriff ”<br />
Laufzeitinformationen“ von einem unbekannten Projekt<br />
ausgegangen werden. Ein Projekt ist dann unbekannt, wenn <strong>der</strong> innere Aufbau und die<br />
innere Funktionsweise unbekannt sind. Man spricht in diesem Fall von einer Blackbox.<br />
Abbildung 3.1: Blackbox-Ansicht eines Projekts<br />
Betrachtet man nun eine Blackbox, wie sie in <strong>der</strong> Abbildung 3.1 zu sehen ist, so beginnt<br />
man automatisch, sich Fragen über das vorliegende Objekt und dessen Laufzeitinformationen<br />
zu stellen. Mögliche Laufzeitinformationen werden nun im Folgenden strukturiert<br />
erfasst. Als Grobeinteilung wurden die Strukturierungspunkte Klassen, Methoden, Einund<br />
Ausgaben, Threads, Exceptions, Technisches und Sonstiges gewählt:<br />
• Klassen<br />
– Zählen <strong>der</strong> Instanzen einer bestimmten Klasse<br />
– Einhalten vorgeschriebener Schnittstellen<br />
– Korrektes Abbilden <strong>der</strong> Instanzen auf die vorgegebene Softwarearchitektur<br />
– Rekonstruierung <strong>der</strong> Softwarearchitektur<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Aufrufreihenfolge <strong>der</strong> Komponenten<br />
(ruft obere immer untere auf?)<br />
• Methoden<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Anzahl <strong>der</strong> Aufrufe einer Methode<br />
– Messung <strong>der</strong> Ausführungszeit einer Methode<br />
– Auffinden von ungenutzten Methoden<br />
– Auffinden von ungenutzten Argumente<br />
14
• Threads<br />
– Auffinden vorhandener Threads<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Anzahl von Threads zu einem gewissen Zeitpunkt<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Anzahl von Threads über den Gesamtablauf des Programms<br />
– Messen <strong>der</strong> Ausführungszeit eines Threads<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Größe <strong>der</strong> zugeteilten Zeitscheibe an einen Thread<br />
– Auffinden von ungenutzten Threads<br />
– Auffinden von Deadlocks<br />
– Messen <strong>der</strong> Wartezeit eines Threads auf gesperrte Objekte<br />
• Ein- und Ausgaben<br />
– Erfassen von Logging-Informationen<br />
– Erfassen von gelesenen und geschriebenen Inhalten einer Datei<br />
– Verwendung von Dateien<br />
– Erfassen <strong>der</strong> Anzahl von verwendeten Dateien<br />
– Korrektes Öffnen und Schließen <strong>der</strong> Dateien<br />
– Verwendung von Netzwerkverbindungen<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Verwendungsdauer externer Quellen<br />
(Dateien und Netzwerkverbindungen)<br />
– Er<strong>mit</strong>tlung von verschickten Informationen über das Netzwerk<br />
– Erfassung von Benutzereingaben (Konsole / GUI)<br />
• Exceptions<br />
– Er<strong>mit</strong>tlung aller geworfenen Exceptions<br />
– Auffinden unvorhergesehener Exceptions<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Anzahl und des Zeitpunkts von geworfenen Exceptions<br />
• Technisches<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Anzahl benötigter Speicherzellen<br />
(im Hinblick auf Embedded Systems)<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Speicheradresse von Variable X<br />
– Messen <strong>der</strong> Speicherbelastung des Programms<br />
– Er<strong>mit</strong>tlung <strong>der</strong> Belegungenen <strong>der</strong> Prozessorregister<br />
– Auffinden von fehlenden Speicherfreigaben<br />
• Sonstiges<br />
– Protokollierung des detaillierten Programmablaufs<br />
(Wann hat Benutzer was gedrückt?)<br />
15
– Erkennung von Endlosschleifen<br />
– Ausgeben von SQL-Statements<br />
– Er<strong>mit</strong>tlung des Ausführungsorts des Programms<br />
– Er<strong>mit</strong>tlung des Bauplans <strong>der</strong> GUI<br />
– Zählen von GUI-Elementen<br />
Hinweis: Diese Liste hat nicht die Anfor<strong>der</strong>ung vollständig zu sein. Sie soll lediglich<br />
einen Überblick über mögliche Laufzeitinformationen geben.<br />
3.2 Erhebung von Laufzeitinformationen über<br />
Aspekte<br />
Nachdem nun ein Überblick über mögliche Laufzeitinformationen erstellt wurde, sind<br />
diese nun aus dem Black-Box-Programm zu extrahieren. Dazu wird im Weiteren die<br />
Laufzeitinformation ”<br />
Ausgeben von SQL-Statements“ als Erklärungsbeispiel herangezogen.<br />
Wie bereits zu Beginn dieses Kapitels erwähnt, verschafft AOP <strong>mit</strong> <strong>Hilfe</strong> <strong>der</strong> Aspekte die<br />
Möglichkeit, dieses Vorhaben umzusetzen. Das Black-Box-Projekt muss dazu im ersten<br />
Schritt in ein AspectJ-Projekt umgewandelt werden. Dies geschieht durch die Auswahl<br />
des Menüpunktes ”<br />
Rechtsklick auf den Black-Box-Projektordner“ −→ ”<br />
AspectJ Tools“<br />
−→ ”<br />
Convert to AspectJ Project“.<br />
Abbildung 3.2: Umwandlung eines Java-Projekts in ein AspectJ-Project<br />
Anschließend sollte in dem Black-Box-Projektordner ein neues Package für die zu implementierenden<br />
Aspekte erstellt werden. In dem Aspekt-Package muss nun im zweiten<br />
Schritt ein Aspekt erstellt werden. Der Aspekt wird über ”<br />
Rechtsklick auf Aspekt-<br />
Package −→ New −→ Other. . . −→ AspectJ −→ Aspect“ <strong>mit</strong> einem beliebigen Namen<br />
16
erstellt. Ein Aspekt ist grundsätzlich ein Behälter für AOP-Code und am ehesten <strong>mit</strong><br />
einer Java-Klasse zu vergleichen – nur dass <strong>der</strong> Aspekt statt *.java auf *.aj endet.<br />
Nach <strong>der</strong> Erstellung des Aspekts, ist in <strong>der</strong> *.aj-Datei bereits folgen<strong>der</strong> Code enthalten:<br />
Listing 3.1: Die erste Codezeile vom Aspekt SqlStatementsAspect<br />
public aspect SqlStatementsAspect { . . . }<br />
Nun muss geklärt werden, welche Sprachkonstrukte die gesuchte Laufzeitinformation<br />
charakterisieren. Diese Charakterisierung ist beson<strong>der</strong>s für die Erstellung <strong>der</strong> Join-Points<br />
wichtig. Im Fall <strong>der</strong> ”<br />
Ausgeben von SQL-Statements“-Laufzeitinformation genügt ein<br />
kurzer Blick in die Java-Dokumentation im Abschnitt java.sql.Statement unter http://<br />
java.sun.com/javase/6/docs/api/java/sql/Statement.html , woraus hervor geht,<br />
dass jedes SQL-Statement unter JDBC über eine <strong>der</strong> execute*-Methoden des Statement-<br />
Interfaces abgesetzt wird (die addBatch()-Methode wird hier ignoriert).<br />
Anschließend kann durch diese Charaktereigenschaften <strong>der</strong> Pointcut <strong>mit</strong> seinen Join-<br />
Points definiert werden (in unserem Laufzeitinformationen-Fall existiert nur ein Join-<br />
Point!). Dazu wird zuerst <strong>der</strong> Pointcut <strong>mit</strong> einem Namen und einem abschließenden ”<br />
:“<br />
angegeben, gefolgt von den Join-Point-Definitionen:<br />
Listing 3.2: Pointcut- und Join-Point-Definition im Aspekt SqlStatementsAspect<br />
pointcut getSqlStatement ( S t r i n g s q l ) :<br />
c a l l (∗ java . s q l . Statement . execute ∗ ( . . ) ) && args ( sql , . . ) ;<br />
Sollen weitere Join-Points angegeben werden, so geschieht dies durch das Anhängen des<br />
O<strong>der</strong>-Operators ‖ an den ersten Join-Point (nach ”<br />
&& args(sql, ..)“ ), gefolgt von den<br />
weiteren Join-Point-Definitionen. Um nicht jede <strong>der</strong> execute*-Methoden einzeln angeben<br />
zu müssen, können Wildcards eingesetzt werden. Ein Stern * steht für eine beliebige<br />
Anzahl beliebiger Zeichen (außer dem Punkt). Zwei Punkte .. stehen für eine beliebige<br />
Anzahl von beliebigen Zeichen (einschließlich dem Punkt) und ein Plus + steht für<br />
sämtliche Unterklassen bzw. Interfaces des vorgegebenen Typs. Ist zusätzlich <strong>der</strong> Zugriff<br />
auf die Argumente einer Methode nötig, so muss das Schlüsselwort ”<br />
args“ <strong>mit</strong> Angabe<br />
<strong>der</strong> erwarteten Argumente dem Join-Point hinzugefügt werden.<br />
Nachdem <strong>der</strong> Einstiegspunkt unserer Interessen definiert ist, muss nun <strong>der</strong> auszuführende<br />
Code an dieser Stelle definiert werden. Dies wird in einem before-, after- o<strong>der</strong> einem<br />
around-Advice <strong>mit</strong> Angabe des Pointcuts festgelegt:<br />
Listing 3.3: Advice-Definition im Aspekt SqlStatementsAspect<br />
b e f o r e ( S t r i n g s q l ) : getSqlStatement ( s q l ) {<br />
System . out . p r i n t l n ( ”SQL−Statement ’ ”+s q l+” ’ was c a l l e d ” ) ;<br />
}<br />
17
Wurde alles richtig implementiert, so wird in <strong>der</strong> Entwicklungsumgebung ”<br />
Eclipse“ links<br />
neben dem Aspekt ein kleiner Pfeil angezeigt, <strong>der</strong> beim Herüberfahren <strong>mit</strong> <strong>der</strong> Maus die<br />
anvisierte Stelle im Black-Box-Projekt anzeigt.<br />
Abbildung 3.3: Der Aspekt Ausgeben von SQL-Statements zeigt auf eine Codestelle<br />
Umgekehrt wird bei <strong>der</strong> anvisierten Stelle im Black-Box-Projekt ein entgegengesetzter<br />
Pfeil angezeigt:<br />
Abbildung 3.4: Main-Methode, auf die <strong>der</strong> Aspekt Ausgeben von SQL-Statements zeigt<br />
Allerdings sind <strong>der</strong> Erfassung <strong>der</strong> Laufzeitinformationen gewisse Grenzen gesetzt, die im<br />
Folgenden untersucht werden.<br />
3.3 Grenzen <strong>der</strong> Laufzeitinformationsbeschaffung<br />
Mit AOP können keine echten Ausführungszeiten einer Methode o<strong>der</strong> eines Teilbereichs<br />
des Programms er<strong>mit</strong>telt werden. Für eine Zeitmessung muss vor und nach dem zu<br />
messenden Code zusätzlicher AOP-Code eingefügt werden. Dieser zusätzliche Code ist,<br />
zusätzlich zum normalen Code, während <strong>der</strong> Programmausführung auszuführen, wodurch<br />
wie<strong>der</strong>um zusätzliche Zeit beansprucht wird und so<strong>mit</strong> das Gesamtergebnis einer<br />
gemessenen Ausführungszeit verfälscht wird. Die <strong>mit</strong> AOP er<strong>mit</strong>telten Ausführungszeiten<br />
18
sind daher immer nur Näherungswerte, die allerdings trotzdem gut für Vergleichszwecke<br />
herangezogen werden können.<br />
Mit AOP ist es zudem nicht möglich, den Code zwischen einem try- und catch-Block o<strong>der</strong><br />
den Ausführungsort eines Threads in einem Join-Point anzugeben. Auch das Erfassen<br />
von technischen Informationen, wie z.B. die Anzahl <strong>der</strong> benötigten Speicherzellen für ein<br />
Objekt, ist beim managed Code, wie z.B. Java, nicht möglich, da AOP ebenfalls wie <strong>der</strong><br />
managed Code nur in einer Sandbox abläuft und keinen Zugriff auf die Arbeitsweisen<br />
<strong>der</strong> Virtual Machine hat.<br />
3.4 Anwendungsmöglichkeiten <strong>mit</strong><br />
Laufzeitinformationen<br />
Wurden einige Laufzeitinformationen aus einem Programm <strong>mit</strong> den entsprechenden<br />
Aspekten extrahiert, so stellt sich nun die Frage, wie diese weiterzuverarbeiten sind.<br />
Ein beson<strong>der</strong>s wichtiger Anwendungsfall ist die Grundlagenbildung für einen Profiler.<br />
Dieser müsste allerdings zusätzlich <strong>mit</strong> statischen Analysen ergänzt werden, um das<br />
volle Spektrum <strong>der</strong> Softwareanalyse nutzen zu können.<br />
Weiter könnten die Laufzeitinformationen für die Erstellung von Sequenzdiagrammen<br />
o<strong>der</strong> zum Kennenlernen eines Programms verwendet werden. Unter Kennenlernen ist<br />
hier zu verstehen, dass ein Entwickler die Aufgabe bekommen hat, eine ihm unbekannte<br />
Software um gewisse Funktionalitäten zu erweitern / abzuän<strong>der</strong>n ohne dabei auf Softwaredokumentation<br />
o<strong>der</strong> früher beteiligte Entwickler zurückzugreifen. Durch entsprechende<br />
Laufzeitinformationen könnten hier schnell und einfach die vorhandene Softwarearchitektur,<br />
die dafür benötigt wird, und <strong>der</strong> Programmablauf <strong>der</strong> zu erweiternden Software<br />
er<strong>mit</strong>telt werden.<br />
Soll darüber hinaus ein Programm aufgrund schlechter Performance optimiert werden,<br />
so könnte es sehr wichtig sein, die Methode <strong>mit</strong> <strong>der</strong> längsten Ausführungsdauer zu identifizieren.<br />
Auch dies ist <strong>mit</strong> Laufzeitinformationen möglich.<br />
Ebenfalls sind Laufzeitinformationen für das Testen von Software geeignet: Es wird vor<br />
<strong>der</strong> Ausführung eines Programms abgeschätzt, wie lange eine gewisse Methode benötigen<br />
darf. Danach wird die Software ausgeführt und die theoretisch er<strong>mit</strong>telte Zeit <strong>mit</strong> <strong>der</strong><br />
tatsächlichen Ausführungszeit verglichen. Unterscheiden sich diese Werte gravierend, so<br />
könnte dies bereits ein Indiz für fehlerhaften Code sein. Eine weitere Möglichkeit des<br />
Testens wäre eine eigene Implementierung, auf Basis von Laufzeitinformationen, für das<br />
Tracen eines Programms.<br />
Auch können Laufzeitinformationen zur Softwarequalitätsicherung <strong>der</strong> Softwarearchitektur<br />
eingesetzt werden, indem die Soll-Architektur <strong>mit</strong> <strong>der</strong> tatsächlichen Implementierung<br />
abgeglichen wird und dabei Komponenten überführt werden, die an<strong>der</strong>e Komponenten<br />
verbotener Weise übergehen.<br />
19
4 Entwurf eines prototypischen<br />
Werkzeugs<br />
In diesem Abschnitt wird das von uns implementierte prototypische Werkzeug kurz<br />
vorgestellt.<br />
Gerade beim Profiling beziehungsweise <strong>der</strong> Analyse von Methodenlaufzeiten, muss viel<br />
Wert auf Performance und Skalierbarkeit gelegt werden, da<strong>mit</strong> ein möglichst unverfälschtes<br />
Ergebnis erzielt werden kann. Die von uns implementierten Aspekte sowie die erstellte<br />
Architektur wurden nicht speziell auf einen performanceorientierten o<strong>der</strong> gar skalierbaren<br />
Ansatz optimiert. Die erstellte Lösung ist also eher als ”<br />
Prototyp“ o<strong>der</strong> ”<br />
Proof of<br />
Concept“ zu verstehen – die generierten Ausgaben können als Richtwerte herangezogen<br />
werden, sind aber auf jeden Fall kritisch zu hinterfragen.<br />
4.1 Architektur des Programms / Entscheidungen<br />
Um die Architektur des Programms verstehen zu können, ist es wichtig die Grundzüge<br />
Aspektorientierter Programmierung zu verstehen – siehe dazu Kapitel 1.<br />
Bei <strong>der</strong> Analyse <strong>der</strong> Architekturanfor<strong>der</strong>ungen an unser Programm wurde schnell klar,<br />
dass eine <strong>der</strong> Hauptanfor<strong>der</strong>ungen die Interoperabilität ist, also die Möglichkeit unser<br />
Werkzeug ohne große Anpassungen auf ein beliebiges Java-Projekt anwenden zu können.<br />
Diese Funktion wird durch AspectJ und so<strong>mit</strong> von den verschiedenen Aspekten von<br />
Haus aus bestens Unterstützt – wir mussten also nur noch darauf achten beim Sammeln,<br />
Verwalten und Ausgeben <strong>der</strong> Daten keine projektspezifischen Abhängigkeiten zu<br />
generieren.<br />
Um die Informationen, die von den einzelnen Aspekten gesammelt werden auswerten<br />
und eventuell darstellen zu können, muss eine Ausgabe stattfinden. Dies könnte nun<br />
per Ausgabe in eine Datei, Schreiben in eine Datenbank, auf einen Socket o<strong>der</strong> eine<br />
MessageQueue realisiert werden. Wir entschieden uns <strong>der</strong> Einfachheit halber für eine<br />
simple Ausgabe in eine Datei. Dabei entschieden wir uns für eine Ausgabe im XML-<br />
Format. Auch dafür musste eine – soweit überhaupt möglich – gemeinsame Struktur<br />
festgelegt werden. Einer <strong>der</strong> Gründe für das XML-Ausgabeformat war die Möglichkeit<br />
das erzeugte XML-Dokument <strong>mit</strong>tels XSLT in verschiedene an<strong>der</strong>e Formate (z.B. CSV,<br />
SVG, XHTML usw.) transformieren zu können.<br />
20
Ein zusätzlicher wichtiger Punkt in diesem Kontext ist die Fragestellung, wann die Ausgabe<br />
erfolgt. Bei jedem Funktionsaufruf mehrere XML-Java-Elemente zu erstellen und<br />
diese dann in eine XML-Datei korrekt einzuschachteln würde eine – selbst für einen Prototypen<br />
– zu hohe Beeinflussung <strong>der</strong> Laufzeit bedeuten. Selbst Ausgaben per System.out<br />
beeinflussen die Laufzeit schon deutlich (Programmlaufzeit Normal: 6.5Sec, <strong>mit</strong> Aspekten<br />
7Sec, <strong>mit</strong> System.out-Ausgabe bei jedem Methodenaufruf: 25Sec). Daher wurde<br />
von uns eine Möglichkeit angestrebt die Informationen während dem Programmablauf<br />
lediglich <strong>mit</strong>tels Java-Objekten vorzuhalten und nach dem Beenden des eigentlichen<br />
Programmablaufs die Transformation in XML-Objekte und die Datei-Ausgabe in eine<br />
XML-Datei zu starten. Natürlich ergeben sich dadurch gewisse Constraints und Nachteile.<br />
So steigt die Speichernutzung stärker an und sehr lang laufende Programme <strong>mit</strong><br />
vielen Methodenaufrufen führen letztendlich zum Speicherüberlauf. Um diese Nachteile<br />
zu beseitigen, müsste eine ausgereiftere Logging-Methode benutzt werden, welche ständig<br />
o<strong>der</strong> auch in bestimmten Intervallen die Log-Ausgabe in eine Logdatei startet und dabei<br />
die Ausführung des zu profilenden Programms möglichst wenig beeinflusst.<br />
Eine weitere Entscheidung, die wir Treffen mussten, war die Wahl <strong>der</strong> Zeitabstände<br />
zur Methodenmessung. Sehr viele Methoden laufen äußerst kurz, so dass eine Messung<br />
<strong>mit</strong> System.getCurrentTimeMillis jedes Mal 0ms ergeben hätte. System.nanoTime liefert<br />
zwar nicht wirklich Zeiten <strong>mit</strong> Nanosekunden-Granularität, schafft aber durchaus<br />
genauere Messungen als Millisekunden, zusätzlich ist System.nanoTime() nicht von <strong>der</strong><br />
Betriebssystem-Zeit abhängig, kann also nicht durch den User beeinflusst werden.<br />
Eine sehr wichtige Funktion ist die Beschränkung des Profilings auf einen Teilbereich <strong>der</strong><br />
Applikation. So möchte man vielleicht nur die Datenbank-Schicht profilen, während <strong>der</strong><br />
Anwendungskern und die GUI nicht von Interesse sind. Dies konnte ebenfalls <strong>mit</strong> einer<br />
AspectJ-Funktion gelöst werden: Pointcuts die within(Packagename..*) beinhalten treffen<br />
nur auf Joinpoints zu, die im jeweiligen Package o<strong>der</strong> Unterpackage liegen (Wildcard-<br />
Bedeutung siehe [Syn]). So konnten wir <strong>mit</strong> dem Pointcut excluded: !within(cleverTec..*)<br />
auch festlegen, dass sich unsere Aspekte nicht gegenseitig profilen.<br />
21
Abbildung 4.1: Die excluded-/included-Anweisung<br />
Eine weitere Anfor<strong>der</strong>ung war es, die einzelnen Aspekte modular zu entwickeln, um<br />
wenig o<strong>der</strong> keine Abhängigkeiten zwischen den Aspekten untereinan<strong>der</strong> zu generieren.<br />
So<strong>mit</strong> konnte die Implementierung unabhängig voneinan<strong>der</strong> vonstatten gehen und so die<br />
Arbeit besser aufgeteilt werden. Zusätzlich können die Aspekte dann auch einzeln zuo<strong>der</strong><br />
abgeschaltet werden und neue Aspekte ohne große Probleme hinzugefügt werden.<br />
Letztendlich konnten wir die Architektur sehr simpel und überschaubar halten:<br />
22
Abbildung 4.2: Die technische Architektur des Prototyps<br />
Wie man sieht, wird ein AbstractAspect verwendet, in welchem zum einen die angesprochenen<br />
Pointcuts excluded()/included() vorhanden sind – welche von jedem konkreten<br />
Aspekt später auch für jeden Pointcut benutzt werden müssen – und zum an<strong>der</strong>en<br />
die Verbindung zum Logger, also <strong>der</strong> Datenhaltung hergestellt wird. Dabei wird keine<br />
Schnittstelle verwendet, son<strong>der</strong>n <strong>der</strong> Logger direkt übernommen. Dies bedeutet, dass bei<br />
einem Austausch des Loggers sämtliche Aspekte an den neuen Logger angepasst werden<br />
müssen – dies nehmen wir aber in Kauf, da bei einer Umstellung des Loggers auf eine<br />
an<strong>der</strong>e, performantere Methode gleich die gesamte Architektur sowie die Struktur <strong>der</strong><br />
ValueObjects neu überdacht werden muss.<br />
4.2 Implementierung<br />
Bei <strong>der</strong> Implementierung starteten wir da<strong>mit</strong>, unsere Loggerklasse zu schreiben. Der Logger<br />
ist dafür zuständig die Logdaten aufzunehmen und nach Ablauf des zu überwachenden<br />
Programms auszugeben.<br />
23
Zur Datenhaltung innerhalb des Loggers entschlossen wir uns parametrisierte, geschachtelte<br />
Maps zu verwenden. Im Logger selbst finden wir eine Map, welche aus String/-<br />
Map als Key/Value Paaren besteht (Map). Je<strong>der</strong><br />
Aspekt nutzt die vom Logger angebotene Methode ”<br />
Map<br />
getHashMap(String)“, um eine exklusive Map zu bekommen – dafür übergibt man als<br />
String-Parameter Aspektname.class.getName(), um so eine Eindeutigkeit zu erlangen.<br />
In diese Map schreibt <strong>der</strong> Aspekt dann seine Daten als String, ValueObject-Paare.<br />
Die nächste Funktionalität die wir implementieren wollten, war die Ausgabe <strong>der</strong> Daten<br />
nach Ablauf des zu überwachenden Programms. Dies gestaltete sich als etwas schwieriger.<br />
Wie wird man darüber Informiert, dass das Programm beendet ist? Wenn es sich um ein<br />
rein linear ablaufendes Programm handelt, könnte man am Ende eine Benachrichtigung<br />
einbauen, dazu müsste das Programm aber geän<strong>der</strong>t werden, was wir eigentlich vermeiden<br />
wollten. Eine an<strong>der</strong>e Möglichkeit wäre die finalize()-Methode unseres Loggers zu<br />
überschreiben. Jedes Objekt in Java besitzt die von Object geerbte finalize()-Methode.<br />
Diese wird aufgerufen, sobald das jeweilige Objekt explizit - also vom GarbageCollector<br />
- entfernt wird. Wann und ob Objekte vom GarbageCollector entfernt werden, ist aber<br />
nicht festgelegt. Wird das Programm beendet, werden alle noch existierenden Objekte<br />
zerstört, wobei die finalize()-Methode aber nicht ausgeführt wird.<br />
Eine bessere Möglichkeit bietet ein sogenannter ShutdownHook.<br />
Per Runtime.getRuntime().addShutdownHook(Thread t); wird ein Thread in <strong>der</strong> Runtime<br />
registriert, welcher ausgeführt wird, sobald exit(int) aufgerufen wird, keine weiteren<br />
aktiven Threads mehr am Leben sind o<strong>der</strong> die VirtualMachine vom Benutzer terminiert<br />
wird (Logoff z.B.), also kurz gesagt das Programm beendet wird. Durch diesen Mechanismus<br />
ist es uns möglich relativ einfach und zuverlässig die Ausgabe erst nach Beenden<br />
des Hauptprogramms zu starten.<br />
Die verschiedenen Aspekte erheben unterschiedliche Daten, teilweise werden nur String/Integer<br />
Paare gespeichert (z.B. zählen <strong>der</strong> Instanzen einer Klasse), an<strong>der</strong>e speichern zu<br />
einem String mehrere Zeit-Intervalle <strong>mit</strong> Anfangs und Endzeit sowie zusätzliche Informationen,<br />
wie etwa die <strong>mit</strong>tlere Ausführungszeit. Die Struktur und das Ausgabeformat<br />
<strong>der</strong> einzelnen Aspektdaten unterscheiden sich also stark untereinan<strong>der</strong> und sind auch<br />
nicht generisch darstellbar. Daher entwickelten wir unser ValueObject Interface. Das<br />
Interface beinhaltet zwei Methoden:<br />
• public void printValues();<br />
//Gibt die Daten auf System.out aus – Debugging Zwecke etc.<br />
• public Element toXMLElement();<br />
//Gibt ein JDOM-Element zurück, in welchem die gesamten Daten als XML-<br />
Sturktur vorhanden sind<br />
Wie die Implementierung des ValueObjects ansonsten aufgebaut ist und wie es seine<br />
mehr o<strong>der</strong> weniger komplexen Daten hält, interessiert uns hier nicht – das ist <strong>der</strong> Aufgabenbereich<br />
des einzelnen Aspekts.<br />
24
Unser Logger, welcher ja als ShutdownHook-Thread registriert ist, kann nun zunächst<br />
über die erste Map iterieren und erhält so für jeden Key – welcher ja einem Aspekt-<br />
Package entspricht – als Value eine Map aus String, ValueObjects. Nun kann er über diese<br />
String-, ValueObjects-Paare iterieren und die toXMLElement()-Methode des ValueObjects<br />
benutzen, um so die gesamten Daten für einen Aspekt zu aggregieren. So entsteht<br />
dann aus den einzelnen XML-Teilbäumen ein großes XML Gesamtdokument. Dieses liegt<br />
nun als JDOM-Document vor und kann direkt <strong>mit</strong> dem org.jdom.output.Outputter in<br />
eine Datei geschrieben werden.<br />
Nachdem dies geschehen ist, wird überprüft, ob in <strong>der</strong> clevertec.properties XSLT-Stylesheets<br />
angegeben wurden. Gegebenenfalls wird das JDOM-Document dann <strong>mit</strong> diesen<br />
XSLT-Stylesheets transformiert und die Ausgaben in die entsprechenden Dateien geschrieben.<br />
So liefern wir z.B. Templates für das erzeugen von CSV-Dateien, welche dann<br />
in Excel weiterverarbeitet werden können, z.B. sortieren, selektieren und erstellen von<br />
Charts, etc. Eine dieser CSVs wurde auch vom Team RoQuMod weiterverwendet: Es<br />
wurde ein conqat-Prozessor geschrieben, welcher das CSV einliest und so die Werte in<br />
Conqat als Daten verfügbar macht und letztendlich als Tabelle ausgibt. Die erzeugten<br />
SVG-Grafiken sind eine Art ”<br />
Proof of Concept“, dass man unsere Daten auch visualisieren<br />
kann.<br />
Der gesamte Ablauf kann etwa folgen<strong>der</strong>maßen dargestellt werden:<br />
25
Abbildung 4.3: Programmablauf des Prototypen<br />
4.3 Implementierte Aspekte<br />
Folgende Aspekte haben wir in <strong>der</strong> aktuellen Version des Programms implementiert:<br />
• ExceptionsAspect: Zählt wie oft Exceptions aufgetreten sind und wann<br />
26
• Instances: Zählt wie viele Objektinstanzen erzeugt werden<br />
• MethodExecutionNumber: Zählt wie oft Methoden aufgerufen wurden<br />
• MethodExecutionRuntime: Misst die Zeit, die für eine Methodenausführung benötigt<br />
wird<br />
• MethodCallRuntime: Wie MethodExecutionRuntime, nur für Calls, standardmäßig<br />
disabled da sonst sehr viele Logdaten entstehen!<br />
• ThreadCreation: Zählt wieviele Threads erzeugt wurden<br />
• ThreadStartup: Zählt wie viele Threads ausgeführt / beendet wurden<br />
Die Aspekte konnten alle unabhängig voneinan<strong>der</strong> implementiert werden. Teilweise wurden<br />
gemeinsame Utility-Klassen benutzt, z.B. für das Festhalten von Zeitintervallen.<br />
Beispielsaspekt: Instances.aj<br />
Exemplarisch möchten wir hier nun einen Aspekt noch mal etwas genauer betrachten:<br />
Der Aspekt Instances.aj zählt alle Objektinstanzen.<br />
Der Aspekt leitet sich – wie je<strong>der</strong> Aspekt – von unserem AbstractAspect ab. Dadurch<br />
erbt er die beiden Pointcuts included && excluded und den Logger log. Nun wird aus<br />
dem Logger eine für diesen Aspekt exklusive Map geholt. Dafür wird <strong>der</strong> Aspektname.class.getName()<br />
– also die Packagestruktur und <strong>der</strong> eigentliche Aspektname – an<br />
die Methode Logger.getHashMap(String) übergeben. In <strong>der</strong> Ausgabe wird später eine<br />
Section für diesen Aspekt erstellt, die <strong>der</strong> Packagestruktur entspricht – hier also ”<br />
clever-<br />
Tec.aspects.instanceCounter“. Dies bedeutet, wenn man zwei Aspekte in einem Package<br />
implementieren würde, würden ihre Daten in <strong>der</strong> gleichen Sektion <strong>der</strong> XML-Ausgabe<br />
landen.<br />
Als nächstes muss ein o<strong>der</strong> mehrere Pointcuts definiert werden. Dabei ist darauf zu achten<br />
”<br />
&& excluded() && included()“ an den Pointcut anzufügen, da<strong>mit</strong> sich die Aspekte<br />
nicht gegenseitig ”<br />
matchen“ und zusätzlich eine Einschränkung <strong>der</strong> Analyse auf Teilbereiche<br />
möglich ist. In unserem Fall sieht <strong>der</strong> Pointcut so aus:<br />
pointcut InstancesCount ( ) :<br />
execution ( new ( . . ) ) && excluded ( ) && included ( ) ;<br />
Es werden also alle Joinpoints gematched, welche ein neues Objekt erzeugen. Nun muss<br />
noch definiert werden was dann zu tun ist, dies geschieht im Advice. Hier benutzten wir<br />
einen before() : InstancesCount() Advice. Als nächstes wird geprüft welches Objekt gerade<br />
instanziert wurde. Dies geschieht <strong>mit</strong>tels thisJoinPoint.getTarget().getClass().getCanonicalName();<br />
- was uns die Packagestruktur sowie den Klassennamen liefert. Sollte<br />
gerade eine lokale o<strong>der</strong> anonyme Instanz erzeugt worden sein – liefert die Methode Null.<br />
Diese Ausnahme wird manuell behandelt und <strong>der</strong> Objektname dann auf ”<br />
localOrAnonymous“<br />
gesetzt.<br />
27
Nun kann begonnen werden diese Logdaten in die Map zu schreiben. Zunächst wird geprüft,<br />
ob zu dem Key – also hier dem Namen des instanzierten Objekts – bereits ein ValueObject<br />
vorliegt. Falls dies nicht <strong>der</strong> Fall ist, wird ein neues angelegt und in die Map geschrieben.<br />
Sollte bereits ein ValueObject zum jeweiligen Key vorliegen, wird dieses durch<br />
seine Methoden um den Wert 1 inkrementiert. Hier also c.setCount(c.getCount()+1).<br />
Nun sind die Daten in <strong>der</strong> Map gespeichert und so<strong>mit</strong> die Aufgabe dieses Aspekts fertiggestellt.<br />
28
5 Einbinden benutzerspezifischer<br />
Aspekte in den Prototyp<br />
Nachdem die Architektur und <strong>der</strong> allgemeine Ablauf des Programms vorgestellt worden<br />
sind, wird in diesem Abschnitt ein detailliertes Beispiel für das Einbinden eines benutzerspezifischen<br />
Aspekts im Programm beschrieben. Eine unaufwändige und schnelle<br />
Erweiterung von Aspekten im Programm war eines <strong>der</strong> Hauptziele bei <strong>der</strong> Entwicklung<br />
des Programms. Der Einbindevorgang wird anhand eines Beispielaspekts erläutert. Der<br />
Beispielaspekt, <strong>der</strong> in diesen Kapitel vorgestellt wird, ist für die Zählung von Getterund<br />
Setter-Methoden zuständig. Die nötigen Testklassen sowie die Aspektklasse befinden<br />
sich im GetterSetter.jar auf <strong>der</strong> CD.<br />
Bevor <strong>mit</strong> dem Erstellen des neuen Aspekts begonnen werden kann, ist es nötig, das<br />
Projekt in Eclipse zu importieren. Zum Einbinden des Programms in Eclipse ist lediglich<br />
die Installation von AJDT(AspectJ Development Tools) notwendig (siehe HowTo<br />
Dokument). Im einzelnen muss zunächst ein neues Projekt erstellt werden, indem in<br />
Eclipse das CLeverTec Programm importiert wird. Anschliessend muss das Projekt <strong>mit</strong><br />
<strong>Hilfe</strong> von AJDT in ein AspectJ Projekt konvertiert werden, falls dies nicht automatisch<br />
geschehen ist. Das Projekt kann auch problemlos in Maven eingebunden werden, dazu<br />
ist lediglich die Einbindung von JDOM-Bibliothek in <strong>der</strong> pom.xml notwendig.<br />
Nun werden drei Testklassenn zu Testzwecken erstellt. Dazu muss zunächst ein neues<br />
Package test.all.aspects im Verzeichnis src/test angelegt werden. Die Klasse Konto<br />
enthält zwei Variablen guthaben und kontoNummer, zwei Methoden abheben(int Betrag)<br />
und einzahlen(int betrag), sowie die für den neu einzubindenden Aspekt relevanten<br />
Getter- und Setter-Methoden. Die Methode abheben() verursacht eine OverLi<strong>mit</strong>Exception,<br />
falls versucht wird, mehr Geld abzuheben als vorhanden ist. Die OverLi<strong>mit</strong>Exception<br />
Klasse ist eine selbstgeschriebene Exception, die von <strong>der</strong> Basisklasse Exception<br />
abgeleitet ist. In <strong>der</strong> Mainmethode <strong>der</strong> Klasse TestKonto wird ein Konto erstellt und<br />
eine Einzahlung sowie eine Abhebung wird ausgeführt, am Schluss wird dann die für relevante<br />
getter-Methode getGuthaben() aufgerufen. Schließlich muss die Klasse Testkonto<br />
als Java Application o<strong>der</strong> AspectJ/Java kompiliert und ausgeführt werden. Dabei wird<br />
ein neues Verzeichnis out erstellt, die unter an<strong>der</strong>em eine XML-Datei out.xml enthält, in<br />
<strong>der</strong> alle ausgewerteten Daten zusammengefasst sind. Um die Än<strong>der</strong>ungen festzustellen,<br />
die durch die Aspekterweiterung aufgetreten sind, sollte diese generierte XML Datei in<br />
ein beliebiges Verzeichnis gespeichert werden. Der Aufbau und die Struktur <strong>der</strong> XML<br />
Datei wird in Kapitel 5 näher erläutert.<br />
29
Innerhalb des Projekts wird nun ein Package namens cleverTec.aspects.getterandsetter<br />
und anschließend eine Klasse CountGetterAndSetter erstellt. Die Klasse CountGetterAndSetter<br />
ist eine Implementierung des Interface ValueObject. Diese enthält zwei Methoden<br />
zur Speicherung bzw. Ausgabe <strong>der</strong> entsprechenden Ergebnisse.<br />
Dabei entspricht die Methode printValue() <strong>der</strong> Konsolenausgabe und die Methode toXM-<br />
LElement() ist für das Festhalten <strong>der</strong> Daten in einer XML Datei verantwortlich. Des<br />
Weiteren besitzt die Klasse zwei Variablen getSetNumber die für die Zählung <strong>der</strong> aufgerufenen<br />
Getter- und Setter-Methoden zuständig ist und eine Variable getSet die die<br />
Methodennamen <strong>der</strong> Getter- und Setter-Methoden speichert. Die Methode increment()<br />
zählt die Anzahl <strong>der</strong> Getter- und Setter-Aufrufe um eins hoch.<br />
30
Abbildung 5.1: ValueObject-Klasse CountGetterUndSetter<br />
Innerhalb desselben Packages muss nun eine Aspektklasse GetterAndSetter erstellt werden,<br />
die von AbstractAspect abgeleitet ist. In <strong>der</strong> GetterAndSetter Aspektklasse ist ein<br />
pointcut definiert, <strong>der</strong> alle Getter und Setter-Methoden durchläuft. Nach jedem Aufruf<br />
einer Getter- o<strong>der</strong> Setter-Methode, wird <strong>der</strong> Name <strong>der</strong> Methode sowie die aktualisierte<br />
Anzahl <strong>der</strong> bisherigen Aufrufe <strong>mit</strong> <strong>Hilfe</strong> des Advice in eine HashMap gespeichert, die<br />
ein Attribut <strong>der</strong> Aspektklasse ist.<br />
31
Abbildung 5.2: Benutzerspezifisches Aspekt GetterAndSetter.aj<br />
Nachdem nun erneut die Klasse TestKonto kompiliert und ausgeführt wurde, kann die<br />
neu generierte XML <strong>mit</strong> <strong>der</strong> ursprünglichen XML Datei vor <strong>der</strong> Aspekterweiterung verglichen<br />
werden. Innerhalb <strong>der</strong> neuen XML Datei ist ein weiterer Tag zu<br />
finden, <strong>der</strong> in <strong>der</strong> alten XML Datei nicht vorhanden war.<br />
Abbildung 5.3: Loggerausgabe <strong>mit</strong> dem Ergebnis des benutzerspezifischen Aspekts<br />
Zusammenfassend sind folgende Punkte bei <strong>der</strong> Erstellung eigener Aspekte zu beach-<br />
32
ten:<br />
• Je<strong>der</strong> neue Aspekt muss in einem eigenen Package erstellt werden (siehe Kapitel<br />
4.3)<br />
• Die Javaklasse implementiert das Interface ValueObject und da<strong>mit</strong> dessen zwei<br />
Methoden printValues() und toXMLElement(). Dabei gibt erstere die Daten auf<br />
<strong>der</strong> Konsole aus und letztere speichert die Daten in geeignetem Format in die XML<br />
Datei.<br />
• Die Aspektklasse ist von <strong>der</strong> Klasse AbstracLogger abgeleitet. Von dieser erbt sie<br />
das Attribut vom Typ Logger.<br />
33
6 Benutzung des Prototyps<br />
Die Idee <strong>der</strong> Anwendung war, dass man jeglichen Quellcode <strong>mit</strong> Aspekten versehen kann<br />
und dadurch Messungen am Programm vornehmen kann. Dies kann auf zwei verschiedene<br />
Arten erreicht werden. Zum einen kann man dies über Eclipse erreichen, zum an<strong>der</strong>en hat<br />
man die Möglichkeit den Quellcode über einen Kommandozeilenaufruf <strong>mit</strong> den Aspekten<br />
versehen zu lassen und kann das Kompilat daraus wie jedes an<strong>der</strong>e Javaprogramm laufen<br />
lassen. Als Ausgabe entsteht dadurch eine XML-Datei sowie eine CSV-Datei.<br />
6.1 Was leistet das Programm?<br />
Die Aspekte sind so gedacht, dass sie jedem beliebigem Quellcode hinzugefügt werden<br />
können und den Quellcode so erweitern, dass statistische Informationen ausgelesen<br />
werden können. In <strong>der</strong> vorliegenden Version können bereits folgende Daten ausgelesen<br />
werden, sind also als Aspekt vorhanden:<br />
• Instanzen zählen<br />
• Methodenaufrufe zählen, sowohl gesamt wie zu je<strong>der</strong> spezifisch<br />
• Methodenlaufzeiten<br />
• Threadinstanzen zählen<br />
• Threadausführungen (Aufrufe von start())<br />
• Exceptions die geworfen werden<br />
Das Programm führt eine Statistik während <strong>der</strong> zu untersuchende Quellcode ausgeführt<br />
wird und schreibt die erworbenen Daten, bei Beendigung <strong>der</strong> virtuellen Maschine, in eine<br />
XML-Datei, welche zusätzlich noch gleich <strong>mit</strong> beliebigen XSLT-Templates transformiert<br />
werden kann (z.B. CSV o<strong>der</strong> SVG-Ausgaben). Die Sammlung <strong>der</strong> Daten passiert so<strong>mit</strong> in<br />
Java nativem Code und kann daher auf je<strong>der</strong> zur Javaspezifikation kompatiblen virtuellen<br />
Maschine laufen, wenn es entsprechend ihrer Version kompiliert wurde.<br />
6.2 Wie benutzt man das Programm?<br />
Das Programm kann auf zwei verschiedene Arten benutzt werden. Zum einen durch einbinden<br />
in ein Eclipseprojekt (6.3) und zum an<strong>der</strong>en durch Aufruf des Codeweavers (6.4),<br />
34
welchem <strong>der</strong> zu untersuchende Quellcode und die Aspekte gegeben werden und dieser<br />
dann die Aspekte an den Quellcode knüpft und ein lauffähiges Programm ausgibt.<br />
6.3 Einbinden in ein Eclipseprojek<br />
Der einfachste Weg ist den Quellcode, den Quellcode <strong>der</strong> Aspekte und dazugehöriger<br />
Klassen in ein AspectJ-Projekt in Eclipse zu kopieren. Dann kann man die ganze Anwendung<br />
direkt aus Eclipse heraus laufen lassen und muss sich um nichts weiter kümmern.<br />
Das Anlegen eines AspectJ-Projekts funktioniert gleich wie das Anlegen eines normalen<br />
Javaprojekts, nur das man eben als Projektbasis ein AspectJ-Projekt auswählt. Danach<br />
ist zu empfehlen zwei Quellverzeichnisse anzulegen, eins für das Programm welches untersucht<br />
werden soll und eins für die Aspekte. Jetzt wäre bereits alles lauffähig, aber<br />
es können noch weitere Konfigurationen vorgenommen werden. In <strong>der</strong> Datei AbstractAspect.aj<br />
können ausgeschlossene Bereiche, sowie explizit eingeschlossene Bereiche bestimmt<br />
werden. Die hierbei relevanten Pointcuts sind folgende:<br />
Listing 6.1: relevante Pointcuts bei <strong>der</strong> Verwendung des Prototyps<br />
public pointcut excluded ( ) :<br />
! within ( cleverTec . . ∗ ) ;<br />
public pointcut included ( ) :<br />
within ( ∗ ) ;<br />
Alle Dateien o<strong>der</strong> Pakete die explizit ausgeschlossen werden sollen können in den Klammern<br />
des ”<br />
excluded“-Pointcut definiert werden. Die einzelnen Teilbereiche werden über<br />
&& (logisches Und) o<strong>der</strong> ‖ (logisches O<strong>der</strong>) verbunden. Wichtig hierbei ist, dass das<br />
Paket des Prototypen sowie alle seine Unterpakete und Klassen (cleverTec..*) ausgeschlossen<br />
werden. Als Beispiel für die Benutzung:<br />
Listing 6.2: Benutzungsbeispiel des within() im Prototyp<br />
! within ( cleverTec . . ∗ && java . s q l . . ∗<br />
Diese Definition würde nun das Paket des Prototypen und dessen Unterpakete ausschließen<br />
sowie zusätzlich alle Pakete von java.sql sowie dessen Unterpakete. Ähnlich läuft dies<br />
nun <strong>mit</strong> den explizit zu inkludierenden Paketen und Dateien. Die Än<strong>der</strong>ung die man hier<br />
vornehmen muss, passiert in den Klammern des within-Fragments <strong>mit</strong> den gleichen Regeln<br />
wie oben bei den für die explizit ausgeschlossenen Pakete. Die Grundeinstellung *<br />
bedeutet, dass alle Pakete eingeschlossen werden (außer natürlich diejenigen die ausgeschlossen<br />
sind). Das ist eigentlich auch schon alles was es zu konfigurieren gibt.<br />
Man kann zusätzlich seine eigenen Aspekte schreiben aber sollte sie über den gerade<br />
beschriebenen Mechanismus aus den Statistiken ausschließen.<br />
35
Zuletzt bleibt nur noch zu konfigurieren wohin die Ausgabe geschrieben werden soll und<br />
ob XSLT-Templates auf sie angewendet werden sollen - dies kann man <strong>mit</strong>tels <strong>der</strong> Datei<br />
clevertec.properties im Unterverzeichnis conf erledigen. Die Erklärung zu dieser Datei<br />
ist im nächsten Abschnitt zu finden. Hat man nun dies alles gemacht kann man das zu<br />
untersuchende Programm laufen lassen und am Ende des Laufs sind die gesammelten<br />
Daten in den in <strong>der</strong> clevertec.properties definierten Ausgabedateien zu finden.<br />
Das ganze wurde auch nochmal als Video beigelegt, in dem gezeigt wird wie man alles<br />
in diesem Punkt beschriebene macht.<br />
6.4 Einbinden über den Codeweaver<br />
Der Codeweaver ist ein Javaprogramm, das einem letztlich den Aufruf für den acj<br />
(AspectJ Compiler) von AspectJ abnimmt. Er baut den kompletten Aufruf auf, führt<br />
ihn aus und fügt dem kompilierten Projekt noch Konfigurationsdateien hinzu. Was man<br />
braucht um ihn zu benutzen sind die Aspect Tools von AspectJ, die unter an<strong>der</strong>em die<br />
Laufzeitumgebung von AspectJ enthalten. Diese findet man hier:<br />
http://www.eclipse.org/aspectj/downloads.php<br />
Der Codeweaver selbst baut eigentlich nur eine Zeichenfolge auf <strong>mit</strong> welcher <strong>der</strong> ajc<br />
von AspectJ aufgerufen wird, indem er einen Prozess dafür startet und die Zeichenfolge<br />
ausführt. Die ganzen Daten, die benutzt werden um diesen Aufruf aufzubauen kommen<br />
aus <strong>der</strong> config.properties-Datei. Das nachfolgende Bild sollte dies etwas deutlicher<br />
machen.<br />
36
Abbildung 6.1: Die Verwendung <strong>der</strong> config.properties-Datei<br />
Anhand <strong>der</strong> Informationen aus dieser Datei wird dann ein <strong>mit</strong> den Aspekten versehener<br />
Bytecode erzeugt, welchen man wie jedes an<strong>der</strong>e Javaprogramm laufen lassen kann.<br />
Zusätzlich werden noch die Verzeichnisse libs und conf, die im Arbeitsverzeichnis des<br />
CodeWeavers liegen, kopiert. Zum Schluss wird noch eine Batch- o<strong>der</strong> Skriptdatei erzeugt,<br />
je nach unterliegen<strong>der</strong> Plattform, die dann <strong>mit</strong>tels Parameterübergabe des Pfades<br />
zur main-Methode den kompilierten Code ausführt.<br />
Der Codeweaver kann sowohl unter Linux als auch unter Windows benutzt werden,<br />
dabei ist natürlich zu beachten das die Pfadangaben für Dateien <strong>der</strong> jeweiligen Umgebung<br />
angepasst werden müssen. Gehen wir nun Schritt für Schritt durch, wie man ihn<br />
benutzt.<br />
1. Als erstes brauchen wir eine Konfigurationsdatei <strong>mit</strong> dem Namen config.properties<br />
im Arbeitsverzeichnis. Da man diese wahrscheinlich noch nicht hat kann man sie<br />
<strong>mit</strong>tels dem Aufruf ”<br />
java cleverTec.ajc.Codeweaver -makeconfig“ erzeugen lassen.<br />
Der Aufbau <strong>der</strong> Konfigurationsdatei wird am Ende des Abschnittes erklärt<br />
2. Nachdem man die Konfigurationen gesetzt hat, u.a. wo die AspectJ-Bibliotheken<br />
sowie <strong>der</strong> zu untersuchende Quellcode liegt, kann man dann den Codeweaver aufrufen<br />
<strong>mit</strong>tels: java cleverTec.ajc.Codeweaver. Dieser generiert einem dann den By-<br />
37
tecode verwoben <strong>mit</strong> den Aspekten und zusätzlich, in dem in <strong>der</strong> Konfigurationsdatei<br />
definiertem Ausgabeverzeichnis, ein Unterverzeichnis <strong>mit</strong> dem Namen conf,<br />
in welchem eine Konfigurationsdatei namens clevertec.properties für das gerade<br />
kompilierte Programm liegt.<br />
3. Danach können wir in das Ausgabeverzeichnis wechseln und noch Einstellungen in<br />
<strong>der</strong> Konfigurationsdatei clevertec.properties vornehmen:<br />
xmloutfile definiert den Namen <strong>der</strong> xml-Ausgabedatei<br />
xsltfiles p10cm definiert die Namen <strong>der</strong> Umwandlungsdateien, die die xml-<br />
Ausgabe weitertransformieren. Durch ;“ getrennt – Reihenfolge<br />
”<br />
analog zu xsltouts<br />
xsltouts definiert die Namen <strong>der</strong> zusätzlichen Ausgabedateien, die <strong>mit</strong>tels<br />
XSLT-Files generiert werden. Durch ;“ getrennt – Reihenfolge analog<br />
zu ”<br />
xsltfiles.<br />
4. Im letzten Schritt muss man nur noch die main-Methode des kompilierten Programms<br />
aufrufen und nach Beendigung ist sowohl die xml- als auch die csv-Ausgabe<br />
vorhanden in den jeweils im clevertec.properties definierten Dateien.<br />
Das war eigentlich schon alles was man machen muss. Es fehlt nur noch die Konfiguration<br />
des Codeweaver über dessen Konfigurationsdatei config.properties in dessen Arbeitsverzeichnis.<br />
Hier ein Beispiel für eine Windowskonfiguration und danach die Erklärungen<br />
<strong>der</strong> einzelnen Variablen:<br />
Listing 6.3: Die Konfigurationsdatei config.properties<br />
FURTHERJAVAARGS=<br />
ACJCLASSPATH=Libs \\jdom −1.1\\jdom . j a r ;<br />
C:\\ Programme\\ Java \\ a s p e c t j \\ l i b \\ a s p e c t j r t . j a r<br />
DESTINATION=t e s t B i n<br />
FURTHERACJARGS=−source 1 . 5<br />
ASPECT LIB PATH=C:\\ Programme\\ Java \\ a s p e c t j \\ l i b<br />
JAVAFILES=t e s t s S o u r c e s \\ t e s t S o u r c e s \\ threads \\;\<br />
s r c \\ cleverTec \\ l o g g e r \\;\<br />
s r c \\ cleverTec \\ a s p e c t s \\methodExecutionNumber \\;\<br />
s r c \\ cleverTec \\ a s p e c t s \\ threads \\;\<br />
s r c \\ cleverTec \\ u t i l \\<br />
ACJ=org . a s p e c t j . t o o l s . a j c . Main<br />
CLASSFILESGIVEN=0<br />
Die Konfigurationsdatei ist eine Java konforme properties-Datei. Deswegen auch die ”<br />
\\“<br />
in den Windowspfadangaben. Ansonsten gelten die üblichen Regeln für solche Dateien.<br />
Die Erklärung <strong>der</strong> einzelnen Variablen:<br />
38
FURTHERJAVAARGS<br />
ACJCLASSPATH<br />
DESTINATION<br />
FURTHERACJARGS<br />
ASPECT LIB PATH<br />
JAVAFILES<br />
ACJ<br />
CLASSFILESGIVEN<br />
Hier können zusätzliche Argumente für den Javakompilierer<br />
(javac) übergeben werden, z.B. -Xstdout<br />
Hier müssen die vom acj zur Übersetzung benötigten Bibliotheken<br />
übergeben werden. Mehrere Angaben sollten <strong>mit</strong>tels<br />
Strichpunkt getrennt werden<br />
Diese Variable bestimmt das Ausgabeverzeichnis wo die kompilierten<br />
Dateien hingeschrieben werden sollen<br />
Dies sind Argumente die dem acj zusätzlich übergeben werden<br />
sollen, ähnlich wie FURTHERJAVAARGS<br />
Hier müssen die AspectJ-Bibliotheken angegeben werden die<br />
u.a. den acj enthalten <strong>der</strong> zum übersetzen <strong>der</strong> Javadateien<br />
benutzt werden soll<br />
Hier werden sowohl <strong>der</strong> zu untersuchende Quellcode angegeben<br />
als auch die Aspekte die man benutzen möchte. Wenn die<br />
Angaben <strong>mit</strong> einem systemspezifischen Pfadseparator, z.B.<br />
Unix /“, enden, werden die verschiedenen Pfade passend umgebaut<br />
und als Argument an den acj übergeben. Falls sie nicht<br />
”<br />
so enden werden dem acj nur die angegebenen Dateien, welche<br />
<strong>mit</strong> .java o<strong>der</strong> .aj enden sollten, übergeben. Wenn man mehrere<br />
Pfade angeben will kann man diese <strong>mit</strong>tels Strichpunkt<br />
trennen.<br />
Der eigentliche Aufruf des acj selbst. Falls sich irgendwann<br />
einmal die Struktur <strong>der</strong> AspectJ-Bibliotheken än<strong>der</strong>t kann das<br />
hier angepasst werden<br />
Entwe<strong>der</strong> 0 o<strong>der</strong> 1. Bei 0 bedeutet dies, dass man nur Quellcode<br />
kompilieren will (.java), bei 1, dass man nur kompilierte<br />
Dateien verweben will (.class-Dateien)<br />
6.5 Ausführen <strong>mit</strong>tels Ant-Task<br />
AspectJ liefert standardmäßig Taskdefinitions für das weitverbreitete Build-Tool Ant<br />
(siehe [The]) <strong>mit</strong> jedem Release in <strong>der</strong> ”<br />
aspectjtools.jar“ aus. Da<strong>mit</strong> ist die Integration<br />
des AspektJ-Compilers ”<br />
ajc“ in Ant ein leichtes. Ein sehr einfaches compile Target für<br />
Ant könnte in etwa so aussehen:<br />
Listing 6.4: Ein einfaches compile Target für Ant<br />
<br />
<br />
<br />
<br />
39
<br />
<br />
<br />
Der Aufruf kompiliert hier sämtliche .java und .aj Dateien im Verzeichnis src<br />
und testsSrouces und schreibt die kompilierten .class Dateien in das destDir-Verzeichnis<br />
– ajc agiert hier also ebenso wie javac, nur eben <strong>mit</strong> AspectJ Unterstützung. Je nachdem<br />
welches Projekt hier kompiliert werden soll, müssen weitere Bibliotheken im Classpath<br />
angegeben werden, o<strong>der</strong> Konfigurationsdateien <strong>mit</strong> in das destDir kopiert werden.<br />
Hierfür kann die Build-Datei beliebig erweitert werden. Für einfache Aufgaben sind hier<br />
kaum Kentnisse <strong>der</strong> Ant-Syntax notwendig, die Build-Datei ist ausserdem für sich sehr<br />
sprechend.<br />
Um das volle potential des AspektJ-Compilers auszunutzen und sich so die Möglichkeit<br />
zu erschließen inkrementell zu kompilieren o<strong>der</strong> ByteCode-Weaving, also das anwenden<br />
eines Aspekts auf bereits kompilierte .class Dateien (bzw. .jar-Dateien), zu betreiben –<br />
verweisen wir auf die sehr gute Dokumentation <strong>der</strong> AspectJ-Ant-Tasks unter [Aspa].<br />
Das aktuell enthaltene Buildfile aspectj build.xml kompiliert sämtliche Aspekte und<br />
Tests. Um es zu verwenden sind folgende Schritte vorzunehmen:<br />
Zunächst sollte Ant in einer möglichst neuen Version installiert werden und das ant/bin-<br />
Verzeichnis gegebenenfalls zur Path-Variable hinzugefügt werden, um Ant direkt als<br />
Kommando in <strong>der</strong> Konsole verwenden zu können. Nun müssen noch die Properties im<br />
aspectj build.xml angepasst werden. Beson<strong>der</strong>s wichtig sind hier die beiden Properties<br />
<br />
<br />
Diese müssen auf den jeweiligen Pfad zeigen, in dem AspectJ installiert wurde.<br />
Um unsere Beispiele auszuführen ist sonst keine weitere Einstellung nötig. Sollen aber<br />
an<strong>der</strong>e Projekte kompiliert werden, muss das classpath compile bzw. classpath execution<br />
Property gegebenenfalls um weitere librarys erweitert werden.<br />
Nun kann das default-target, hier compile, per Kommandozeile gestartet werden: ”<br />
ant<br />
–f aspectj build.xml“ führt das default-target aus. Zunächst, wird falls vorhanden <strong>der</strong><br />
Ordner ant-out im relativen Verzeichnis gelöscht (clean). Dann werden Librarys und<br />
Konfigurationsdateien ins ant-out Verzeichnis kopiert(copy). Zu guter letzt werden noch<br />
sämtliche java/aj Dateien compiliert, sowie alle weiteren Dateien die sich in den beiden<br />
sourceroot-Verzeichnissen befinden ins jeweilige ant-out Unterverzeichnis kopiert<br />
(ausgeschlossen von diesem Kopieren sind .java und .aj Dateien – siehe SourceRootCopyFilter).<br />
Nun können die kompilierten .class Dateien ganz normal per java Aufruf ausgeführt werden<br />
(zusätzlich muss man nur die aspectjrt.jar im Classpath angeben) – auch dies kann<br />
40
per Ant erledigt werden. ant –f aspectj build.xml run“ startet z.B. die Main-Methode<br />
”<br />
<strong>der</strong> Klasse testSources.threads.Restaurant – dies kann durch setzen des mainclass-properties<br />
in <strong>der</strong> XML-Datei auch geän<strong>der</strong>t werden, o<strong>der</strong> per Kommandozeile direkt überschrieben<br />
werden:<br />
ant –f aspectj build.xml run -Dmainclass=testSources.svg.SVG Coordinate System Test“<br />
”<br />
führt also die Klasse testScources.svg.SVG Coordinate System Test aus.<br />
Ant bietet noch viele weitere Möglichkeiten automatische Builds und weitere Aufgaben<br />
zu realsieren. Hierfür verweisen wir auf die Ant-Dokumentation unter [Apa].<br />
Durch die Verwendung des Ant-Build-Files ist eine nahtlose Integration in eventuell bereits<br />
bestehende automatische Buildprozesse möglich. So könnten beispielsweise Tests<br />
<strong>mit</strong>hilfe von AspectJ geschrieben werden und die von uns dabei erzeugten Ausgaben direkt<br />
ausgewertet, o<strong>der</strong> die SVG-Grafiken in Report-Seiten <strong>mit</strong>eingebunden werden. Hier<br />
ist auch zu beachten, dass weitere XSL-Transformationen direkt per clevertec.properties<br />
eingebunden werden können. So könnte aus den Ergebnissen direkt eine passende XHTML-<br />
Seite generiert werden, welche dann automatisch generiert wird und weiterverarbeitet<br />
werden kann (Maven, CruiseControl, Wikis etc..).<br />
6.6 Was wird Ausgegeben?<br />
Hat man nun den einen o<strong>der</strong> an<strong>der</strong>en Weg genommen um sein Programm <strong>mit</strong> den<br />
Aspekten zu verweben und laufen zu lassen bekommt man als Ergebnis sowohl eine<br />
XML-Datei, sowie eventuell weitere durch XSLT erzeugte Dateien. Die XML-Datei ist<br />
so aufgeteilt, dass zu jedem Unterpaket von clevertec.aspects eine Sektion aufgemacht<br />
wird und die Werte die über die dort enthaltenen Aspekte gesammelt wurden in diese<br />
Sektion geschrieben werden. Die einzelnen Aspekte in jedem Paket wie<strong>der</strong>um bekommen<br />
eine eigene Sektion <strong>mit</strong> dem Namen ëntry”welcher den Aspekt definiert. In dieser<br />
ëntrySektion sind dann die Werte die von dem jeweiligen Aspekt gesammelt wurden,<br />
als Kindelemente <strong>mit</strong> Attributen enthalten. Die Formatdefinition dieser Kindelemente,<br />
also welcher Wert ist ein Attribut o<strong>der</strong> sollen Werte als Elemente eingefügt werden, wird<br />
von dem zum Aspekt gehörigen ValueObject bestimmt. Präziser, die Methode toXM-<br />
LElement (implementiert von dem Interface ValueObject) bestimmt wie die Ausgabe<br />
innerhalb jedes ëntryäussieht.<br />
Die CSV-Datei wird eigentlich in mehreren Teilen ausgegeben (momentan). Die Idee<br />
hierbei ist, dass die CSV-Datei(en) <strong>mit</strong> <strong>Hilfe</strong> von XSLT-Datei(en) aus <strong>der</strong> produzierten<br />
XML-Datei erzeugt wird. Die XSLT-Datei(en) die benutzt werden soll, wird einfach<br />
in <strong>der</strong> clevertec.properties unter xsltfiles vermerkt (siehe 6.4 ). Diese Dateien werden<br />
dann zusätzlich nach Beendigung <strong>der</strong> XML-Ausgabe verarbeitet. Über diesen Mechanismus<br />
kann man leicht beliebige CSV-Ausgaben erzeugen, die immer an die Umstände<br />
angepasst werden können sowohl <strong>mit</strong> den Daten als auch <strong>mit</strong> dem Format.<br />
41
Literaturverzeichnis<br />
[Apa]<br />
Apache Ant 1.7.0 Manual. http://ant.apache.org/manual/index.html, Abruf:<br />
20. Januar 2008<br />
[Aspa] AspectJ Ant Tasks. http://www.eclipse.org/aspectj/doc/released/<br />
devguide/antTasks.html, Abruf: 20. Januar 2008<br />
[Aspb] The AspectJ Programming Guide. http://www.eclipse.org/aspectj/doc/<br />
released/progguide/index.html, Abruf: 20. Januar 2008<br />
[Böh06] Böhm, Oliver: Aspekorientierte Programmierung <strong>mit</strong> AspectJ 5. Dpunkt-<br />
Verlag, 2006. – ISBN 3–89864–330–1<br />
[Lad03] Laddad, Ramnivas: AspectJ In Action. Manning, 2003. – ISBN 1–930110–93–6<br />
[Mar] Metric mishap caused loss of NASA orbiter. http://www.cnn.com/TECH/<br />
space/9909/30/mars.metric.02/, Abruf: 20. Januar 2008<br />
[Syn] Syntax <strong>der</strong> TypePatterns. http://www.eclipse.org/aspectj/doc/<br />
released/progguide/semantics-pointcuts.html#type-patterns, Abruf:<br />
20. Januar 2008<br />
[The] The Apache Ant Project. http://ant.apache.org/, Abruf: 20. Januar 2008<br />
42
Abbildungsverzeichnis<br />
1.1 Logging in Apache Tomcat . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />
1.2 XML-Parsing in Apache Tomcat . . . . . . . . . . . . . . . . . . . . . . . 6<br />
2.1 Wo AOP angreift . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />
2.2 Eine Java-Klasse Konto <strong>mit</strong> Beispiel-Join-Points . . . . . . . . . . . . . . 9<br />
2.3 Der Advice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11<br />
3.1 Blackbox-Ansicht eines Projekts . . . . . . . . . . . . . . . . . . . . . . . 14<br />
3.2 Umwandlung eines Java-Projekts in ein AspectJ-Project . . . . . . . . . 16<br />
3.3 Der Aspekt Ausgeben von SQL-Statements zeigt auf eine Codestelle . . . 18<br />
3.4 Main-Methode, auf die <strong>der</strong> Aspekt Ausgeben von SQL-Statements zeigt . 18<br />
4.1 Die excluded-/included-Anweisung . . . . . . . . . . . . . . . . . . . . . 22<br />
4.2 Die technische Architektur des Prototyps . . . . . . . . . . . . . . . . . . 23<br />
4.3 Programmablauf des Prototypen . . . . . . . . . . . . . . . . . . . . . . . 26<br />
5.1 ValueObject-Klasse CountGetterUndSetter . . . . . . . . . . . . . . . . . 31<br />
5.2 Benutzerspezifisches Aspekt GetterAndSetter.aj . . . . . . . . . . . . . . 32<br />
5.3 Loggerausgabe <strong>mit</strong> dem Ergebnis des benutzerspezifischen Aspekts . . . . 32<br />
6.1 Die Verwendung <strong>der</strong> config.properties-Datei . . . . . . . . . . . . . . . . 37<br />
43
Listings<br />
2.1 Ein Aspekt in AspectJ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8<br />
3.1 Die erste Codezeile vom Aspekt SqlStatementsAspect . . . . . . . . . . . 17<br />
3.2 Pointcut- und Join-Point-Definition im Aspekt SqlStatementsAspect . . . 17<br />
3.3 Advice-Definition im Aspekt SqlStatementsAspect . . . . . . . . . . . . . 17<br />
6.1 relevante Pointcuts bei <strong>der</strong> Verwendung des Prototyps . . . . . . . . . . . 35<br />
6.2 Benutzungsbeispiel des within() im Prototyp . . . . . . . . . . . . . . . . 35<br />
6.3 Die Konfigurationsdatei config.properties . . . . . . . . . . . . . . . . . . 38<br />
6.4 Ein einfaches compile Target für Ant . . . . . . . . . . . . . . . . . . . . 39<br />
44