W dzisiejszym wpisie wystąpi trochę anglojęzycznych zwrotów - ucząc się AOP nie próbowałem niektórych pojęć tłumaczyć i zapożyczyłem je, jeśli szanowny Czytelnik ma pomysł na tłumaczenia proszę o komentarz.
Aby zrozumieć czym jest, a może ważniejsze - po co jest AOP spójrzmy na dwa problemy występujące w systemach :
Aby zwiększyć precyzję myśli wprowadźmy pojęcia :
Typy advice-ów :
Kilka ograniczeń Spring AOP :
Tutaj powiem od razu, że można tak skonfigurować spring aop, aby korzystał z modyfikacji bytecode za pomocą AspectJ, zamiast z dynamic proxy. Można to osiągnąć na dwa sposoby :
Załóżmy, że mamy adnotację, interfejs oraz jego implementację:
Mamy :
Teraz kolejno co się stało :
Czy w tym przykładzie konieczna jest adnotacja ? Oczywiście nie. Moglibyśmy na kilka innych sposobów napisać nasz pointcut. W kolejnym wpisie temat poin-cutów opiszę dokładniej.
Wyżej napisałem, że spring tworzy obiekt proxy i w ten sposób dzieje się magia aop. Sprawdźmy to :
Powiedziałem wyżej, że wystarczy trochę konfiguracji (oraz ku ścisłości : aspectjweaver i aspectjrt na classpath) aby spring nie tworzył proxy a zmodyfikował bytecode, spójrzmy :
Powiedziałem też, że jeśli bean nie implementuje interfejsu z metodą join point to spring automatycznie skorzysta z modyfikacji bytecodu, spójrzmy :
W powyższym przykładzie korzystałem z adnotacji aby skonfigurować aspekt. Jak to w springu bywa można to zrobić również w xml-u, ale tutaj osobiście myślę, że adnotacje są czytelniejsze - czytając klasę aspektu widzimy jego pointcuty.
AOP na pierwszy rzut oka może przerażać, przyznam się, że mnie przeraziło. Mamy trochę konfiguracji, jakąś klasę aspektu i nagle nasz kod zaczyna inaczej się zachowywać. Jednak po 2 latach z wykorzystaniem aspektów myślę, że to świetne narzędzie.
STS (springsource tool suite) potrafi przejrzyście pokazywać wszystkie advice-y i pointcuty w aplikacji. Da się zatem nad tym zapanować.
AOP pozwala na fajne zaimplementowanie transakcyjności (dokładnie tak w springu działa moduł odpowiedzialny za trasakcyjność :)), strategii cache, prób ponowienia wywołania metody przy jakiś tam warunkach.
Generalnie super sprawa.
Aby zrozumieć czym jest, a może ważniejsze - po co jest AOP spójrzmy na dwa problemy występujące w systemach :
- "Tangling" - mówi o tym, że mamy pomieszany kod biznesowy z kodem obsługującym : transakcyjność, logowanie, obsługę wyjątków itp. - jakiś kod techniczny.
- "Scattering" - mamy podobny kod rozsiany po całym systemie - np kod obsługi transakcyjności, otwieranie sesji hibernate itp.
Aby zwiększyć precyzję myśli wprowadźmy pojęcia :
- Join point - np wywołanie metody lub przypisanie wartości do pola. Spring AOP wspiera tylko wywołania publicznych metod na bean-ach.
- Pointcut - wyrażenie określające miejsca, do których zostaną zaaplikowane aspekty. Możemy rozumieć jako 'wyrażenie regularne', które 'wyciąga' nam odpowiednie miejsca w systemie (join point-y) aby tam umieścić kod - advice.
- Advice - kod, który wcześniej opisałem jako ten 'wyjęty', kod najczęściej nie biznesowy.
- Aspect - byt (w spring aop będzie to klasa) zawierający pointcut-y oraz advice-y.
Typy advice-ów :
- before - advice uruchomi się przed wywołaniem join point-a.
- afterReturning - advice uruchomi się po poprawnym wykonaniu join point-a.
- afterThrowing - advice uruchomi się po rzuceniu wyjątku z join point-a.
- after - advice obejmuje @AfterReturning oraz @AfterThrowing.
- around - advice uruchomi się podobnie jak dla before, ale sami wołamy join point-a. Skoro sami go wołamy, to możemy również wywołać go kilkukrotnie bądź nie wywołać w ogóle (np skorzystać z cache-a).
Kilka ograniczeń Spring AOP :
- pointcut-y odnosić się będą tylko do beanów springa (np nie zadziałają np na encjach hibernate).
- jeśli bean A posiada dwie publiczne metody 'm1' i 'm2', na każdą z nich nałożymy odpowiednio aspekt 'a1' i 'a2'. Dalej, jeśli metoda 'm1' woła w sobie 'm2' (lub na odwrót), to nie zostanie uruchomiony aspekt 'a2'. Dzieje się tak, dlatego że wołając metodę 'm1' na bean-ie wołamy ją tak naprawdę na obiekcie proxy. Advice się uruchomi, i następnie odpali metodę 'm1' na obiekcie A (a nie na proxy do A). Zatem aspekt 'a2' nie zostanie wywołany.
- możemy nakładać aspekty tylko na metody publiczne.
- jest wolniejszy od AspectJ (który to modyfikuje bytecode).
Tutaj powiem od razu, że można tak skonfigurować spring aop, aby korzystał z modyfikacji bytecode za pomocą AspectJ, zamiast z dynamic proxy. Można to osiągnąć na dwa sposoby :
- Jeśli nakładamy aspekt na beana, który nie posiada interfejsu spring automatycznie skorzysta z AspectJ.
- W konfiguracji taga <aop:aspectj-autoproxy> podać atrybut proxy-target-class="true".
Załóżmy, że mamy adnotację, interfejs oraz jego implementację:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { String logText() default ""; } public interface Service { String getMessage(String name); } public class ExampleService implements Service { @Log @Override public String getMessage(String name) { return name; } }Powiedzmy, że chcemy zalogować wywołanie metody getMessage. Oznaczyliśmy ją naszą adnotacją @Log. Napiszmy teraz odpowiedni pointcut :
@Aspect public class LoggerAspect { @Before(value = "@annotation(log)") public void trackLog(JoinPoint joinPoint, Log log) { String logText = extractLogText(joinPoint, log); System.out.println(logText); } . . . }Oczywiście trzeba jeszcze dodać kawałek xml-a (nasz ExampleService musi być beanem, tak samo, jak aspekt).
<bean id="service" class="pl.turo.spring.aop.service.ExampleService" /> <aop:aspectj-autoproxy /> <bean id="loggerAspect" class="pl.turo.spring.aop.utils.LoggerAspect" />Korzystam z namespace 'aop' - trzeba pamiętać aby dodać jego definicję.
Mamy :
- bena z metodą którą chcemy 'aspektować' - 'service',
- aspekt z definicją pointcut oraz z naszym kodem advice.
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/app-context.xml"); Service service = context.getBean(Service.class); service.getMessage("czy zadziała AOP ?"); }i na konsoli widzimy :
ExampleService.getMessage (czy zadziała AOP ?)Odpaliliśmy metodę na beanie 'service', aspekt 'przechwycił' to wywołanie i je zalogował. (Pełny kod aspektu udostępniam w źródłach projektu).
Teraz kolejno co się stało :
- Stworzyliśmy interfejs z metodą którą chcielibyśmy aspektować.
- Napisaliśmy implementację naszego interfejsu.
- Stworzyliśmy adnotację, aby jakoś oznaczyć metodę, że chcemy ją logować/aspektować.
- Napisaliśmy aspekt z pointcutem typu before.
- W advice rzucamy sysout z logiem.
Czy w tym przykładzie konieczna jest adnotacja ? Oczywiście nie. Moglibyśmy na kilka innych sposobów napisać nasz pointcut. W kolejnym wpisie temat poin-cutów opiszę dokładniej.
Wyżej napisałem, że spring tworzy obiekt proxy i w ten sposób dzieje się magia aop. Sprawdźmy to :
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/app-context.xml"); Service service = context.getBean(Service.class); System.out.println(service.getClass()); service.getMessage("czy zadziała AOP ?"); }Po uruchomieniu tego kodu widzę na konsoli :
class $Proxy5 Service.getMessage (czy zadziała AOP ?)Mamy proxy.
Powiedziałem wyżej, że wystarczy trochę konfiguracji (oraz ku ścisłości : aspectjweaver i aspectjrt na classpath) aby spring nie tworzył proxy a zmodyfikował bytecode, spójrzmy :
<aop:aspectj-autoproxy proxy-target-class="true" />ustawiliśmy proxy-target-class="true", uruchamiamy jeszcze raz naszego maina :
class pl.turo.spring.aop.service.ExampleService$$EnhancerByCGLIB$$8993e64c ExampleService.getMessage (czy zadziała AOP ?)Widzimy, CGLIB-a - działa.
Powiedziałem też, że jeśli bean nie implementuje interfejsu z metodą join point to spring automatycznie skorzysta z modyfikacji bytecodu, spójrzmy :
public class NoInterfaceService { @Log public String getMessage(String name) { return name; } }
<bean id="noInterfaceservice" class="pl.turo.spring.aop.service.NoInterfaceService" /> <aop:aspectj-autoproxy proxy-target-class="false" /> <bean id="loggerAspect" class="pl.turo.spring.aop.utils.LoggerAspect" />i main :
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/app-context.xml"); NoInterfaceService noInterfaceService = context.getBean(NoInterfaceService.class); System.out.println(noInterfaceService.getClass()); noInterfaceService.getMessage("bez interfejsu mamy zawsze AspectJ"); }i konsola :
class pl.turo.spring.aop.service.NoInterfaceService$$EnhancerByCGLIB$$1d51d172 NoInterfaceService.getMessage (bez interfejsu mamy zawsze AspectJ)Widzimy, że działa.
W powyższym przykładzie korzystałem z adnotacji aby skonfigurować aspekt. Jak to w springu bywa można to zrobić również w xml-u, ale tutaj osobiście myślę, że adnotacje są czytelniejsze - czytając klasę aspektu widzimy jego pointcuty.
AOP na pierwszy rzut oka może przerażać, przyznam się, że mnie przeraziło. Mamy trochę konfiguracji, jakąś klasę aspektu i nagle nasz kod zaczyna inaczej się zachowywać. Jednak po 2 latach z wykorzystaniem aspektów myślę, że to świetne narzędzie.
STS (springsource tool suite) potrafi przejrzyście pokazywać wszystkie advice-y i pointcuty w aplikacji. Da się zatem nad tym zapanować.
AOP pozwala na fajne zaimplementowanie transakcyjności (dokładnie tak w springu działa moduł odpowiedzialny za trasakcyjność :)), strategii cache, prób ponowienia wywołania metody przy jakiś tam warunkach.
Generalnie super sprawa.
Mógłbyś wyjaśnić dokładnej składnię:
OdpowiedzUsuń" String logText() default "";"
Jest ona intuicyjna, jednak spotkałem się z nią po raz pierwszy.
Pewnie :-)
OdpowiedzUsuńZauważ, że jest to kawałek kodu z deklaracji adnotacji Log. Dla tej adnotacji deklarujemy jeden atrybut o nazwie 'logText'. Atrybut jest typu String i jest opcjonalny - domyślnie przyjęta wartość to pusty napis.