Хорошо, вы не можете решить это элегантно в Spring AOP - см. Мое первое замечание к ответу Andrei Stefan. Если в АОП код приложения должен знать о существовании аспекта или даже вызывать код, связанный с аспектом, это плохой дизайн и анти-АОП. Таким образом, здесь у меня есть решение AspectJ для вас.
Прежде всего, в AspectJ есть больше, чем просто execution()
pointcuts, например. call()
. Таким образом, простой подсчет точек соединения, аннотированных @Log
, даст результат, в два раза превышающий фактическое количество рекурсивных вызовов calcFibonacci(int)
. Из-за этого pointcut не должен быть просто
@annotation(log)
но
execution(* *(..)) && @annotation(log)
На самом деле, этого все еще недостаточно, потому что что, если несколько методов содержат аннотации @Log
? Должны ли эти звонки учитываться? Нет, только те, что до calcFibonacci(int)
! Таким образом, мы должны еще больше ограничить «счетчик вызовов Фибоначчи» чем-то вроде:
execution(* *..Application.calcFibonacci(int)) && @annotation(log)
Вот пример полностью компилируемого кода:
Аннотация:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {}
Применение с рекурсивным методом Фибоначчи:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
int fibonacciNumber = 6;
System.out.println("Fibonacci #" + fibonacciNumber + " = " + new Application().calcFibonacci(fibonacciNumber));
}
@Log
public int calcFibonacci(int n) {
return n <= 1 ? n : calcFibonacci(n - 1) + calcFibonacci(n - 2);
}
}
Аспект, версия 1:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import de.scrum_master.app.Log;
@Aspect
public class LoggingAspect {
int count = 0;
@Before("execution(* *..Application.calcFibonacci(int)) && @annotation(log)")
public void measure(JoinPoint thisJoinPoint, Log log) {
System.out.println(thisJoinPoint + " - " + ++count);
}
}
Вывод, версия 1:
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 1
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 2
(...)
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 24
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 25
Fibonacci #6 = 8
А что, если мы дважды вызовем метод Фибоначчи?
int fibonacciNumber = 6;
System.out.println("Fibonacci #" + fibonacciNumber + " = " + new Application().calcFibonacci(fibonacciNumber));
fibonacciNumber = 4;
System.out.println("Fibonacci #" + fibonacciNumber + " = " + new Application().calcFibonacci(fibonacciNumber));
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 1
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 2
(...)
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 24
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 25
Fibonacci #6 = 8
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 26
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 27
(...)
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 33
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 34
Fibonacci #4 = 3
Uh-oh!!!
Нам нужно либо сбросить счетчик между вызовами (а также убедиться, что все это потокобезопасно, используя ThreadLocal
или около того), либо использовать инстанцирование аспекта для каждого потока управления вместо одноэлементного аспекта, что я и буду использовать здесь. просто для удовольствия:
Аспект, версия 2:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import de.scrum_master.app.Log;
@Aspect("percflow(execution(* *.calcFibonacci(int)) && !cflowbelow(execution(* *.calcFibonacci(int))))")
public class LoggingAspect {
int count = 0;
@Before("execution(* *.calcFibonacci(int)) && @annotation(log)")
public void measure(JoinPoint thisJoinPoint, Log log) {
System.out.println(thisJoinPoint + " - " + ++count);
}
}
Примечание:
- Я сократил спецификацию пакета и класса до
*
, чтобы сделать исходный код более читабельным. Вы также можете использовать de.scrum_master.app.Application
или любую его аббревиатуру, чтобы избежать конфликтов с похожими именами классов/методов.
- В аннотации
@Aspect
теперь был параметр, который говорит: «Создать один экземпляр на выполнение метода Фибоначчи, но исключить рекурсивные».
Вывод, версия 2:
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 1
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 2
(..)
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 24
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 25
Fibonacci #6 = 8
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 1
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 2
(..)
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 8
execution(int de.scrum_master.app.Application.calcFibonacci(int)) - 9
Fibonacci #4 = 3
Теперь это выглядит намного лучше. :)))
Наслаждаться!
person
kriegaex
schedule
23.06.2014
calcFibonacci()
вам нужно, чтобы внутренний вызов был примерно таким:((CalcFibonaciiInterface) AopContext.currentProxy()).calcFibonacci()
. Вы не опубликовали полный класс, но я предположил, что ваш класс, содержащийcalcFibonacci
, реализует интерфейс (я назвал его CalcFibonacciInterface). - person Andrei Stefan   schedule 20.06.2014