Każdy kto cokolwiek słyszał o AspectJ wie, że można za jego pomocą modyfikować wywołania metod (dynamic crosscutting), jednak mało kto wie, że za jego pomocą można także modyfikować statyczne właściwości klas, np dodawać atrybuty lub metody. To jest właśnie static crosscutting. Jak wykorzystać static crosscutting zaprezentuję na prostym przykładzie.
Tak więc mamy klasę reprezentująca wpis w serwisie blogowym.
package blog;
import java.util.ArrayList;
import java.util.List;
public class Wpis {
private String tresc;
private List komentarze = new ArrayList();
public Wpis() {
}
Wpis(String tresc){
this.tresc = tresc;
}
public String getTresc() {
return tresc;
}
public void setTresc(String tresc) {
this.tresc = tresc;
}
public List getKomentarze() {
return komentarze;
}
public void setKomentarze(List komentarze) {
this.komentarze = komentarze;
}
public void addKomentarz(String komentarz){
this.komentarze.add(komentarz);
}
@Override
public String toString() {
return "Wpis o tresci: " + tresc;
}
}
Dla celów demonstracyjnych utworzyliśmy także klasę Test demonstrującą użycie klasy Wpis.
package blog;
public class Test {
public static void main(String[] args) {
Wpis w = new Wpis("Hello world");
System.out.println(w.getTresc());
System.out.println(w.toString());
}
}
Załóżmy teraz, że chcemy dodać do niej atrybut reprezentujący datę utworzenia, automatycznie inicjowany bieżącym czasem.
package blog;
import java.text.MessageFormat;
import java.util.Date;
public aspect WpisAspect {
private Date blog.Wpis.dataUtworzenia;
public Date blog.Wpis.getDataUtworzenia(){
return this.dataUtworzenia;
}
after(Wpis wpis) returning() : execution(blog.Wpis.new(..)) && target(wpis) {
wpis.dataUtworzenia = new Date();
}
String around(Wpis wpis) : execution(public String blog.Wpis.toString()) && target(wpis) {
return MessageFormat.format("Wpis z dnia {0} o tresci: {1}", wpis.getDataUtworzenia(), wpis.getTresc());
}
}
Najpierw definiujemy nowy atrybut w klasie Wpis - jak widać nie różni się to od zwykłego utworzenia atrybutu, z wyjątkiem tego, że nazwa atrybutu zawiera też nazwę typu w którym będzie zdefiniowany. Następnie do klasy Wpis dodajemy metodę umożliwiającą pobranie wartości nowo-utworzonego atrybutu. Dokonujemy też modyfikacji wszystkich konstruktorów, tak by go odpowiednio inicjowały. Ostatnią modyfikacją jest "nadpisanie" metody toString() własną implementacją, która uwzględnia atrybut dataUtworzenia.
Oto rezultat uruchomienia klasy test:
Hello world
Wpis z dnia 02.12.09 19:33 o tresci: Hello world
Jak widać wszystko działa zgodnie z naszymi zamierzeniami.
Do czego można wykorzystać static crosscutting?
Pierwszą rzeczą jaka się nasuwa w odpowiedzi jest możliwość modyfikacji kodu do którego nie posiadamy źródeł. W takiej sytuacji możemy dokonać modyfikacji w kodzie bez nieetycznej dekompilacji.
Drugim, chyba sensowniejszym przykładem jego użycia będzie dodanie do klasy elementów, które chcemy mieć, ale które są typowo technicznymi jej składnikami, np logger, czy połączenie z bazą danych.