AspectJ

język programowania

AspectJ jest aspektowym rozszerzeniem dla języka Java realizującym paradygmat programowania aspektowego. Został stworzony w laboratoriach Xerox PARC w zespole kierowanym przez dr. Gregora Kiczalesa, prowadzącego badania nad technikami modularyzacji kodu. Pierwsza publiczna wersja powstała w 2001 roku - datę tę oficjalnie uznaje się za początek jego istnienia.

Podstawowe założenia edytuj

W języku Java, oraz w innych obiektowych językach programowania podstawową jednostką modularności jest klasa. AspectJ wprowadza dodatkową jednostkę jaką jest aspekt. Aspekt opisuje zagadnienie, którego implementacja przecina więcej niż jedną klasę. Kod Java napisany przy użyciu AspectJ składa się z klas reprezentujących tradycyjną strukturę modularną oraz z aspektów, które ją przecinają. Najważniejszą ideą AspectJ (jak i również samego paradygmatu programowania aspektowego) jest wspomaganie separacji zagadnień i rozdzielenie programu na części w jak największym stopniu niezwiązane funkcjonalnie.

Przecinanie standardowej struktury modularnej klas w AspectJ dzieli się na:

  • statyczne (modyfikujące strukturę klas, hierarchię dziedziczenia),
  • dynamicznie (zmieniające zachowanie programu).

W czystym kodzie bez dodatkowych narzędzi nie sposób dojrzeć aspektów. Ich łączenie z implementacją odbywa się na poziomie kodu bajtowego. Kod źródłowy podlegający modularyzacji na klasy i aspekty jest kompilowany do kodu bajtowego, w który aspekty zostają wplecione. AspectJ jest pełnym językiem programowania - oprócz składni posiada także własne narzędzia – kompilator i debugger. Ponadto od czasu gdy został przeniesiony do projektu Eclipse obserwuje się jego coraz silniejszą integrację z tą platformą.

Składnia edytuj

W celu poprawnego opisu problemu przekrojowego AspectJ definiuje kilka nowych elementów składni, rozszerzających znaczenie podstawowej semantyki znanej z języka Java:

  • punkty złączeń (ang. joinpoints),
  • punkty przecięcia (ang. pointcuts),
  • rady (ang. advices).
  • aspekty.

Punkty złączeń (joinpoints) edytuj

Podstawowym elementem programowania aspektowego jest definicja punktu złączenia. Punkt taki jest atomową jednostką opisu problemu przekrojowego. Może nim być dowolny identyfikowalny przez semantykę punkt programu. W praktyce typowymi punktami złączenia może być:

  • wykonanie dowolnej metody lub konstruktora,
  • odwołanie do pola w klasie,
  • statyczna inicjacja klasy,
  • obsługa wyjątku.

Poniższy przykład punktu złączenia ilustruje przechwycenie wywołania na obiekcie klasy Point metody setX() z argumentem typu double:

call(void Point.setX(double))

Punkty przecięcia (pointcuts) edytuj

Punkty przecięcia grupują w jednostki logiczne, za pomocą określonej deklaracji, dowolne zbiory punktów złączeń. Można nawet rzec, że istnienie punktów przecięcia jest naturalną konsekwencją istnienia punktów złączeń. Punkty przecięcia mogą mieć dostęp do wybranych części kontekstu wykonywania, w którym nastąpiło złączenie aspektu, i eksponować je do użycia w deklaracji rady.

W ramach logiki grupującej punkty złączeń w punktach przecięcia mogą być wykorzystane operatory:

Punkty przecięcia można podzielić według dwóch podstawowych kryteriów:

  • złożoności:
    • proste punkty przecięcia - zbudowane z jednej klauzuli dopasowującej,
    • punkty złożone - opisane negacją, alternatywą lub koniunkcją prostych punktów przecięcia.
  • tożsamości:
    • nazwane punkty przecięcia - mogą być wielokrotnie używane w różnych miejscach aplikacji,
    • anonimowe punkty przecięcia - są tworzone w miejscu ich użycia i nie można odwołać się do nich z innej części systemu.

Poniższy przykład ilustruje punkt przecięcia grupujący w sobie zbiór wywołań metod pewnych abstrakcyjnych klas geometrycznych:

public pointcut zmianaGeometrii():
        call(void FigureElement.setXY(int,int)) ||
        call(void Point.setX(int)) ||
        call(void Point.setY(int)) ||
        call(void Line.setP1(Point)) ||
        call(void Line.setP2(Point));

Dowolny element, który w punkcie przecięcia został opisany szczegółowo, można opisać także ogólnie, stosując znaki unikowe * (dopasowanie nazwy) oraz + (domknięcie przechodnie relacji dziedziczenia). Lista argumentów zaś może być zastąpiona poprzez dwie kropki:

public pointcut zmianaGeometrii():
        call(void FigureElement.set*(..)) ||
        call(void Point.set*(int)) ||
        call(void Line.set*(Point));

Rady (advices) edytuj

Rada jest funkcjonalnym blokiem kodu, który należy wykonać w momencie napotkania danego punktu przecięcia. Definicja rady w AspectJ składa się z określenia momentu uruchomienia porady, definicji punktu przecięcia oraz ciała rady.

Punkt przecięcia mapuje daną linię w kodzie źródłowym. Rada, w zależności od funkcjonalności, którą chcemy uzyskać może zostać wywołana przed, w czasie, lub po osiągnięciu punktu przecięcia. Aby wybrać pożądany należy wybrać jedną z poniższych rad:

  • before() – wykonywana jest tuż przed wystąpieniem punktu przecięcia. Ten typ rady może służyć na przykład do weryfikacji parametrów metody, zmiany sposobu sterowania etc.
  • after() – wykonywana jest zaraz po wystąpieniu punktu cięcia. Sam moment wystąpienia punktu przecięcia jest nieprecyzyjny, dlatego można wyróżnić dwie sytuacje, które ta rada opisuje:
    • taką w której wykonanie oryginalnego kodu w punkcie cięcia zakończyło się poprawnie (sytuacja taka opisywana jest klauzulą after() returning)
    • taką w której efektem wykonania kodu było zgłoszenie wyjątku (opisywane klauzulą after() throwing)
  • around() - rada ta w odróżnieniu od dwóch poprzednich, posiada możliwość zastąpienia oryginalnego kodu związanego z punktem cięcia treścią porady oraz możliwością zezwolenia lub odmówienia wykonania punktu przecięcia za pomocą polecenia proceed().

Kilka przykładowych rad:

// przed wykonaniem punktu przecięcia
before(): zmianaGeometrii() {
    System.out.println("Przeliczanie geometrii...");
    Screen.getCurrentScreen().update();
}

// po opuszczeniu punktu przecięcia
after(): zmianaGeometrii() {
    System.out.println("Zakończono przeliczanie geometrii");
}

// wykonywane po wyrzuceniu w punkcie przecięcia wyjątku
after() throwing (GeometryException e): zmianaGeometrii() {
    System.out.println("Błędna geometria. Przyczyna " + e.getMessage());
    // ... dodatkowa logika obslugi bledow
}

// wykonywane zamiast punktu przeciecia
void around(): zmianaGeometrii() {
    boolean geometryChangeAllowed = // ...
    if (geometryChangeAllowed) {
         proceed();
    } else {
         return;
    }
}

Aspekty edytuj

Aspekt jest główną jednostką modularyzacji programu napisanego przy pomocy AspectJ ponieważ występuje w nim jako niezależny moduł, o charakterystyce mocno podobnej do klasy. Oznacza to, że aspekt można traktować jako wyspecjalizowaną klasę, która ma możliwość przecinania implementacji, oraz modyfikacji jej struktury i/lub zachowania.

W aspekcie mogą być zadeklarowane, poza typowymi elementami klasy, jak pola i metody, elementy definiujące punkty cięć (czyli punkty, w których kod programu przecina się z aspektem), oraz rady, będące substytutem metod i fragmentami kodu, który ma być wykonywany w odpowiednim punkcie cięcia.

Źródła aspektów zapisywane są w plikach o rozszerzeniu ".aj". Po skompilowaniu natomiast przyjmują postać kodu bajtowego i zapisane są w standardowych plikach binarnych z rozszerzeniem ".class". Z perspektywy maszyny wirtualnej Javy są one traktowane jako zwyczajne klasy.

Poniższy przykład ilustruje konstrukcję prostego aspektu logowania błędów z metod publicznych:

public aspect PublicErrorLogging {

    private Log log = new Log();

    pointcut publicMethodCall():
        call(public * com.sarxos.*.*(..));

    after() throwing (Exception e): publicMethodCall() {
        log.write(e);
    }
}
 
Proces wplatania logiki aspektowej w kod obiektowy.
 
Źródło aspektu jest kompilowane do pliku class.

Tkacz edytuj

Podczas rozwiązywania problemów przekrojowych ich implementacja zostaje przeniesiona do logiki aspektowej. Jednak ich funkcjonalność nadal jest wpleciona w kod obiektowy. Na poziomie kodu źródłowego problem został wyeliminowany, jednak binarny kod obiektowy nadal jest obiektowy. Aby funkcjonalność była zachowana, konieczne jest splatanie logiki obiektowej i aspektowej w wynikowym kodzie binarnym. Zadaniem tym zajmuje się tkacz (ang. weaver) w procesie tzw. tkania (ang. weaving).

Niezależnie od zastosowanej techniki tkania, istota tego procesu polega na identyfikacji określonych punktów programu (punktów złączeń), w które wplatany jest dodatkowy kod aspektowy, a rolę tkacza odgrywa specjalny kompilator, który produkuje kod wynikowy będący prawidłowym programem w języku Java.

Proces tkania odbywać się może na jedną z trzech możliwych metod:

  • Tkanie w czasie kompilacji (ang. compile-time weaving). W przypadku gdy istnieje kod źródłowy dla danej aplikacji, ajc (kompilator aspektowy) skompiluje go do postaci splecionych binarnych plików klas. Splatanie w tym przypadku jest jednoznaczne z procesem kompilacji, a aspekty mogą być dostarczone w formie binarnej lub źródłowej. Jeśli jakieś aspekty są w procesie kompilacji wymagane przez klasy wynikowe, wówczas tkanie w czasie kompilacji powinno mieć zastosowanie. Aspekty mogą być wymagane w czasie kompilacji klas jeśli np. modyfikują lub dodają pola klas, a inne klasy z tych pól korzystają.
  • Tkanie po kompilacji (ang. post-compile weaving), często również nazywane tkaniem binarnym. Ta metoda ma zastosowanie w przypadku splatania istniejących klas i plików JAR.
  • Tkanie w czasie ładowania (ang. load-time weaving, LTW). Jest to prosta metoda binarnego splatania w czasie gdy ładowarka klas ładuje klasę i definiuje ją w wirtualnej maszynie. Aby ta metoda była dostępna, w kodzie użyta musi zostać jedna z tkających ładowarek klas.
 
AJDT w Eclipsie.

AJDT edytuj

AJDT (ang. AspectJ Development Tools) jest wtyczką do projektu Eclipse wspomagającą pisanie kodu aspektowego. Dzięki tej wtyczce możliwe stają się między innymi:

  • dodanie/usunięcie z projektu natury aspektowej,
  • edycja (wraz z podpowiadaniem) plików źródłowych aspektów,
  • analiza referencji przekrojowych (ang. cross-references) w nowym widoku.

Ponadto AJDT dodaje nowe elementy do standardowych widoków perspektywy Java:

  • indykator natury aspektowej dla projektu w Eksploratorze pakietów i zakładce Nawigator,
  • nowa ikonka typu aspektowego dla plików z rozszerzeniem ".aj",
  • wizualizacja punktów cięć oraz ich typów na paskach kontekstowych edytora Javy,
  • dodatkowe indykatory punktów cięć na liście zakładki Outline.

Wtyczka ta w olbrzymim stopniu wspomaga proces tworzenia kodu aspektowego.

Zobacz też edytuj

Linki zewnętrzne edytuj