AspectJ 处理多个匹配建议
AspectJ Handling of Multiple Matching Advices
我在 Java 中使用 AspectJ 来记录对某些方法的调用。我在网上看过,但找不到答案:
当两个 @Around
建议匹配一个方法时会发生什么?
具体来说,我使用了两个@Around 建议,如下所示:
@Around("condition1() && condition2() && condition3()")
public Object around(ProceedingJoinPoint point) {
return around(point, null);
}
@Around("condition1() && condition2() && condition3() && args(request)")
public Object around(ProceedingJoinPoint point, Object request) {
...
result = (Result) point.proceed();
...
}
如果这两个建议都匹配,这会导致 point.proceed()
被调用两次(实际方法被调用两次)吗?
为了完整起见,将此留在这里。
我最终实现了一个虚拟项目来测试它。答案是该方法只会执行一次。我已经实施了以下内容:
Aspects.java:
package base;
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 Aspects {
@Pointcut("@annotation(java.lang.Deprecated)")
public void hasErrorResponseMethod() {
}
@Around("hasErrorResponseMethod()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("In CHILD advice.");
return around(point, null);
}
@Around("hasErrorResponseMethod() && args(request)")
public Object around(ProceedingJoinPoint point, Object request) throws Throwable {
System.out.println("In PARENT advice with request " + request);
return point.proceed();
}
}
Methods.java:
package base;
public class Methods {
private static int COUNT = 1;
@Deprecated
public int method1(String param) {
System.out.println(">>> In method1! Param: " + param);
return COUNT++;
}
}
applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<context:component-scan base-package="base" annotation-config="true" />
<aop:aspectj-autoproxy />
<bean id="logAspect" class="base.Aspects"/>
<bean id="methods" class="base.Methods"/>
<bean id="main" class="base.Main"/>
</beans>
Main.java:
package base;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Methods methods = (Methods) context.getBean("methods");
System.out.println("<<< Result: " + Methods.method1("REQUEST_VALUE"));
}
}
输出如下:
In PARENT advice with request REQUEST_VALUE
In CHILD advice.
In PARENT advice with request null
>>> In method1! Param: REQUEST_VALUE
<<< Result: 1
如您所见,该方法只被调用一次,由看起来更具体的建议调用。知道 AspectJ 如何决定两者中的哪一个调用它仍然很棒。
您的方法存在很大问题,因为您手动从另一个建议中调用一个建议。这不是应用 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 框架更聪明,可能会造成损害(见我的第一个例子)。
还有一件事:如果多个方面有匹配的切入点,您可以通过 AspectJ 中的 @DeclarePrecedence
影响它们的执行顺序,但在单个方面内您对执行顺序没有影响,或者至少您应该不要依赖它。在 Spring AOP 中,您可以使用 @Order
注释来确定方面优先级,但是对于来自同一方面的多个建议,顺序也是未定义的,另请参见 Spring manual.
更新2016-02-28,18:30 CET,经过评论讨论:
好的,我们稍微扩展驱动程序 class 以便我们可以测试更多:
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;
}
}
控制台日志应该和上一个完全一样。
更新二:
好吧,我刚刚意识到空的 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;
}
}
这是可以接受的,也是优雅的,因为它不会为每个连接点生成两次字节码。这两个切入点是互斥的,所以这是一件好事。我推荐这个解决方案。
我在 Java 中使用 AspectJ 来记录对某些方法的调用。我在网上看过,但找不到答案:
当两个 @Around
建议匹配一个方法时会发生什么?
具体来说,我使用了两个@Around 建议,如下所示:
@Around("condition1() && condition2() && condition3()")
public Object around(ProceedingJoinPoint point) {
return around(point, null);
}
@Around("condition1() && condition2() && condition3() && args(request)")
public Object around(ProceedingJoinPoint point, Object request) {
...
result = (Result) point.proceed();
...
}
如果这两个建议都匹配,这会导致 point.proceed()
被调用两次(实际方法被调用两次)吗?
我最终实现了一个虚拟项目来测试它。答案是该方法只会执行一次。我已经实施了以下内容:
Aspects.java:
package base;
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 Aspects {
@Pointcut("@annotation(java.lang.Deprecated)")
public void hasErrorResponseMethod() {
}
@Around("hasErrorResponseMethod()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("In CHILD advice.");
return around(point, null);
}
@Around("hasErrorResponseMethod() && args(request)")
public Object around(ProceedingJoinPoint point, Object request) throws Throwable {
System.out.println("In PARENT advice with request " + request);
return point.proceed();
}
}
Methods.java:
package base;
public class Methods {
private static int COUNT = 1;
@Deprecated
public int method1(String param) {
System.out.println(">>> In method1! Param: " + param);
return COUNT++;
}
}
applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<context:component-scan base-package="base" annotation-config="true" />
<aop:aspectj-autoproxy />
<bean id="logAspect" class="base.Aspects"/>
<bean id="methods" class="base.Methods"/>
<bean id="main" class="base.Main"/>
</beans>
Main.java:
package base;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Methods methods = (Methods) context.getBean("methods");
System.out.println("<<< Result: " + Methods.method1("REQUEST_VALUE"));
}
}
输出如下:
In PARENT advice with request REQUEST_VALUE
In CHILD advice.
In PARENT advice with request null
>>> In method1! Param: REQUEST_VALUE
<<< Result: 1
如您所见,该方法只被调用一次,由看起来更具体的建议调用。知道 AspectJ 如何决定两者中的哪一个调用它仍然很棒。
您的方法存在很大问题,因为您手动从另一个建议中调用一个建议。这不是应用 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 框架更聪明,可能会造成损害(见我的第一个例子)。
还有一件事:如果多个方面有匹配的切入点,您可以通过 AspectJ 中的 @DeclarePrecedence
影响它们的执行顺序,但在单个方面内您对执行顺序没有影响,或者至少您应该不要依赖它。在 Spring AOP 中,您可以使用 @Order
注释来确定方面优先级,但是对于来自同一方面的多个建议,顺序也是未定义的,另请参见 Spring manual.
更新2016-02-28,18:30 CET,经过评论讨论:
好的,我们稍微扩展驱动程序 class 以便我们可以测试更多:
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;
}
}
控制台日志应该和上一个完全一样。
更新二:
好吧,我刚刚意识到空的 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;
}
}
这是可以接受的,也是优雅的,因为它不会为每个连接点生成两次字节码。这两个切入点是互斥的,所以这是一件好事。我推荐这个解决方案。