2

是否可以使用 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 被调用了多少次(计算在经常调用中)。

有没有办法做到这一点?

4

2 回答 2

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);
        }
    }
}

相关文档部分在这里

于 2014-06-20T11:17:27.370 回答
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 {}

递归斐波那契方法的应用:

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

现在,看起来好多了。:)))

享受!

于 2014-06-23T08:03:07.190 回答