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äť |