好的,您无法在 Spring AOP 中优雅地解决这个问题 - 请参阅我对Andrei Stefan的回答的第一句话。如果在 AOP 中应用程序代码需要知道某个切面的存在,甚至调用切面相关的代码,这就是糟糕的设计和反 AOP。因此,我在这里为您提供了一个 AspectJ 解决方案。
首先,在 AspectJ 中不仅仅是execution()
切入点,例如call()
. 因此,仅计算由 注释的连接点将@Log
产生两倍于实际递归调用次数的结果calcFibonacci(int)
。因此,切入点不应该只是
@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
现在,如果我们两次调用 Fibonacci 方法会怎样?
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
哦哦!!!
我们需要在调用之间重置计数器(并确保整个事情是线程安全的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
有一个参数,上面写着:“每次执行 Fibonacci 方法创建一个实例,但不包括递归实例。”
输出,版本 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
现在,看起来好多了。:)))
享受!