Spring AOP折返方面

时间:2014-06-20 10:18:31

标签: java spring aspectj spring-aop aspect

是否可以使用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(计算循环调用)。

有没有办法实现这个目标?

2 个答案:

答案 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

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

享受!