我认为吉吉对mix aspect styles的想法是一个很好的迁移方法,甚至是永久性的,只要你没有任何引人注目的问题(例如性能)。
现在,说了这些,我有一个解决方法,以防万一您需要排除内部调用全面吹AspectJ出于任何原因。我不认为它是优雅的,但它的工作原理。这是一个概念证明:
两个示例应用程序类:
这些类调用自己的方法内部,还包括其他类的方法。例如。 Foo.fooOne(Bar)
在外部呼叫Bar.doSomethingBarish()
,但在内部也呼叫Foo.fooTwo(int)
。
package de.scrum_master.app;
public class Foo {
public void doSomethingFooish() {
fooTwo(22);
}
public void fooOne(Bar bar) {
bar.doSomethingBarish();
fooTwo(11);
}
public String fooTwo(int number) {
return fooThree("xxx");
}
public String fooThree(String text) {
return text + " " + text + " " + text;
}
}
驱动应用与main
方法:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
Foo foo = new Foo();
Bar bar = new Bar();
foo.fooOne(bar);
bar.barOne(foo);
}
}
样本方面包括内部呼叫:
这是写方面通常的方式。它再现了你的问题。我在这里使用call()
切入点而不是execution()
,以便访问来电者(JoinPoint.EnclosingStaticPart
)和被呼叫者(JoinPoint
)连接点并能够打印它们以便说明。在一个execution()
切入点中,两个值都是相同的。
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SampleAspect {
@Pointcut("call(public * de.scrum_master..*(..)) && !within(SampleAspect)")
public static void publicMethodCalls() {}
@Before("publicMethodCalls()")
public void myPointcut(
JoinPoint thisJoinPoint,
JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart
) {
System.out.println(thisEnclosingJoinPointStaticPart + " -> " + thisJoinPoint);
}
}
控制台输出:
在这里你可以看到很好
Application
电话都Foo
和Bar
方法,
Foo
如何调用Bar
方法,而且其自身的人内部,
Bar
在内部调用Foo
方法,但也调用它自己的方法。
execution(void de.scrum_master.app.Application.main(String[])) -> call(void de.scrum_master.app.Foo.fooOne(Bar))
execution(void de.scrum_master.app.Foo.fooOne(Bar)) -> call(void de.scrum_master.app.Bar.doSomethingBarish())
execution(void de.scrum_master.app.Bar.doSomethingBarish()) -> call(String de.scrum_master.app.Bar.barTwo(int))
execution(String de.scrum_master.app.Bar.barTwo(int)) -> call(String de.scrum_master.app.Bar.barThree(String))
execution(void de.scrum_master.app.Foo.fooOne(Bar)) -> call(String de.scrum_master.app.Foo.fooTwo(int))
execution(String de.scrum_master.app.Foo.fooTwo(int)) -> call(String de.scrum_master.app.Foo.fooThree(String))
execution(void de.scrum_master.app.Application.main(String[])) -> call(void de.scrum_master.app.Bar.barOne(Foo))
execution(void de.scrum_master.app.Bar.barOne(Foo)) -> call(void de.scrum_master.app.Foo.doSomethingFooish())
execution(void de.scrum_master.app.Foo.doSomethingFooish()) -> call(String de.scrum_master.app.Foo.fooTwo(int))
execution(String de.scrum_master.app.Foo.fooTwo(int)) -> call(String de.scrum_master.app.Foo.fooThree(String))
execution(void de.scrum_master.app.Bar.barOne(Foo)) -> call(String de.scrum_master.app.Bar.barTwo(int))
execution(String de.scrum_master.app.Bar.barTwo(int)) -> call(String de.scrum_master.app.Bar.barThree(String))
改进方面动态排除内部呼叫:
现在,我们需要比较,如果主叫方(this()
切入点在本地AspectJ的语法)是相同的被叫方(target()
切入点)。如果是这样,我们想跳过建议执行。有两种方法可以在AspectJ的主叫/被叫引用:
- 通过
this()
和/或target()
他们结合参数在poinctut。这里有一个警告:如果this()
或target()
是null
指针将不匹配,即静态方法作为调用者或被调用者被排除。在我的示例中,我希望看到Application.main(..)
的调用,所以我只会绑定切入点中的目标/被调用者,而不是调用者/此对象。
- 通过
JoinPoint.getThis()
和/或JoinPoint.getTarget()
从正在执行的建议中动态地确定它们。这很好,但需要注意的是,它可能会慢一点,即使您想立即排除静态调用者/被调用者,建议也会执行。
在这里我们选择一种混合的办法,包括静态调用者,但为了证明这两个变种除静态的被叫方:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SampleAspect {
@Pointcut("call(public * de.scrum_master..*(..)) && !within(SampleAspect)")
public static void publicMethodCalls() {}
@Before("publicMethodCalls() && target(callee)")
public void myPointcut(
Object callee,
JoinPoint thisJoinPoint,
JoinPoint.EnclosingStaticPart thisEnclosingJoinPointStaticPart
) {
Object caller = thisJoinPoint.getThis();
if (caller == callee)
return;
System.out.println(thisEnclosingJoinPointStaticPart + " -> " + thisJoinPoint);
System.out.println(" caller = " + caller);
System.out.println(" callee = " + callee);
}
}
控制台输出:
正如你所看到的,我们已将输出减少到仅限我们感兴趣的呼叫。如果您还想排除静态呼叫者Application.main(..)
,则只需直接绑定this()
即可。
execution(void de.scrum_master.app.Application.main(String[])) -> call(void de.scrum_master.app.Foo.fooOne(Bar))
caller = null
callee = [email protected]
execution(void de.scrum_master.app.Foo.fooOne(Bar)) -> call(void de.scrum_master.app.Bar.doSomethingBarish())
caller = [email protected]
callee = [email protected]
execution(void de.scrum_master.app.Application.main(String[])) -> call(void de.scrum_master.app.Bar.barOne(Foo))
caller = null
callee = [email protected]
execution(void de.scrum_master.app.Bar.barOne(Foo)) -> call(void de.scrum_master.app.Foo.doSomethingFooish())
caller = [email protected]
callee = [email protected]
如果在特殊情况下,你知道通过建议所针对的确切类的名字,你也许能以排除不难看if
结构内部呼叫使用cflow()
,但我还没有想通了,并没有尝试过无论是。无论如何,它在一般情况下都不起作用。
更新1:
我周围的一些人也玩过。这是一个替代使用execution()
而不是call()
。因此,它不能依赖封闭的连接点,但需要分析当前的调用堆栈。我没有对上述解决方案的性能进行基准测试,但它肯定会编织更少的连接点。此外,它在其切入点内使用if()
,而不是在建议中使用if
声明。该条件仍然是在运行时动态确定的,而不是在编译代码时静态的,但我认为这是不可能的,因为它取决于控制流。
只是为了它的乐趣,我在本机和基于注释的语法中都提出了解决方案。我个人更喜欢本地语法,因为它更具表现力恕我直言)。
天然AspectJ的语法可替代的解决方案:
package de.scrum_master.aspect;
public aspect DemoAspect {
pointcut publicMethodCalls() :
execution(public !static * de.scrum_master..*(..)) &&
if(Thread.currentThread().getStackTrace()[3].getClassName() != thisJoinPointStaticPart.getSignature().getDeclaringTypeName());
before() : publicMethodCalls() {
System.out.println(thisJoinPoint);
}
}
在@AspectJ语法替代解决方案:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SampleAspect {
@Pointcut("execution(public !static * de.scrum_master..*(..)) && if()")
public static boolean publicMethodCalls(JoinPoint thisJoinPoint) {
return Thread.currentThread().getStackTrace()[3].getClassName() != thisJoinPoint.getSignature().getDeclaringTypeName();
}
@Before("publicMethodCalls(thisJoinPoint)")
public void myPointcut(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
控制台输出替代溶液(相同两种语法变体):
execution(void de.scrum_master.app.Foo.fooOne(Bar))
execution(void de.scrum_master.app.Bar.doSomethingBarish())
execution(void de.scrum_master.app.Bar.barOne(Foo))
execution(void de.scrum_master.app.Foo.doSomethingFooish())
更新2:
下面是使用execution()
加上通过Class.isAssignableFrom(Class)
一些反思也应与类层次结构和接口工作的另一种变体:切换到时
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.SoftException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SampleAspect {
@Pointcut("execution(public !static * de.scrum_master..*(..)) && target(callee) && if()")
public static boolean publicMethodCalls(Object callee, JoinPoint thisJoinPoint) {
Class<?> callerClass;
try {
callerClass = Class.forName(
Thread.currentThread().getStackTrace()[3].getClassName()
);
} catch (Exception e) {
throw new SoftException(e);
}
Class<?> calleeClass = callee.getClass();
return !callerClass.isAssignableFrom(calleeClass);
}
@Before("publicMethodCalls(callee, thisJoinPoint)")
public void myPointcut(Object callee, JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
什么是你的目标AspectJ的?通常我会看到用户切换*,因为他们想拦截内部调用的方法。你为什么要切换并保持旧的行为?请帮我理解这一点,以便让我提出一个很好的建议。 – kriegaex 2014-11-21 23:27:10
@ kriegaex:我想使用一些新的方面,切入点应拦截内部调用的方法。但是,在迁移过程中,我不想触及已编写的方面。然而,编写的方面假定它们不会在内部调用的方法上调用(即,它们是使用Spring AOP开发的) – Setheron 2014-11-21 23:54:33