niedziela, 7 listopada 2010

Spring AOP - definiowanie poincut-ów cz. 1

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 :
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 :
  1. 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).
  2. 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.
  3. 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 :-).
Nazwane pointcut-y.
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 :
  1. 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.
  2. Możemy ich teraz użyć :
    @Before("getMessageWithNoArgument() || getMessageWithStringArgument()")
    
  3. 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ć.
W następnym poście napiszę conieco o bardziej zaawansowanych i chyba bardziej przydatnych możliwościach definiowania pointcut-ów.
Stąd można pobrać źródła projektu, na którym testowałem wszystkie wymienione pointcut-y.

Brak komentarzy:

Prześlij komentarz