2.8.2004 - Úvod do aspektovo orientovaného programovania
Abstrakt
Aspektovo orientované programovanie je pomerne nová,
mladá disciplína. Téma, ktorá mi bola zverená bola o to ?ažšia na spracovanie,
pretože existuje len ve?mi málo zdrojov pojednávajúcich o tejto vetve
programovania. Na Slovensku som sa s touto tematikou ešte nestretol. O
to ?ažšie bolo preloži? jednotlivé dôležité – zlomové pojmy tohto jazyka
do sloven?iny. Ich preklad som zvolil ako kombináciu významu pojmov a
problematiky ich nasadenia.
Aspektové programovanie je zaujímavé svojím nasadením a špecifickým použitím,
o ktorom je v tejto práci pojednávané teoreticky i na konkrétnych príkladoch.
Konkrétne prinášam použitie aspektov v programovacom jazyku Java s jeho
rozšírením o aspekty AspektJ, v jazyku ktorého sú robené všetky demonštra?né
príklady.
Úvod
Aspect-Oriented Programming a WebObjects
AspectJ je asprektovo orientované rozšírenie programovacieho
jazyka Java. Je vo?ne dostupné ako „open source“. Verzia 1.0 bola uvo?nená
30. novembra roku 2001. Je ?as pre vývojárov WebObjects, aby ocenili ?i
a ako môžu prosperova? s používania AspectJ.
Aspect-Oriented Programming (AOP)
Don't Repeat Yourself ( = DRY) – „neopakuj sa“, je dôležitou
vodiacou ?iarou pre dobré programovanie. S objektovo orientovaným programovaním
nie je vždy jednoduché nasledova? DRY princíp. Niekedy sa prichytíme pri
implementovaní toho istého druhu funk?nosti v rôznych metódach a triedach.
Triedy sú nato?ko odlišné, že nemôžu ma? všeobecnejšiu nadtriedu. A implementácia
pre ich vlastnú funk?nos? je nato?ko malá, že vytvorenie všeobecného delegáta
alebo pomocnej metódy je ve?mi komplikovaná. AOP sa snaží pomôc? rieši?
tieto typy problémov.
Príkladom je oby?ajné logovanie - malé „jednoriadky“ kódu.. S AOP môžeme
špecifikova? kde a ako logova?, nájs? výskyt a vybra? tieto logovacie
príkazy do jednej samostatnej unity – aspektu.
Zadefinovanie nosných pojmov
Nahliadnime na dôležité pojmy aspektového programovania:
Aspect / aspekt Nový koncept, ktorý dovo?uje implementova?
viacscénickos? na jednom mieste.
Joinpoint / spojovací bod Spojovacie body sú oblasti
v programovej štruktúre, ktoré môžu by? použité ako spojky pre Aspekty.
Pointcut /bod zlomu Kde aplikova? Aspekt v termíne spojovacích
bodov.
Advice / pokyn ?o by mal aspekt vykonáva? v spojovacom
bode.
Weaving / tkanie Aplikovanie aspektu do kódu
Viacscénickos? – alebo prelínanie kódov
Pre pochopenie apektov je dôležité pochopi? spôsob,
ako daná aplikácia pracuje. Nosným pojmom v oblasti aspektov je crosscutting
concern – teda viacscénickos?, alebo prelínanie kódov aplikácie.
Na obrázku možno vidie? princíp chodu aplikácie.
Concern identifier – bod zlomu
Concerns implementation – chod aplikácie a aspektu
Weaver – následné spätné dotkanie kódu
Pri novom logovaní aspektu sa rozhodneme, o ktoré spojovacie
body máme záujem, bod pred invokáciou metódy, napríklad. Definujem bod
zlomu pre tento spojovací bod spolu s informáciou že, napríklad, aspekt
by mal by? aplikovaný na všetky triedy aktuálneho balíka (package), a
v týchto triedach aplikovaný na všetky skupiny metód. Korešpondujúci pokyn
môže by? implementovaný na logovanie názvu metódy a novej hodnoty s ktorou
skupina metód bola volaná.
Tento príklad aspektu môže by? vyjadrený v jednej ?asti 10 riadkami kódu
a nahrádza všetky individuálne logy v každej skupine metód.
AspectJ
Xerox PARC rozšíril programovací jazyk Java, aby umožnil
aspektovo orientované programovanie. Jeho AspectJ compilátor (ajc) je
reimplementáciou javac kompilátora plus rozšírenie pre nové AOP funkcie.
Samotný AspectJ je implementovaný v Jave, a kompiluje do štandartného
byte kódu Javy, ktorý je spustite?ný bežným Java Virtual Machines (JVM).
Alternatívne, AspectJ môže by? použitý ako preprocesor na produkovanie
štandardných Java sources.
Momentálne, AspectJ môže zakomponováva? aspekty iba po?as kompilovania
do vašich tried; napr. triedy potrebujú by? dostupné v zdroji (source)
a by? kompilované spolo?ne s aspektami. Jam „make“-súbory nemôžu automaticky
vymedzova?, ktorá z vašich tried bude ovplyvnená ktorým aspektom. Preto
musíte urobi? „clean-build“ vždy ke? meníte aspekt alebo meníte triedu
ovplyvnenú aspektom. Špeciálne pre ve?ké projekty, toto môže spomali?
vývoj aplikácie.
Od kedy je AspectJ používaný, kompilovaný kód nie je zapísaný pomalšie
v porovnaní s programami tých istých funkcií, implemetovanými bez aspektov.
Nasledujúca verzia AspectJ vylepší ?as kompilovania, a ?alšia verzia dovolí
byte kód tak, že aspekty môžu by? aplikované do tried, ktoré nie sú dostupné
v zdroji.
Inštalácia AspectJ
Chceme používa? AspectJ ako javac alebo jikes s Project
Builderom na tvorbu WebObjects aplikácií.
Pre nainštalovanie AspectJ a integráciu do Project Builder-a je nutné
nasledova? tieto kroky. Tieto pokyny boli napísané pre inštaláciu AspectJ
v1.03, WebObjects 5.1 a opera?ný systém OS X 10.1.2. Rozdielne verzie
môžu vyžadova? iné nastavenia.
Krok 1
Na stránke aspectj.org si stiahnite aspectj-tools -nástroje, poprípade
dokumentáciu.
Krok 2
Spustite stiahnutý jar súbor. Zobrazí sa grafická inštala?ná aplikácia.
Nasledujte inštrukcie pre doinštalovanie AspectJ.
Krok 3
Urobte inštalované skripty spustite?nými:
chmod +x ~/aspectj1.0/bin/*
Krok 4
Vložte cestu k ajc skriptu do nastavenia premennej PATH.
Napríklad:
sudo ln -s ~/aspectj1.0/bin/ajc /usr/local/bin/ajc
Krok 5
Ak používate originálne vývojárske nástroje a nemodifikovali ste PBXCore.framework
ani ProjectBuilderJambase, jednoducho ich upgradnite súbormi ProjectBuilderJambase.diff
Jambase-parse-info.plist.diff, nájdenými na internete na www.stepwise.com,
alebo ich môžete zmeni? manuálne.
sudo patch -bp0 <ProjectBuilderJambase.diff
sudo patch -bp0 <Jambase-parse-info.plist.diff
Krok 6
Reštartujte Project Builder.
Krok 7
Vo vašich WebObjects projektoch vždy potrebujete prida?:
JAVA_COMPILER = ajc
do Build nastavení cie?a aplika?ného servera (Application
Server target)ak chcete používa? AspectJ.
Step 8
„jar“ súbor aspectjrt.jar z aspectj1.0/lib/ musí by? v ceste ku triedam
(classpath). Alebo pridajte súbor do cie?a aplika?ného servera a nastavte
ho na "Merge" vo Files & Build Phases/Frameworks & Libraries
pane.
Step 9
Prípadne, nastavte "Build log detail level" na "Detailed
Logs" v nastaveniach Project Buildera, ?o pomôže nájs? ?ahšie chyby
vzniknuté pri inštalácii AspectJ.
Teraz ste pripravený na tvorbu aspektov vo vašich WebObjects-projektoch.
Používajte príponu „.java“ pre súbory obsahujúce aspekty. Prípona „.aj“
môže by? akceptovaná ajc kompilátorom tiež, ale potrebuje viac práce s
integráciou Project Buildera.
?alšie otvorené vydanie integrujú AspectJ debugger ajdb , pridávanie k?ú?ových
slov AspectJ do Project Builderovskej syntaxe, farebné oddelenie entít
AspectJ v Project Builderovskej funkcie pop-up. Pokro?ilejšia otvorená
úloha je rozšíri? Project Builder tak, že automaticky ukazuje, ktoré triedy
a metódy budú ovplyvnené aspektom.
Písanie aspektov
Jednoduchá aplikácia AOP s WebObjects je použitie aspektu
na rozšírenie EOF obchodnej triedy. Použite EOModeler na vytvorenie súboru
Java triedy so všetkým príslušenstvom pre konštantné vlastnosti. Potom
vytvorte ?alší súbor Java s aspektom pre pripojenie prídavných obchodných
logík, zme?te príslušenstvo a pridajte tranzitné vlastnosti.
Vytvorenie jednoduchého aspektu
Predpokladajme, že máte entitu menom Contact, ktorá je používaná pre aplikáciu
Adresár. Contact má tri vlastnosti, firstName, lastName a companyName
typu re?azec (string), a volite?nú „to-many“ väzbu na inú entitu nazvanú
ContactCategory. V Project Builderi, vytvoríme nový Java súbor nazvaný
Contact_Extension.java zapíšeme nasledujúci blok:
import com.webobjects.foundation.*;
import com.webobjects.eocontrol.*;
public aspect Contact_Extension {
// vytvoríme novú metódu do Contact
public String Contact.shortDescription() {
return lastName() + ", " + firstName();
}
// rozšírime metódu implementovanú v Contact - superclass
public void Contact.validateForSave() throws NSValidation.ValidationException
{
super.validateForSave();
if (lastName() == null && companyName() == null)
throw new NSValidation.ValidationException("Enter either a name or
a company.");
}
// rozšírime metódu o before, robiacu nie?o pred
before(): call(String Contact.firstName()) {
System.out.println("preparing firstName");
}
// rozšírime metódu o after, robiacu nie?o za
after(): call(String Contact.lastName()) {
System.out.println("concluding lastName");
}
}
Ke? sa tento súbor pridá do cie?a aplika?ného servera,
AspectJ kompilátor pridá metódy shortDescription() a validateForSave()
do Contact triedy kde budú dostupné a budú sa chova? ako keby boli implementované
vo vnútri triedy Contacts. Tak isto dve get metódy firstName() a lastName()
budú modifikované compilerom.
Analýza kódu
Pozrime sa na tieto riadky kódu z Aspektu:
before(): call(String Contact.firstName()) {
System.out.println("preparing firstName");
}
Toto je "before" príkaz, ktorý znamená, že
telo kódu medzi zátvorkami je volané pred bodom zlomu. ?as? za dvojbodkou
a pred telom kódu je bod zlomu. V tomto prípade, bod zlomu je metóda nazvaná
firstName() triedy Contact. Ak firstName() je implementované ako:
public String firstName() {
return (String)storedValueForKey("firstName");
}
aplikovanie príkazu vytvorí kód ekvivalentný s:
public String firstName() {
System.out.println("preparing firstName");
return (String)storedValueForKey("firstName");
}
Druhý príkaz je podobný prvému. Jeho telo bude volané
po volaní lastName() z Contact. Kompilovaná metóda bude ekvivalentná s:
public String lastName() {
try {
return (String)storedValueForKey("lastName");
} finally {
System.out.println("concluding lastName");
}
}
Sú možné tri druhy príkazov: before, after a around.
Tu je príklad na príkaz around:
void around(Contact aContact, String theNewLastName):
call(void Contact.setLastName(String)) && target(aContact) &&
args(theNewLastName) {
String replacementValue = theNewLastName != null ? theNewLastName.toUpperCase()
: null;
proceed(aContact, replacementValue);
System.out.println("replaced " + theNewLastName + " with
" + replacementValue);
}
Tento príkaz kapitalizuje písmená parametra, prístupného
pri vykonávaní originálnou metódou s novou hodnotou argumentu a následného
logovania ur?itých informácií. Príkaz Around musí vraca? hodnotu pretože
je zodpovedný za vrátenie. V tomto príklade je typom procedúra, pretože
setLastName(..) je tiež procedurálnou metódou. Nie je nutné pokra?ova?
do originálnej metódy a vrátené hodnoty môžu by? modifikované. V tomto
príkaze pre prístup na obsah setLastName(..), používame body zlomu na
príjímanie cie?ových inštancií a argument objektu. Tieto objekty tiež
potrebujú by? deklarované ako argumenty príkazu. Argumenty príkazu nie
sú preto naozaj pochopite?né bez prezretia bodu zlomu daného príkazu.
Pridávanie premennej do triedy pomocou aspektu
Rovnako ako metódy, aspekty môžu pridáva? premenné do tried.
Tu je príklad, ktorý spo?íta množstvo volaní willChange() na Contact objekte
s novou premennou inštancie:
int Contact.willChangeCounter = 0;
after(Contact aContact): call(void Contact.willChange())
&& target(aContact) {
aContact.willChangeCounter++;
System.out.println("willChange: " + aContact.willChangeCounter);
}
Aspekty môžu tiež deklarova? statické premenné a statické
metódy do triedy.
Porovnate?ný je príklad, ktorý po?íta, ko?ko krát bola inštancia Contact
vytvorená:
int instanceCounter = 0;
after(Contact aContact) returning: initialization(new())
&& target(aContact) {
instanceCounter++;
System.out.println("instanceCounter: " + instanceCounter);
}
"after(..) returning" je špeciálny príkaz ktorý
sa vykoná len ke? daná metóda vráti hodnotu bez výnimky; v našom prípade,
ak inštancia Contact bola úspešne vytvorená. V tomto príklade je nový
bod zlomu initialization(..) ktorý sa používa na nájdenie konštruktora.
Volanie aspektov na špecifické komponenty
Logovanie je všeobecným príkladom viacscénnosti, ktorý môže by? jednoducho
implementovaný jednoduchým aspektom. Povedzme, že nechceme prida? logovanie
všetkým komponentom appendToResponse(..), invokeAction(..), takeValuesFromRequest(..)
a všetkým set() metódam. Pridáme nový súbor, nazvaný MyLogging.java do
projektu s nasledujúcim obsahom:
import com.webobjects.appserver.*;
public aspect MyLogging {
pointcut requestResponseSet(): (
target(WOComponent) && (
call(void appendToResponse(WOResponse, WOContext))
|| call(WOActionResults invokeAction(WORequest, WOContext))
|| call(void takeValuesFromRequest(WORequest, WOContext))
|| call(* set*(..))
)
);
before(): requestResponseSet() {
System.out.println("before " + thisJoinPoint);
}
after(): requestResponseSet() {
System.out.println("after " + thisJoinPoint);
}
}
Nachádzame tu nieko?ko nových vecí. Všetky predchádzajúce
príkazy používali anonymné body zlomu ako ?as? príkazu. Tu, sa definujú
pomenované body zlomov "requestResponseSet" a používajú sa pre
dva príkazy. Pomenované body zlomu robia príkaz ?itate?nejší. Všimnime
si použitie masky na bode zlomu: "* set*(..)" znamená ?ubovo?nú
metódu za?ínajúcu na "set", ktorá má ?ubovolné množstvo argumentov
a ?ubovo?nú návratovú hodnotu (vrátane void). Nový je tiež thisJoinPoint,
ktorý je objekt vždy použite?ný v tele aspektu. Zahr?uje kontextové informácie
aktuálneho spojovacieho bodu.
Logovanie môže by? jednoducho vypnuté odstránením aspektu z cie?a aplika?ného
servera (Application Server target). Bude to jasnejšie, ke? implementácia
logovania môže by? teraz zmenená centrálne.
Ten istý aspekt používajúci log4j namiesto System.out:
import com.webobjects.appserver.*;
import org.apache.log4j.*;
public aspect MyLogging {
public static Category cat =
Category.getInstance(MyLogging.class);
pointcut requestResponse(): (
target(WOComponent) && (
call(void appendToResponse(WOResponse, WOContext))
|| call(WOActionResults invokeAction(WORequest, WOContext))
|| call(void takeValuesFromRequest(WORequest, WOContext))
|| call(* set*(..))
)
);
before(): requestResponse() {
cat.info("before " + thisJoinPoint);
}
after(): requestResponse() {
cat.info("after " + thisJoinPoint);
}
after(Application application): initialization(new())
&& target(application) {
String path =
application.resourceManager().pathForResourceNamed("log4j.properties",
null, null);
PropertyConfigurator.configure(path);
}
}
V posledných dvoch logovacích príkladoch sa bude logova?
len pre tie metódy, ktoré sú aktuálne implementované vo vašich WOComponent
podtriedach. Napríklad, v hlavnej Main komponente iba ak sme prepísali
(override) WOComponent appendToResponse(..) metódu. Môžeme implementova?
metódy ako ?asti aspektu, takže ich bude aspekt predstavova? Main triede
tak ako sme predstavovali metódu shortDescription() do Contact na za?iatku
tejto sekcie. Alternatívne, môžeme použi? AspectJ na nahradenie Main superclass
z WOComponent inou, ktorú môžeme definova? v tom istom súbore ako aspekt.
public class MyLoggingComponent extends WOComponent {
public MyLoggingComponent(WOContext aContext) {
super(aContext);
}
public void awake() {
System.out.println(getClass().getName() + " awake");
super.awake();
}
}
public aspect MyLoggingAspect2 {
declare parents: Main extends MyLoggingComponent;
}
Toto je príklad, kde Main awake() metóda bude stále logova?,
nezáležiac na prepise Main s awake. Podobná technika môže by? použitá
vytvorenie tried implementovaných interfejsom.
Na jednotlivých príkladoch možno vidie? variabilitu AspectJ – rôzne možnosti
zakomponovania aspektov do kódu.
Záver
Programátori, ktorý používali skoršie verzie WebObjects a vyvíjali v Objective-C
mohli zaznamena? podobnosti vlastnostiam chýbajúcim od prechodu na Javu.
Úvod do Aspektu je podobný kategóriám Objective-C, umož?uje rozšírenie
tried metódou.
Pozitívum aspektov vidím v možnosti pripájania premenných a rozšírených
existujúcich metód za špecifických podmienok.
Ich slabos?ou je obmedzenie práce s nimi len po?as kompilácie nad triedami,
ktoré sú kompilované spolu s aspektami. Táto nevýhoda aspektov bude ?iasto?ne
odstránená AspectJ verzie 2, v ktorom sa predpokladá, že bude schopný
aplikova? aspekty v bytovom kóde. Programová ?as? declare parent je tiež
prekvapujúco podobná s funk?nos?ou poseAs v Objective-C.
Toto je len stru?ný preh?ad schopností AspectJ. ?alšie vlastnosti obsahujú
viac spojovacích bodov, abstraktné aspekty, s ktorých môžu ostatné aspekty
dedi? (inherit), vnútorné aspekty (inner aspects) ktoré sú definované
v rámci triedy a iné.
AspectJ je ve?mi stabilný kompilátor, pripravený na využitie v programátorskej
praxi. Pracuje bezchybne spolu s WebObjects. Integrácia s Project Builderom
bude isto vylepšená.
Aspektovo orientované programovanie je relatívne mladá disciplína. Nie
je doposia? známe, ktoré znaky za akých okolností budú užito?né.
Použité zdroje
1. http://www.stepwise.com (ku d?u 19.3.2004)
2. http://AOSD.net (ku d?u 20.4.2004)
3. http://dev.eclipse.com.org (ku d?u 9.5.2004)
4. http://www.javaworld.com (ku d?u 12.5.2004)
späť |