Post ten nawiązuje do dwóch poprzednich w tematyce spring aop. Będę opierał się o ten sam projekt.
Definiując pointcut możemy uzyskać dostęp do kontekstu join point-a. Do tego kontekstu należą:
Podział ten odnosi się do podziału ze względu na typ :
Po pełną listę polecam zajrzeć tu. Ja skupię się wyłącznie na tych wyżej wymienionych.
Załóżmy, że logując wywołanie metody getMessage(String) chcemy dodatkowo zalogować z jaką wartością parametru została ta metoda wywołana.
Aspekt może wyglądać tak :
Podsumowując: dzięki użyciu 'args' otrzymaliśmy tutaj dwie rzeczy :
Wyobraźmy sobie, że same parametry wywołania join point-a nam nie wystarczają, chcemy mieć referencję do obiektu target, zmodyfikujmy nasz pointcut i advice :
A co jeśli chcielibyśmy zmodyfikować nazwane poincut-y tak, aby to one wyciągały nam interesujący nas kontekst :
A teraz: chciałbym mieć kontekst obiektu this (na tym obiekcie wywoływany jest advice - jest to obiekt proxy). Spring tworzy jeden obiekt proxy dla wszystkich wywołań, co można łatwo sprawdzić.
Modyfikujemy nazwany pointcut-y:
W metodach ExampleServie dodałem sysout aby na konsoli łatwo móc zweryfikować którą metodę zawołaliśmy.
Napisałem wyżej że można odfiltrowywać się do metod oznaczonych odpowiednią adnotacją. Załóżmy, że mamy adnotację Log i chcemy złapać się na wszystkie wywołania metod za-adnotowanych tą adnotacją:
Jeśli chcielibyśmy z tej adnotacji wyciągnąć jakieś wartości to możemy zrobić tak:
Pokażę jeszcze jak skorzystać z @args. Weźmy dwie różne markerowe adnotacje Anno i Anno2 (wybacznie finezję nazewnictwa). Tworzę dwie klasy ParamClass i ParamClass2 odpowiednio za-adnotowane. Spójrzmy na pointcut:
Możemy również chcieć ograniczyć się do szukania join point-ów tylko w określonych pakietach lub określonej klasie, robimy to tak:
Wniklimy Czytelnik zapewne zauważy, że jest to bardzo podobne do paterna z execution, przecież moglibyśmy mieć tak:
To tyle w tematyce definiowania pointcut-ów. Temat jest o wiele szerszy, niż mój wpis, ale wydaje mi się, że wiedza zebrana w tym poście wystarczy do rozpoczęcia pracy ze spring aop i porywa sporą część standardowych problemów. Stąd można pobrać źródła projektu, na którym testowałem opisywane pointcut-y.
W następnym wpisie opiszę różne typy advice-ów.
Definiując pointcut możemy uzyskać dostęp do kontekstu join point-a. Do tego kontekstu należą:
- this - oznacza rzeczywisty obiekt, na którym została wywołana metoda (proxy)
- target - oznacza obiekt zawierający join point (bean springa, którego metodę 'aspektujemy')
- argumenty wywołania metody
- adnotacje związane z metodą.
Podział ten odnosi się do podziału ze względu na typ :
- execution - ten już znamy :-), dla przypomnienia - używamy jak chcemy napisać pointcut po paternie na nazwę metody.
- within - służy do określenia pakietu w którym będą szukane join point-y. Zwykle w spring aop używany wraz z execution aby odfiltrować wyniki do danej paczki.
- this - możemy odfiltrować się do typu obiektu this(spring aop proxy).
- target - możemy odfiltrować się do typu obiektu target.
- args - odfiltrowujemy się do join point-ów z pasującymi typami argumentów (za chwilę pokażę na przykładzie)
- @args - możemy odfiltrować join point-y podając typ adnotacji jaką musi być zaadnotowany parametr metody. Możemy podać po przecinku kilka typów adnotacji - będą się odnosić do kolejnych parametrów metody.
- @annotation - obiekt target musi być posiadać adnotacje typów podanych w pointcut-ie
Po pełną listę polecam zajrzeć tu. Ja skupię się wyłącznie na tych wyżej wymienionych.
Załóżmy, że logując wywołanie metody getMessage(String) chcemy dodatkowo zalogować z jaką wartością parametru została ta metoda wywołana.
Aspekt może wyglądać tak :
@Aspect
public class LoggerAspect {
    @Before("oneParameter() && args(message)")                            // 3
    public void trackLog(JoinPoint joinPoint, String message) {           // 4
        StringBuilder logText = createJoinPointTraceName(joinPoint);
        logText.append(" (").append(message).append(")");
        System.out.println(logText);
    }
    @Pointcut("execution(* pl..Service+.get*(*))")                        // 2
    private void oneParameter() {}                                        // 1
... 
}
Kolejno, co się dzieje:- Zdefiniowałem poincut 'oneParameter' wyciągający nam z jakiegokolwiek podpakietu 'pl' interfejs Service (i każdy po nim dziedziczący).
- Na tym interfejsie szukamy metod o nazwie zaczynającej się od 'get' i dokładnie jednym parametrze.
- Nad advice (metoda trackLog) napisałem pointcut, który korzysta z pointcut-a 'oneParameter' oraz dokłada warunek args(message).
- Ten drugi warunek odnosi się do parametru metody trackLog - szuka parametru o nazwie 'message'.
- Następnie sprawdza jakiego typu jest ten argument. Widzi, że typem jest String.
- Znając już ten typ pointuct odfiltrowuje się tylko do metod mających jeden parametr typu String.
Podsumowując: dzięki użyciu 'args' otrzymaliśmy tutaj dwie rzeczy :
- odfiltrowaliśmy join point-y po typie argumentu.
- w kodzie metody trackLog mamy referencję do obiektu przekazanego jako parametr wywołania join point-a.
private String getStringArgs (JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();
    if (args.length != 1) {
        throw new RuntimeException("Niepoprawna ilość parametrów metody");
    }
    String stringParam = (String) args[0];
    return stringParam;
}
Wyobraźmy sobie, że same parametry wywołania join point-a nam nie wystarczają, chcemy mieć referencję do obiektu target, zmodyfikujmy nasz pointcut i advice :
@Before("args(message) && target(service)")
public void trackLog(JoinPoint joinPoint, String message, Service service) {
    StringBuilder logText = createJoinPointTraceName(joinPoint);
    logText.append(" (").append(message).append(")");
    System.out.println("Obiekt target : " + service.getClass());
    System.out.println(logText);
}
Podobnie jak z 'args' - target(service) wskazuje na parametr w argumentach metody trackLog, spring sprawdza typ tego parametru - na tej podstawie nastąpi odfiltrowanie join point-ów. Obiekt ten to bean springa, który 'aspektujemy' (na nim wołaliśmy metodę join ponit). Innymi słowy w tym pointcut-cie wyciągamy join point-y tylko z bean-ów implementujących interfejs Service, metody publiczne z jednym parametrem typu String.A co jeśli chcielibyśmy zmodyfikować nazwane poincut-y tak, aby to one wyciągały nam interesujący nas kontekst :
@Before("oneParameter(message, service)")
public void trackLog(JoinPoint joinPoint, String message, Service service) {
    StringBuilder logText = createJoinPointTraceName(joinPoint);
    logText.append(" (").append(message).append(")");
    System.out.println("Obiekt target : " + service.getClass());
    System.out.println(logText);
}
@Pointcut("execution(* pl..*.get*(*))  && args(message) && target(service)")
private void oneParameter(String message, Service service) {}
Działa identycznie jak poprzedni przykład.A teraz: chciałbym mieć kontekst obiektu this (na tym obiekcie wywoływany jest advice - jest to obiekt proxy). Spring tworzy jeden obiekt proxy dla wszystkich wywołań, co można łatwo sprawdzić.
Modyfikujemy nazwany pointcut-y:
@Pointcut("execution(* pl..Service+.get*(*))  && args(message) && target(service) && this(proxyObject)")
private void oneParameter(String message, Service service, Service proxyObject) {}
@Before("oneParameter(message, service, proxyObject)")
public void trackLog(JoinPoint joinPoint, String message, Service service, Service proxyObject) {
    StringBuilder logText = createJoinPointTraceName(joinPoint);
    System.out.println(proxyObject);
    logText.append(" (").append(message).append(")");
    System.out.println(logText);
}
Wywołajmy trzykrotnie metodę getMessage(String) na naszym beanie :public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/app-context.xml");
    Service service = (Service) context.getBean("service");
    service.getMessage("raz");
    service.getMessage("dwa");
    service.getMessage("trzy");
}
Na konsoli widzę :pl.turo.spring.aop.service.ExampleService@9a9b65 Service.getMessage (raz) ^ string pl.turo.spring.aop.service.ExampleService@9a9b65 Service.getMessage (dwa) ^ string pl.turo.spring.aop.service.ExampleService@9a9b65 Service.getMessage (trzy) ^ stringWidać, że za każdym razem jak wywoływana była metoda getMessage(String) uruchomiony został kod advice i obiekt this to ten sam obiekt w JVM.
W metodach ExampleServie dodałem sysout aby na konsoli łatwo móc zweryfikować którą metodę zawołaliśmy.
@Override
public String getMessage(String name) {
    System.out.println("^ string\n");
    return name;
}
@Override
public String getMessage(Double a) {
    System.out.println("^ double\n");
    return null;
}
Napisałem wyżej że można odfiltrowywać się do metod oznaczonych odpowiednią adnotacją. Załóżmy, że mamy adnotację Log i chcemy złapać się na wszystkie wywołania metod za-adnotowanych tą adnotacją:
@Pointcut("execution(@pl.turo.spring.aop.utils.Log * *(..))")
private void logAnnotation() {}
@Before("logAnnotation()")
public void trackLog(JoinPoint joinPoint) {
    StringBuilder logText = createJoinPointTraceName(joinPoint);
    System.out.println(logText);
}
Utworzyliśmy nazwany pointcut 'logAnnotation' który wyciąga metodę zaadnotowaną adnotacją pl.turo.spring.aop.utils.Log z dowonlego pakietu, o dowolnej nazwie, dowolnej liczbie i typach parametrów, dowolnym zwracanym typie.Jeśli chcielibyśmy z tej adnotacji wyciągnąć jakieś wartości to możemy zrobić tak:
@Pointcut("@annotation(log)")
private void logAnnotationValue(Log log) {}
@Before("logAnnotationValue(log)")
public void trackLog(JoinPoint joinPoint, Log log) {
    System.out.println(log.logText());
}
Mając referencję do adnotacji możemy z niej wyciagnąć całą konfigurację metody. Myślę, że może to być użyteczne np. w obsłudze security (@Secured(allowedRole={"admin"}) czy transakcyjności.Pokażę jeszcze jak skorzystać z @args. Weźmy dwie różne markerowe adnotacje Anno i Anno2 (wybacznie finezję nazewnictwa). Tworzę dwie klasy ParamClass i ParamClass2 odpowiednio za-adnotowane. Spójrzmy na pointcut:
@Before("@args(pl.turo.spring.aop.agrsanno.Anno, pl.turo.spring.aop.agrsanno.Anno2)")
public void trackLog(JoinPoint joinPoint) {
...
}
Odfiltruje wywołania wszystkich metod publicznych, na wszystkich beanach, które mają dokładnie dwa parametry oraz- klasa pierwszego z nich jest zaadnotowana adnotacją Anno
- klasa drugiego z nich jest zaadnotowana adnotacją Anno2.
void getMessage(ParamClass paramClass, ParamClass2 paramClass2); void getMessage(ParamClass paramClass, int i, ParamClass2 paramClass2); void getMessage(ParamClass paramClass, ParamClass2 paramClass2, int i);Tylko pierwsza deklaracja pasuje do naszego pointcut-a.
Możemy również chcieć ograniczyć się do szukania join point-ów tylko w określonych pakietach lub określonej klasie, robimy to tak:
@Pointcut("within(pl.turo.spring.aop.service.ExampleService)")
private void withinPointcut(){}
@Before("logAnnotationValue(log) && withinPointcut()")
public void trackLog(JoinPoint joinPoint, Log log) {
 System.out.println(log.logText());
}
W ten sposób ograniczyliśmy się do klasy ExampleService i tylko w niej szukamy publicznych metod oznaczonych adnotacją Log.Wniklimy Czytelnik zapewne zauważy, że jest to bardzo podobne do paterna z execution, przecież moglibyśmy mieć tak:
@Pointcut("execution(* pl.turo.spring.aop.service.ExampleService.*(..))")
I osiągamy to samo. Ok, ale zauważmy że within jest dedykowane do ograniczania się do jakiegoś pakietu/klasy. Korzystając z nazawnych pointcut-ów możemy ograniczać się do poszczególnych warstw aplikacji w przejrzysty sposób.@Pointcut("within(com.companyname.dao..*)")
public void daoLayer(){}
@Pointcut("within(com.companyname.service..*)")
public void serviceLayer(){}
@Pointcut("within(com.companyname.gui..*)")
public void guiLayer(){}
I teraz możemy pisać tak tego użyć :@Before(logAnnotatioValue(log) && serviceLayer())Jak na moje oko czytelne : chcemy publiczne metody z warstwy service z adnotacją Log.
To tyle w tematyce definiowania pointcut-ów. Temat jest o wiele szerszy, niż mój wpis, ale wydaje mi się, że wiedza zebrana w tym poście wystarczy do rozpoczęcia pracy ze spring aop i porywa sporą część standardowych problemów. Stąd można pobrać źródła projektu, na którym testowałem opisywane pointcut-y.
W następnym wpisie opiszę różne typy advice-ów.
 
Brak komentarzy:
Prześlij komentarz