2014-11-21 150 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