poniedziałek, 8 listopada 2010

Spring aop - różne typy metod advice

W pierwszym poście na temat spring aop wspomniałem o typach advice-ów :
Typy advice-ów :
  • before - advice uruchomi się przed wykonaniem 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 uruchomi się niezależnie od tego czy metoda wykonała się bezbłędnie czy nie.
  • around - advice uruchomi się po before, sami tutaj odpowiadamy za wywołanie join point-a i zwrócenie wartości.

Rozpatrzmy taki oto przypadek:
Niech dany będzie interfejs :
public interface Service {
    String getMessage(String name);
    void getMessageAndReturnVoid(String name);
}
Plus prosta implementacja logująca wykonanie się metody:
public class ExampleService implements Service {
    private static final Logger logger = Logger.getLogger(ExampleService.class);
   
    @Override
    public String getMessage(String name) {
        logger.info("metoda getMessage");
        return name;
    }

    @Override
    public void getMessageAndReturnVoid(String name) {
        logger.info("metoda getMessageAndReturnVoid");
    }
}
Oraz aspekt:
@Aspect
public class LoggerAspect {
    private static final Logger logger = Logger.getLogger(LoggerAspect.class);
   
    @Before("stringParameter()")
    public void before(JoinPoint joinPoint) {
        logger.info("Before advice");
    }
   
    @AfterReturning(pointcut="stringParameter()", returning="value")
    public void afterReturning(JoinPoint joinPoint, String value) {
        logger.info("AfterReturning advice zwraca wartość : " + value);
    }

    @AfterThrowing(pointcut="stringParameter()", throwing="ex")
    public void afterThrowing(JoinPoint joinPoint, RuntimeException ex) {
        logger.info("AfterThrowing advice łapie wyjątek : " + ex);
    }

    @After("stringParameter()")
    public void after(JoinPoint joinPoint) {
        logger.info("After advice ");
    }

    @Around("stringParameter()")
    public Object around (ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("Around advice");
        return joinPoint.proceed();
    }
   
    @Pointcut("execution(* pl..Service+.get*(String))")
    private void stringParameter() {}
}
Mamy pointcut łąpiący się na obie metody w interfejsie oraz advice-y wszystkich wymienionych typów. Odpalmy springa, obie metody i spójrzmy na konsolę :
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("/META-INF/spring/app-context.xml");
    System.out.println();
    Service service = (Service) context.getBean("service");
    service.getMessage("badamy typy advice-ów");
    System.out.println();
    service.getMessageAndReturnVoid("badamy typy advice-ów");
}
Moja konsola daje wynik:
INFO [main] (LoggerAspect.java:22) - Before advice
INFO [main] (LoggerAspect.java:42) - Around advice
INFO [main] (ExampleService.java:11) - metoda getMessage
INFO [main] (LoggerAspect.java:37) - After advice
INFO [main] (LoggerAspect.java:27) - AfterReturning advice zwraca wartość : badamy typy advice-ów

INFO [main] (LoggerAspect.java:22) - Before advice
INFO [main] (LoggerAspect.java:42) - Around advice
INFO [main] (ExampleService.java:17) - metoda getMessageAndReturnVoid
INFO [main] (LoggerAspect.java:37) - After advice
INFO [main] (LoggerAspect.java:27) - AfterReturning advice zwraca wartość : null 
 
Możemy odczytać kolejność uruchamiania się odpowiednich advice-ów:
  1. before.
  2. around (tutaj sami odpalamy metodę na obiekcie target poprzez joinPoint.proceed()).
  3. after.
  4. afterReturning.

A co jeśli z metody poleci wyjątek na który mamy advice? Zmodyfikujmy metodę:
@Override
public String getMessage(String name) {
    logger.info("metoda getMessage");
    throw new NullPointerException();
}
Bez większego zaskoczenia mam na konsoli:
INFO [main] (LoggerAspect.java:22) - Before advice
INFO [main] (LoggerAspect.java:42) - Around advice
INFO [main] (ExampleService.java:11) - metoda getMessage
INFO [main] (LoggerAspect.java:37) - After advice
 INFO [main] (LoggerAspect.java:32) - AfterThrowing advice łapie wyjątek : java.lang.NullPointerException
Exception in thread "main" java.lang.NullPointerException
...
Czyli kolejność jest identyczna jak w pierwszym przypadku, z tym że zamiast afterReturning mamy afterThrowing. Zauważmy również, że deklaracja wyjątku na który się łąpiemy na RuntimeException oznacza również wszystkie jego podtypy (w tym przypadku NullPointerException).

Myślę, że warto rozważyć
  • around
    • jeśli chcemy implementować cache (nie musimy przecież uruchamiać joinPoint.proceed() a wartość zwrócić skądinąd),
    • jeśli chcemy implementować coś w rodzaju kilkukrotnych prób uruchomienia metody. Powiedzmy, że mamy wymaganie biznesowe, które mówi, że należy co najmniej 5-cio krotnie uruchomić metodę (jeśli jej wywołanie kończy się jakimś tam błędem) zanim poleci raport do admina. Advice typu around, joinPoint.proceed() w bloku try - catch i mamy sprawę załatwioną. Co więcej mamy ładne odseparowanie logiki metody biznesowej od logiki raportowania błędów.
  • afterThrowing
    • jeśli chcemy napisać generyczną obsługę wyjątków w aplikacji, logowanie wystąpienia wyjątku, można pisać obsługę wyjątku per typ itp. Pisałem kiedyś taki advice i w tamtym projekcie sprawował się świetnie.
    • jeśli chcemy tłumaczyć wyjątki. 
      • w aplikacjach wielowarstwowych dobrze jest jeśli warstwa widzi tylko warstwę bezpośrednio pod nią. Jeśli mamy 3 warstwy : 1 -> 2 -> 3, to jeśli z dolnej warstwy (3) poleci wyjątek byłoby super gdyby warstwa 2 go przechwyciła i przetłumaczyła na wyjątek który rozumiałaby warstwa 1. Jeśli tego nie zrobimy (aspektem bądź inaczej) to 1 zaczyna widzieć bezpośrednio 3. Można się spierać czy to dobrze czy nie; wiem że spring korzysta z tłumaczenia wyjątków w kontekście warstw (np w module jdbc, o którym będę pisał).
      • inny przypadek, który mi przychodzi do głowy to tłumaczenie wyjątków na styku serwer -> gui. Wydaje się fajnym pomysłem mieć jakiś mechanizm tłumaczący wyjątek z bebechów serwera na coś co użytkownik może zobaczyć w alercie.

Chciałbym jeszcze Czytelnikowi zwrócić uwagę, że do tej pory wszystkie moje advice-y były metodami publicznymi zwracającymi void. Nie ma znaczenia ich widoczność oraz co zwracają, z wyjątkiem advice typu around. Otóż w tym przypadku zwracany obiekt to jest obiekt zwracany przez metodę proceed() - czyli w konsekwencji na obiekcie target. Trzeba o tym pamiętać. Rzućmy okiem jeszcze raz na tą metodę:
@Around("stringParameter()")
public Object around (ProceedingJoinPoint joinPoint) throws Throwable {
    logger.info("Around advice");
    return joinPoint.proceed();
}

Tyle w temacie typów advice-ów w spring aop. Stąd można pobrać źródła projektu.

Brak komentarzy:

Prześlij komentarz