您的方法存在很大问题,因为您手动从另一个建议中调用一个建议。这不是应用 AOP 的方式。请让 AspectJ 根据它们各自的切入点来决定执行哪些建议。您将一个建议委托给另一个建议的方式,您甚至可以调用一个本身不匹配的建议。没有 Spring 的普通 AspectJ 中的示例(但在 Spring AOP 中的工作方式相同):
Java 驱动程序应用程序:
package de.scrum_master.app;
public class Application {
private static void doSomething() {
System.out.println("Doing something");
}
public static void main(String[] args) {
doSomething();
}
}
方面:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyBogusAspect {
@Around("execution(* doSomething(..))")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println("matching advice called on joinpoint " + thisJoinPoint);
return nonMatchingAdvice(thisJoinPoint);
}
@Around("execution(* doSomethingElse(..))")
public Object nonMatchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println("non-matching advice called on joinpoint " + thisJoinPoint);
return thisJoinPoint.proceed();
}
}
控制台日志:
matching advice called on joinpoint execution(void de.scrum_master.app.Application.doSomething())
non-matching advice called on joinpoint execution(void de.scrum_master.app.Application.doSomething())
Doing something
你能看出你的方法有多不健康吗?一个不匹配的通知被匹配的通知调用。这会产生一些非常出乎意料的行为 IMO。请不要这样做!!!
现在,关于您关于多个匹配建议的原始问题,您应该这样做:
修改方面:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyBetterAspect {
@Around("execution(* doSomething(..))")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println(">>> matching advice on " + thisJoinPoint);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
@Around("execution(* doSomething(..))")
public Object anotherMatchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println(">>> another matching advice on " + thisJoinPoint);
Object result = thisJoinPoint.proceed();
System.out.println("<<< another matching advice on " + thisJoinPoint);
return result;
}
}
新的控制台日志:
>>> matching advice on execution(void de.scrum_master.app.Application.doSomething())
>>> another matching advice on execution(void de.scrum_master.app.Application.doSomething())
Doing something
<<< another matching advice on execution(void de.scrum_master.app.Application.doSomething())
<<< matching advice on execution(void de.scrum_master.app.Application.doSomething())
如您所见,AspectJ 或 Spring AOP 将多个匹配建议(如连接点周围的洋葱皮)包装起来,只有最内层proceed()
调用实际连接点,而外层调用内部连接点,确保每个连接点只执行一次。您无需尝试比 AOP 框架更聪明,否则可能会造成损坏(请参阅我的第一个示例)。
还有一件事:如果多个切面有匹配的切入点,你可以通过@DeclarePrecedence
AspectJ 来影响它们的执行顺序,但是在一个切面内你对执行顺序没有影响,或者至少你不应该依赖它。在 Spring AOP 中,您可以使用@Order
注解来确定方面的优先级,但是对于来自同一方面的多个通知,顺序也是未定义的,另请参阅Spring 手册。
2016 年 2 月 28 日更新,欧洲中部时间 18:30,在评论中进行了一些讨论后:
好的,我们稍微扩展一下驱动程序类,以便我们可以进行更多测试:
package de.scrum_master.app;
public class Application {
private static void doSomething() {
System.out.println("Doing something");
}
private static String doSomethingElse(String text) {
System.out.println("Doing something else");
return text;
}
private static int doAnotherThing(int i, int j, int k) {
System.out.println("Doing another thing");
return (i + j) * k;
}
public static void main(String[] args) {
doSomething();
doSomethingElse("foo");
doAnotherThing(11, 22, 33);
}
}
现在,在 AspectJ 中绑定第一个参数就像绑定args(request, ..)
一个或多个参数一样简单。唯一的例外是零参数,在这种情况下切入点不会触发。所以要么我最终得到类似于你所做的事情:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BoundFirstParameterAspect {
@Pointcut("execution(* do*(..))")
public static void myPointcut() {}
@Around("myPointcut()")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
return anotherMatchingAdvice(thisJoinPoint, null);
}
@Around("myPointcut() && args(request, ..)")
public Object anotherMatchingAdvice(ProceedingJoinPoint thisJoinPoint, Object request) {
System.out.println(">>> another matching advice on " + thisJoinPoint);
Object result = thisJoinPoint.proceed();
System.out.println("<<< another matching advice on " + thisJoinPoint);
return result;
}
}
这会使相同的建议触发两次,从而导致开销,即使原始方法只调用一次,但您可以在日志中看到开销:
>>> another matching advice on execution(void de.scrum_master.app.Application.doSomething())
Doing something
<<< another matching advice on execution(void de.scrum_master.app.Application.doSomething())
>>> another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
>>> another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
Doing something else
<<< another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
<<< another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
>>> another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
>>> another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
Doing another thing
<<< another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
<<< another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
您可以很容易地识别出每个连接点是如何触发双重建议的。
或者,您可以在运行时绑定参数,这不是很优雅并且会产生一点运行时损失,但效果很好:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BoundFirstParameterAspect {
@Pointcut("execution(* do*(..))")
public static void myPointcut() {}
@Around("myPointcut()")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println(">>> matching advice on " + thisJoinPoint);
Object[] args = thisJoinPoint.getArgs();
Object request = args.length > 0 ? args[0] : null;
System.out.println("First parameter = " + request);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
}
这避免了双重建议执行以及代码重复,并产生以下控制台输出:
>>> matching advice on execution(void de.scrum_master.app.Application.doSomething())
First parameter = null
Doing something
<<< matching advice on execution(void de.scrum_master.app.Application.doSomething())
>>> matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
First parameter = foo
Doing something else
<<< matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
>>> matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
First parameter = 11
Doing another thing
<<< matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
最后但并非最不重要的一点是,您可以有两个稍微不同的切入点 - 一个为空args()
,一个为空args(request, ..)
- 两者都可以将参数处理、日志记录和异常处理委托给辅助方法以避免重复,正如我在我的一个注释:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BoundFirstParameterAspect {
@Pointcut("execution(* do*(..))")
public static void myPointcut() {}
@Around("myPointcut() && args()")
public Object myAdvice(ProceedingJoinPoint thisJoinPoint) {
return myAdviceHelper(thisJoinPoint, null);
}
@Around("myPointcut() && args(request, ..)")
public Object myAdviceWithParams(ProceedingJoinPoint thisJoinPoint, Object request) {
return myAdviceHelper(thisJoinPoint, request);
}
private Object myAdviceHelper(ProceedingJoinPoint thisJoinPoint, Object request) {
System.out.println(">>> matching advice on " + thisJoinPoint);
System.out.println("First parameter = " + request);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
}
控制台日志应该和之前的完全一样。
更新 2:
好吧,我刚刚意识到空args()
技巧也适用于您的原始想法,并避免双重执行以及辅助方法:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BoundFirstParameterAspect {
@Pointcut("execution(* do*(..))")
public static void myPointcut() {}
@Around("myPointcut() && args()")
public Object myAdvice(ProceedingJoinPoint thisJoinPoint) {
return myAdviceWithParams(thisJoinPoint, null);
}
@Around("myPointcut() && args(request, ..)")
public Object myAdviceWithParams(ProceedingJoinPoint thisJoinPoint, Object request) {
System.out.println(">>> matching advice on " + thisJoinPoint);
System.out.println("First parameter = " + request);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
}
这是可以接受的,也很优雅,因为它不会为每个连接点生成两次字节码。这两个切入点是互斥的,所以这是一件好事。我推荐这个解决方案。