Okay, you cannot solve this elegantly in Spring AOP - see my first remark to Andrei Stefan's answer. If in AOP the application code needs to know about an aspect's existence or even call aspect-related code, this is bad design and anti-AOP. Thus, here I have an AspectJ solution for you.
First of all, in AspectJ there is more than just execution()
pointcuts, e.g. call()
. Thus, just counting joinpoints annotated by @Log
would yield a result twice as big as the actual number of recursive calls to calcFibonacci(int)
. Because of this, the pointcut should not be just
@annotation(log)
but
execution(* *(..)) && @annotation(log)
Actually, this still is not enough because what if multiple methods contain @Log
annotations? Should those calls all be counted? No, only those to calcFibonacci(int)
! So we should restrict the "Fibonacci call counter" even more to something like:
execution(* *..Application.calcFibonacci(int)) && @annotation(log)
Here is some fully compileable sample code:
Annotation:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {}
Application with recursive Fibonacci method:
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);
}
}
Aspect, version 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);
}
}
Output, version 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
Now, what if we call the Fibonacci method twice?
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!!!
We need to either reset the counter in between calls (and also make sure the whole thing is thread-safe by using a ThreadLocal
or so) or use an aspect instantiation per control flow instead of a singleton aspect, which is what I will use here just for the fun of it:
Aspect, version 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);
}
}
Note:
- I have shortened the package and class specification to just
*
in order to make the source code more readable. You can just as well use de.scrum_master.app.Application
or any abbreviation of it in order to avoid collisions with similar class/method names.
- The
@Aspect
annotation now had a parameter which says: "Create one instance per execution of the Fibonacci method, but exclude recursive ones."
Output, version 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
Now, that looks much better. :)))
Enjoy!