Domov arrow Programovanie arrow Dobrá rada - Pointcuts (AOP)
Dobrá rada - Pointcuts (AOP)
pointcutDnes som strávil nejakú hodinku naháňaním problému v mojej skúšobnej Java Web aplikácii, orientovanej okolo Spring 3 Frameworku. Možno keď môj nález zdokumentujem, uľahčím niekomu život, ak bude v rovnakých koncoch a náhodou nájde tento článok.

Ešte stále nie som celkom zbehlý v AspectJ notácii Pointcut výrazov. Človek ich veľa nenapíše, lebo ich v bežnom projekte je relatívne málo. Taktiež tých pár dezignátorov Pointcutov, čo treba napísať, to buď napíše niekto druhý, alebo to človek zmákne sám a potom sa tomu chvíľu nevenuje a časom na to celkom zabudne. Nie sú to výrazy, ktoré programátor celkom bežne používa day in, day out.

Pre začínajúcich so Spring AOP tu mám krátky návod a vzorový príklad použitia aspektov v Spring aplikácii.

Problém

Chcel som napísať pointcut pre exekúciu každej metódy v určitom balíčku, ktorá nenavracia žiadnu hodnotu (void return type) a má jeden vstupný parameter - objekt akejkoľvek triedy. Taktiež som mal záujem odovzdať ten objekt (argument) môjmu around poradcovi (advisor), aby sním poprípade mohol čo-to urobiť.

Moja prvá strela bola dosť blízko:

<aop:aspect ref="my-aspect">
  <aop:pointcut id="p" expression="execution(void com.perhac.www.service.impl.*.*(java.lang.Object)) and args(obj)" />
  <aop:around method="m" pointcut-ref="p" arg-names="obj" />
</aop:aspect>

Všetko pri naštartovaní Spring kontextu prebehlo hladko, no breakpoint, ktorý som mal umiestnený priamo vo vnútri môjho aspektu (metóda vyššie označená ako m), však aplikácia nikdy nedosiahla. Samotný kód môjho aspektu nikdy nedostal možnosť nastúpiť na scénu a odohrať svoju úlohu. Frustrujúce. Potom som strávil hodiny hľadaním problému. Teda možno len hodinu, ale zdalo sa to ako večnosť.

Rozmýšľal som, či som kdesi náhodou nezabudol akosi aktivovať aspekty v konfiguračnom súbore Springu. Ťažko sa zisťuje, keď aspekty sú uvedené tak transparentne a pôsobia ako mágia, v čom je vlastne problém, keď aspekt sa nie a nie aktivovať. Po určitej dobe strávenej hútaním som bol už celkom pevne presvedčený, že to nie je problém ničoho iného, než zlého výrazu pre definíciu pointcut-u. No ani za ten svet prísť na to, prečo nefunguje.

Skúšal som typ Object napísať ako java.lang.Object, skúsil som to aj len ako Object.Mám zlý podpis metódy m v mojom aspekte? Pre kompletnosť uvediem aj ten:

public void m(ProceedingJoinPoint joinpoint, Object o) throws Throwable{...}

Je akýsi problém pri predávaní argumentu? Nie, pri akomkoľvek babraní sa v tomto sa Spring hneď začal sťažovať na nesprávny pointcut výraz, alebo na zlé viazanie argumentov medzi pointcutom a adviceom. V čom je teda problém?

Príčina - riešenie

Náhle ma osvietilo a zavetril som možnú príčinu toho, že môj aspekt sa nikdy nemá možnosť realizovať. V celkom projekte totiž nemám ani jednu jedinú metódu, ktorá by brala len jediný vstupný parameter typu Object. Opakujem, typu Object.

Samozrejme tu sme sa so Spring AOP nie celkom rozumeli. Chyba sa stala v preklade môjho zámeru do reči AspectJ pointcut definície. Ja som chcel aby metódy akceptujúce akýkoľvek objekt boli interceptované a bola im vnuknutá moja dobrá rada (advice). No Spring AOP to pochopil tak, že chcem radiť len metódam akceptujúcim konkrétne argumenty typu Object a len Object - no a také ja som nikde nemal, tak nebolo komu radiť a môj poradca ostal ticho ako voš pod chrastou napriek mojim úpenlivým prosbám, žiadostiam a výzvam.

Nahradenie vyššie uvedeného výrazu pre pointcut týmto výrazom:

execution(void com.perhac.www.service.impl..*.*(..)) and args(obj)

... vyriešilo všetky moje problémy a môj Aspekt bol hotový. (Dve bodky v názve balíku znamenajú, že akýkoľvek balík nižšie v hierarchii bude vyhovovať.)

Ešte krátke vysvetlenie tohto pointcut-u: Advice bude uplatnený kedykoľvek bude exekuovať metóda akéhokoľvek názvu, v akejkoľvek triede/rozhraní, v ktoromkoľvek balíčku v hierarchii nižšie než com.perhac.www.service.impl a nenavracia žiadnu hodnotu. Táto metóda môže prijímať akékoľvek množstvo argumentov akéhokoľvek typu odvodeného od java.lang.Object (použili sme dve bodky .. ) - nie však primitívne hodnoty (pre úplne akékoľvek argumenty by sme mohli použiť hviezdičku *). Výber miest, kde bude advice použitý ďalej zúžime len na metódy akceptujúce len jeden argument (bez tohto zúženia by boli vyhovujúce metódy s akýmkoľvek počtom parametrov, keďže sme použili dve bodky), ktorý si pomenujeme obj - "and args(obj)".

Poučenie

AspectJ notácia pointcut výrazov musí presne sedieť na všetky body, kde chceme náš kód mierne narezať a vsunúť do nich dobre mienené rady (advices). Keď sa vyjadríme, že metóda ma prijímať argument typu Object, metóda musí prijímať argumenty typu Object - a tak to aj presne musí byť v podpise metód, ktoré chceme obohatiť o určitý aspekt. Neznamená to, že metóda má prijímať akýkoľvek objekt triedy odvodenej od typu Object (čiže hocičo okrem primitívnych hodnôt). Pre vyjadrenie tohto zámeru (t.j. "metóda akceptuje hocijaký objekt, ale nie primitívnu hodnotu") treba použiť divokú kartu (wildcard) .. (respektíve *, ak nám je ozaj jedno čo metóda prijíma - či trebárs aj primitívne hodnoty). O tomto sa môžete dočítať konkrétne v tejto sekcii stránky o semantike jazyka AspectJ pre definíciu pointcut dezignátorov (výrazov). Relevantný výsek:

There is one special case for this kind of exposure. Exposing an argument of type Object will also match primitive typed arguments, and expose a "boxed" version of the primitive. So, pointcut publicCall(): call(public *.*(..)) && args(Object); will pick out all unary methods that take, as their only argument, subtypes of Object (i.e., not primitive types like int), but pointcut publicCall(Object o): call(public *.*(..)) && args(o); will pick out all unary methods that take any argument: And if the argument was an int, then the value passed to advice will be of type java.lang.Integer.

Takže hurá do programovania a tešme sa z aspektov a ďakujme múdrym hlavám, čo ich vymysleli :-)

Pre začínajúcich so Spring AOP tu mám krátky návod a vzorový príklad použitia aspektov v Spring aplikácii.

Posledná úprava ( Friday, 02 September 2011 )
 
< Predchádzajúca   Ďalšia >