我認爲吉吉對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