是否可以使用Spring AOP(或AspectJ)创建可重入的方面?
以下是一个例子:
@Log
public int calcFibonacci(int n) {
if(n <= 1) {
return n;
} else {
return calcFibonacci(n - 1) + calcFibonacci(n - 2);
}
}
和方面:
@Aspect
public class LoggingAspect {
@Around("@annotation(log)")
public Object measure(ProceedingJoinPoint pjp, Log log) throws Throwable {
// log some relevant information from log annotation and pjp
...
return pjp.proceed();
}
}
现在我想知道有多少次调用calcFibonacci(计算循环调用)。
有没有办法实现这个目标?
答案 0 :(得分:4)
您需要以下内容:
<aop:aspectj-autoproxy expose-proxy="true"/>
以及计算值的类:
@Component
public class CalcFibonacci implements CalcFibonacciInterface {
@Log
public int calcFibonacci(int n) {
if(n <= 1) {
return n;
} else {
return ((CalcFibonacciInterface) AopContext.currentProxy()).calcFibonacci(n - 1)
+ ((CalcFibonacciInterface) AopContext.currentProxy()).calcFibonacci(n - 2);
}
}
}
相关文档部分为here。
答案 1 :(得分:3)
好的,你无法在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 {}
使用递归Fibonacci方法的应用程序:
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,版本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
左右确保整个事物是线程安全的)或者每个控制流而不是单个方面使用方面实例化,我将在这里使用它只是为了它的乐趣:
Aspect,版本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
现在,这看起来好多了。 :)))
享受!