2014-11-21 145 views
0

我正在將一些使用Spring AOP的代碼遷移到AspectJ方面(編譯時編譯)。我正在尋找關於如何修改切入點的反饋,以便在遷移後它們的行爲相同?從Spring AOP轉換爲AspectJ

當前Spring AOP Aspects只能作爲'Proxies'使用,因此只能從外部調用者使用public/interface方法。現在我已經切換到AspectJ編織;即使是從課內調用本身的方法也正在編織中。

這使我很頭疼,我想知道我是否可以改變切入點,以某種方式仍然表現得好像它仍然是一個代理? (即,在類型層次結構中的任何點排除來自其內部的呼叫,例如調用繼承功能)

+0

什麼是你的目標AspectJ的?通常我會看到用戶切換*,因爲他們想攔截內部調用的方法。你爲什麼要切換並保持舊的行爲?請幫我理解這一點,以便讓我提出一個很好的建議。 – kriegaex 2014-11-21 23:27:10

+0

@ kriegaex:我想使用一些新的方面,切入點應攔截內部調用的方法。但是,在遷移過程中,我不想觸及已編寫的方面。然而,編寫的方面假定它們不會在內部調用的方法上調用(即,它們是使用Spring AOP開發的) – Setheron 2014-11-21 23:54:33

回答

4

我認爲吉吉對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電話都FooBar方法,
  • 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); 
    } 
} 
+0

我剛用另一種方法更新了我的解決方案。一探究竟。 – kriegaex 2014-11-22 15:19:59

+0

真棒我會檢查出來。這可能會更復雜一些,因爲我現在必須檢查superClass,因爲我現在在抽象類上應用切入點。 – Setheron 2014-11-24 01:57:45

+0

當你有一個類層次結構時,可能我的第一種方法比第二種方法更好,因爲它實際上比較了對象標識,即它應該可靠地檢測類型爲「this.doSomething(..)」的方法調用。新方法不會,所以在父方法從子類調用方法或反之亦然的情況下,它可能會失敗。我忘了說當玩弄代碼的時候。 – kriegaex 2014-11-24 13:00:39

1

邏輯解決方案似乎是混合AspectJ AOP和Spring AOP,如本文section of Spring documentation所述。你應該能夠爲特定的類使用AspectJ AOP,並保持Spring AOP的其餘部分不變。

下面是相關的文字:

爲此,您可以通過使用聲明中的一個或多個元素。每個元素指定一個名稱模式,並僅使用模式中的至少一個匹配的名稱豆類將使用Spring AOP自動代理配置:

<aop:aspectj-autoproxy> 
    <aop:include name="thisBean"/> 
    <aop:include name="thatBean"/> 
</aop:aspectj-autoproxy> 

請注意,我沒有測試過這個自己,但似乎是非常適合你的情況。

+0

此刻,我正在使用AspectJ編譯時編織(方面庫而不是Spring AOP附帶的方法庫)。我將不得不看看這兩個人是否可以輕鬆採取我將報告回來! – Setheron 2014-11-23 21:58:50