Články a foto


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

Ukážeme vám priestor

Náhodná fotografia

Tatry Portál - všetko o Vysokých Tatrách