Az aspektusok csak közvetett hívásoknál aktiválódnak! Ilyen esetek például:
Egy controller @RequestMapping metódusa, melyet nem közvetlenül hívunk, hanem a spring aktiválja.
@Autowired által behúzott bean metódusának hívása, hiszen ilyenkor csak a bean proxy van meghívva.
A valódi bean metódust a spring aktiválja.
Egy aspektus aktiválásához az aspektus osztályt a következő annotációkkal kell ellátni:
Az aspektus osztályon belül lévő metódusokat szintén el kell látni annotációval, melyek leírják mikor futnak,
és milyen metódusokat céloznak meg.
Aspektus osztályban nem működik, nem használható az @ExceptionHandler.
A pointcut kifejezésekben egy osztály, annotáció, ... megnevezése történhet a package megnevezése nélkül,
ha az az aspektus osztállyal egy csomagban van, ha viszont nem, akkor a teljes elérési utat ki kell írni:
vagy
Hatókör szűkítés, vagyis miért nem működnek egyes pointcut kifejezések ?
Az alábbi pointcut kifejezés esetén a program el sem indul:
a kapott hibaüzenet lényege:
Ez azért van, mert az AOP úgy működik, hogy AOP-proxy osztályokat készít a megfelelő osztályokhoz.
A pointcut kifejezést nem csak a saját osztályainkra, hanem a teljes classpath-ra alkalmazza,
így az összes projektbe behúzott library-re is próbálja alkalmazni. Ezért szinte biztos hogy lesz olyan bean,
amely final vagy non-visible, így nem lehet leszármaztatni, nem lehet rá aop proxy-t készíteni.
A megoldás a hatókör szűkítése, amikor a pointcut kifejezés hatókörét szűkítjük úgy,
hogy például csak az általunk írt csomagra és alcsomagjaira vonatkozzon.
A fenti pointcut kifejezés egy lehetséges helyes átalakítása:
dokumentáció
Spring reference documentation: "5. Aspect Oriented Programming with Spring"
Miután a pointcut kifejezés által a cél metódusok meg lettek határozva, azt kell eldönteni hogy az aspektus
osztály metódusa (advice metódus) mikor fusson le a cél metódushoz képest: előtte, utána, körülötte, ... ?
A lehetséges advice-ok:
@Before
@After
@AfterReturning
@AfterThrowing
@Around
Az advice-ok csak @Aspect annotációval ellátott osztályban használhatók.
Az advice metódusnak csak az első paramétere kötelező, melynek JoinPoint-nak kell lennie,
kivéve az @Around metódust, ahol az első paraméter kötelezően a ProceedingJoinPoint.
Az advice metódus többi paraméterével hozzáférhetünk a cél metódus egyes jellemzőihez, lásd a példáknál.
advice: @After
A cél metódus után fut le. Minden esetben lefut, akkor is ha a cél metódus exception-t dob.
Példa:
advice: @AfterReturning
A cél metódus után fut le, de csak akkor ha a metódus nem dob exception-t.
A cél metódus visszatérési értékéhez hozzá lehet férni, ha alkalmazzuk az @AfterReturning annotáció
"returning" paraméterét.
A "returning" típusának meg kell egyezni a cél metódus visszaadott típusával,
vagy annak ősének kell lennie.
Ha nem egyezik a típus, akkor exception nem keletkezik, viszont az advice metódus le sem fut.
advice: @AfterThrowing
A cél metódus után fut le, de csak akkor ha a metódus exception-t dob.
Be lehet állítani úgy, hogy csak adott exception-re (és leszármazottaira) aktiválódjon az advice metódus.
Ehhez az kell, hogy az @AfterThrowing annotáció "throwing" paraméterét használjuk.
Továbbá a kiváltott exception objektumhoz is hozzá lehet férni, az advice metódus paramétere által.
Ebben a példában az advice metódus csak akkor fut le, ha a cél metódus ArithmeticException-t dob:
advice: @Around
A cél metódus hívása előtt és után is fut, mivel az advice metóduson belül kell meghívni a cél metódust így:
Akár az is megtehető, hogy a cél metódust egyszer sem, vagy akár többször is meghívjuk.
Visszakapjuk a cél metódus visszatérési értékét.
Ha a cél metódus void-dal tér vissza, akkor null-t kapunk visszatérési értéknek.
A visszatérési érték módosítható, a hatása olyan mintha ezt adta volna vissza a cél metódus.
A cél metódus argumentumain is lehet módosítani, a proceed() paraméterével:
Példa:
advice: @Before
A cél metódus előtt fut le.
Példa:
JoinPoint
JoinPoint használata
A fenti példában a reflection api segítségével meghatározzuk a metódust,
és az osztályt amely a metódust tartalmazza.
Eztán levizsgáljuk, hogy az osztály-hoz rendelt @MyAnnotation("érték") hogy van beállítva.
Ugyanígy lehetne vizsgálni a metódushoz rendelt annotációkat is.
A reflection api miatt hozzá lehet férni az osztály statikus, és nem statikus tagjaihoz is.
A metódus nevét pedig így kapjuk meg legszebben:
A példában megkaptuk a metódus paramétereit (név, típus, ...),
és a metódus meghívásakor kapott paraméterek értékeit.
Ezekhez való hozzáférés:
examples
Mérjük le az összes közvetett módon hívott metódus végrehajtási idejét.
Irassuk ki a metódus nevét és a végrehajtás idejét.
Az "@annotation()" pointcut kifejezéssel olyan metódusokat célzunk meg, mely a BarAnnotation annotációval
van megjelölve.
Ennek az annotációnak van egy "value" paramétere, ehhez szeretnénk hozzáférni az advice metódusban.
1. megoldás: join point + reflection api használatával:
2. megoldás: advice metódus paraméterezésével:
Az "args()" pointcut kifejezéssel olyan metódusokat célzunk meg, melynek adott a 2 paraméterének típusa:
long, Color
Ezekhez szeretnénk hozzáférni az advice metódusban.
A pointcut kifejezésben hatókör szűkítést is alkalmazunk.
A programban a védett metódusok a @MyProtected annotációval vannak megjelölve.
A @Around advice metódus ellenőrzi, hogy a felhasználó be van-e jelentkezve.
Ha nincs, akkor nem futtatja le a cél metódust, hanem NoLoginException-t vált ki.
Feltételezzük, hogy a programban van egy globális exception handler, mely a NoLoginException esetén
redirektál a "/login" lapra.
A mysession objektumhoz @Autowired-del férünk hozzá.
A programban minden controller (kivéve a MyErrorController) http-get metódusára fut egy @Before advice metódus,
mely a user-id http-request paramétert vizsgálja.
Ha a http-request tartalmaz ilyen paramétert, és az megfelelő (7), akkor a mysession-be a user-id -t bejegyzi.
A mysession és httpServletRequest objektumhoz @Autowired-del férünk hozzá.
metódus lefutás sorrend
Ha egy cél metódust több advice céloz meg, akkor a következő a lefutási sorrend (azonos ORDER esetén).
Az ábrán a betolás azt jelzi, hogy a betolt rész nem biztos hogy lefut. Csak akkor fut le,
ha az around advice meghívja a cél metódust!
Ha valamelyik advice metódus vagy a cél metódus exception-t dob, akkor a fenti metódus lefutás megszakad,
az alatta lévők nem hajtódnak végre, kivéve az AFTER advice metódusok, azok mindig lefutnak !
Az @Order annotációval lehet változtatni a fenti lefutási sorrenden.
Az @Order csak az aspektus osztályokra alkalmazható, a metódusokra nem.
Az @Order úgy módosítja a sorrendet, hogy előszőr a nagyobb értékű @Order metódusait "csomagolja össze"
a cél metódussal, ezután ezt egy zárt csomagnak tekinti (mintha ez lenne mostantól a cél metódus),
majd a kisebb értékű @Order metódusait csomagolja ehhez.
pointcut
A pointcut kifejezések segítségével metódusokat célzunk meg (cél metódusok), melyekre az advice-ok aktiválódnak.
A pointcut kifejezések csak @Aspect annotációval ellátott osztályban használhatók.
A pointcut kifejezések közvetlenül is beírhatók az advice-ok (például: @Before) paramétereként:
vagy pedig a @Pointcut segítségével definiálható egy "logikai metódus" (például myMethods):
és az így létrehozott "logikai metódus" máshol/többször felhasználható:
A pointcut kifejezéseket logikai kapcsolatokkal (&& || !) és zárójelekkel is kombinálhatjuk:
pointcut: @annotation
Azoknak a metódusoknak a megcélzása, melyek egy adott annotációval vannak megjelölve.
Az osztályt jelölő annotációk nem számítanak a megcélzásnál.
Az @annotation paraméterének pontosan egy annotáció nevet kell megadni.
Azok a metódusok melyek a FooAnnotation annotációval vannak megjelölve:
Fordítási hibák:
pointcut: @args
Azoknak a metódusoknak a megcélzása melyeknek paraméterei annotált osztályok példányai.
Az @args paramétereiben csak annotációk sorolhatók fel, és a ".." egyszer szerepelhet!
A ".." jelentése: 0..n számú tetszőleges paraméter.
Az @args paramétereiben a "*" többször szerepelhet! Jelentése: 1 darab tetszőleges paraméter.
Hatókör szűkítéssel kell használni!
Azok a metódusok melyeknek egyetlen paraméterük van, és ennek a paraméternek az osztálya a
FooAnnotation annotációval van ellátva:
Azok a metódusok melyeknek az első paraméterük osztálya a FooAnnotation annotációval van ellátva,
az utolsó paraméterük osztálya a BarAnnotation annotációval van ellátva,
és közöttük 0..n tetszőleges típusú (annotációktól független) paraméterük van:
pointcut: @target
Működése megegyezik a @within pointcut-tal.
pointcut: @within
Azoknak a metódusoknak a megcélzása, melyek egy adott annotációval megjelölt osztályban vannak.
A metódusokat jelölő annotációk nem számítanak a megcélzásnál.
A @within paraméterének pontosan egy annotáció nevet kell megadni.
Hatókör szűkítéssel kell használni!
Azok a metódusok melyek a FooAnnotation annotációval megjelölt osztályban vannak:
Fordítási hibák:
pointcut: args
Azoknak a metódusoknak a megcélzása, melyek adott paramétereket fogadnak.
Az args paramétereiben a ".." csak egyszer szerepelhet! Jelentése: 0..n számú tetszőleges paraméter.
Az args paramétereiben a "*" többször szerepelhet! Jelentése: 1 darab tetszőleges paraméter.
Hatókör szűkítéssel kell használni!
Azok a metódusok melyeknek pontosan egyetlen long (vagy Long) paraméterük van:
Azok a metódusok melyeknek az első paraméterük Long (vagy long) és utána 0..n darab paraméterük van:
Azok a metódusok melyeknek pontosan 2 paraméterük van, az első paraméterük bármi,
a második paraméterük Long (vagy long):
Azok a metódusok melyeknek az utolsó paraméterük Long (vagy long) és előtte 0..n darab paraméterük van:
Azok a metódusok melyeknek nincs paraméterük:
Vigyázat, minden metódus (minden metódusnak 0..n paramétere van):
Azok a metódusok melyeknek az első paraméterük HttpServletRequest, a második HttpServletResponse,
és utána 0..n darab paraméterük van:
Azok a metódusok melyeknek pontosan 2 paraméterük van (mindegy milyen hiszen bármi az Object leszármazott).
Ebből látszik hogy a leszármazottakra is érvényesül az args():
Fordítási hiba, nem lehet többször használni a ".." jelet:
pointcut: execution
Azoknak a metódusoknak a megcélzása, melyek a megadott leírással egyeznek.
Egy konkrét osztály (Bean) konkrét metódusa (m1), ahol minden pontosan meg van adva:
Egy konkrét osztály (Bean) konkrét metódusa (m1), ahol más nincs megadva:
Egy konkrét osztály (Bean) összes metódusa:
Egy konkrét csomag (xesj.app.sbt.aop) összes osztályának összes metódusa:
Egy csomag (xesj.app.sbt) összes alcsomagjának, összes osztályának, összes metódusa:
pointcut: target
Működése megegyezik a "this" pointcut-tal.
pointcut: this
Azoknak a metódusoknak a megcélzása, melyek a megadott osztályban vagy leszármazottjában vannak,
illetve a megadott interface-t implementáló osztályban vagy leszármazottjában vannak.
A this() paramétere pontosan egy elemet tartalmaz, mely osztályt vagy interface-t nevez meg.
Azok a metódusok, melyek a FooInterface-t implementáló osztályban vagy leszármazottjában vannak:
Azok a metódusok, melyek a BeanParent osztályban vagy leszármazottjában vannak:
Fordítási hibát okoz:
pointcut: within
Azoknak a metódusoknak a megcélzása, melyek a megadott osztályban vannak (de nem az osztály leszármazottjában),
illetve ha csomag van megnevezve, akkor abban a csomagban lévő osztályokban van.
Csak csomag és/vagy osztály nevezhető meg, interface nem.
A Bean osztály összes metódusa:
A "xesj.app.sbt.proba" csomagban lévő összes osztály összes metódusa:
A "xesj.app.sbt" csomagban és az összes alcsomagban lévő összes osztály összes metódusa.
Ez nagyon jól alkalmazható a hatókör szűkítésére, a logikai "&&" kapcsolóval és más pointcut elemekkel: