Post ten nawiązuje do poprzedniego posta o spring aop.
Przypomnijmy, że spring aop pozawala na aspektowanie wyłącznie metod publicznych na beanach.
Pointcut to 'wyrażenie regularne' pozwalające springowi znaleźć interesujące nas metody. Spring zaczerpnął sposób ich definiowania z AspectJ.
Ogólny szablon wygląda tak :
Nawiązując do poprzedniego posta, napiszmy pointcut, który nałożyłby aspekt na metodę getMessage :
Pobawmy się nieco tym poincut-em, napiszmy tak :
Modyfikujmy dalej :
Modyfikujmy dalej :
Bardzo dobry opis posiada dokumentacja springa - polecam.
Do zapamiętania :
W definicjach pointcut-ów możemy używać operatorów logicznych '&&', '||' oraz '!' aby móc je łączyć.
Powiedzmy że chcemy napisać pointcut wyciągający nam wywołania metod getMessage bezparametrowej bądź przyjmującej String, możemy to zrobić np tak :
Stąd można pobrać źródła projektu, na którym testowałem wszystkie wymienione pointcut-y.
Przypomnijmy, że spring aop pozawala na aspektowanie wyłącznie metod publicznych na beanach.
Pointcut to 'wyrażenie regularne' pozwalające springowi znaleźć interesujące nas metody. Spring zaczerpnął sposób ich definiowania z AspectJ.
Ogólny szablon wygląda tak :
execution(typ_zwracany pattern_na_ścieżkę_pakietowa_do_klasy? pattern_na_nazwę_metody(pattern_na_parametry) pattern_na_deklarowane_wyjątki?)gdzie oczywiście opcjonalne wartości są zaznaczone znakiem '?'.
Nawiązując do poprzedniego posta, napiszmy pointcut, który nałożyłby aspekt na metodę getMessage :
execution(String pl.turo.spring.aop.service.Service.getMessage(String))co się tłumaczy : szukamy metody na beanie implementującym pl.turo.spring.aop.service.Service o nazwie 'getMessage' przyjmującej dokładnie jeden parametr typu String i zwracającej wartość typu String.
Pobawmy się nieco tym poincut-em, napiszmy tak :
execution(* pl.turo.spring.aop.service.Service.getMessage(String))Zamieniłem zwracany typ ze String na *, co oznacza : dowolny zwracany typ (w tym mieści się również void).
Modyfikujmy dalej :
execution(* pl.turo.spring.aop.service.Service.getMessage(*))Zmianiełem deklarowany typ parametru na dokładnie jeden parametr dowolnego typu.
Modyfikujmy dalej :
execution(* pl.turo..Service.getMessage(*))Zmianiełem ścieżkę pakietową usuwając 'spring.aop.service' i zastępując to miejsce dwoma kropeczkami '..', co oznacza : szukaj w każdym z podpakietów pl.turo
Bardzo dobry opis posiada dokumentacja springa - polecam.
Do zapamiętania :
- Jeśli zdefiniujemy pointcut :
execution(* pl.turo..ExampleService+.getMessage(*))
i mamy beana dziedziczącego z ExampleService :
public class InheritanceServiceTest extends ExampleService { public void getMessage(int a) {} }
i na nim wołamy metodę to advice zostanie uruchomiony. Zauważmy, że metoda pasuje do naszego pointcut-a, ale nie jest odziedziczona z ExampleService (nie nadpisujemy jej). - Pamiętajmy, że jeśli spring tworzy proxy to nie można go zrzutować na typ beana a jedynie na interfejs, czyli przy powyższej konfiguracji nie uda nam się :
InheritanceServiceTest service = (InheritanceServiceTest) context.getBean("inheritanceServiceTest");
Dostaniemy :
java.lang.ClassCastException: $Proxy5 cannot be cast to pl.turo.spring.aop.service.InheritanceServiceTest.
Co więcej, w fabryce springa nie będzie bena o typie InheritanceServiceTest, czyli kod nie zadziała, mimo iż rzutujemy na interfejs :
Service service = context.getBean(InheritanceServiceTest.class);
Otrzymamy wyjątek :
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [pl.turo.spring.aop.service.InheritanceServiceTest] is defined: expected single bean but found 0:
Wniosek z tego jest taki : albo korzystamy z interfejsów i na nich definiujemy metody, jeśli nie chcemy interfejsów to nie korzystajmy z dynamic proxy !.
Włączenie korzystania z modyfikacji bytecodu usuwa oba powyższe problemy.
- Jeśli podziedziczymy z interfejsu Service i dodamy tam metodę :
public interface InheritanceInterface extends Service { String getMessage(Integer name); } public class InheritanceServiceTest implements InheritanceInterface { @Override public String getMessage(Integer value) { return "integer param : " + value; } . . . }
następnie zmodyfikujemy pointcut :
execution(* pl.turo..Service.getMessage(Integer))
wtedy advice nie zostanie uruchomiony. Można użyć symbolu '+' aby oznaczayć w pointcut-cie że chcemy wyszukać wszystkie implementacje interfejsu Service i interfejsów dziedziczących po nim :
execution(* pl.turo..Service.getMessage+(Integer))
wtedy advice zostanie uruchomiony :-).
W definicjach pointcut-ów możemy używać operatorów logicznych '&&', '||' oraz '!' aby móc je łączyć.
Powiedzmy że chcemy napisać pointcut wyciągający nam wywołania metod getMessage bezparametrowej bądź przyjmującej String, możemy to zrobić np tak :
- Do aspektu dopisujemy metody :
@Pointcut("execution(* pl.turo..Service+.getMessage())") public void getMessageWithNoArgument() {} @Pointcut("execution(* pl.turo..Service+.getMessage(String))") public void getMessageWithStringArgument() {}
W ten sposób zdefiniowaliśmy dwa nazwane poincuty. Nie muszą to być metody publiczne. - Możemy ich teraz użyć :
@Before("getMessageWithNoArgument() || getMessageWithStringArgument()")
- Takie nazwane poincuty możemy wynieć do osobnej klasy :
public class Pointcuts { @Pointcut("execution(* pl.turo..Service+.getMessage())") public void getMessageWithNoArgument() {} @Pointcut("execution(* pl.turo..Service+.getMessage(String))") public void getMessageWithStringArgument() {} }
i wtedy tak z nich skorzystać :
@Before("pl.turo.spring.aop.utils.Pointcuts.getMessageWithNoArgument() || " + "pl.turo.spring.aop.utils.Pointcuts.getMessageWithStringArgument()")
Ważnym jest, że klasa Pointcuts nie jest być beanem springa, zauważmy też że nie ma adnotacji @Aspect oraz metody muszą mieć taką widoczność, aby można było w aspekcie się do nich odwołać.
Stąd można pobrać źródła projektu, na którym testowałem wszystkie wymienione pointcut-y.
Brak komentarzy:
Prześlij komentarz