从 Spring AOP 到 AspectJ 的转换 [英] Converting from Spring AOP to AspectJ

查看:32
本文介绍了从 Spring AOP 到 AspectJ 的转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将一些使用 Spring AOP 的代码迁移到 AspectJ 方面(在编译时编织).我正在寻找有关如何修改切入点以使它们在迁移后表现相同的反馈?

I'm migrating some code that uses Spring AOP to AspectJ aspects (weaved at compile time). I'm looking for feedback on how I can modify the pointcut so that that they behave the same after the migration?

当前 Spring AOP Aspects 仅用作代理",因此只能从外部调用者对公共/接口方法起作用.现在我已经切换到 AspectJ 编织;甚至从类内部到自身的方法调用也在被编织.

Currently Spring AOP Aspects only function as 'Proxies' and therefore only work from external callers on public/interface methods. Now that I've switched to AspectJ weaving; even method calls from within the class to itself are being weaved.

这让我很头疼,我想知道我是否可以改变切入点以某种方式仍然表现得好像它仍然是一个代理?(即在类型层次结构中的任何一点从自身内部排除调用,例如调用继承的函数)

This is causing me a big headache and I was wondering if i can change the pointcut to somehow still behave as if it was still a proxy ? (i.e. exclude calls from within itself at any point in the type hierarchy such as calling an inherited function)

推荐答案

我认为 Jigish 的想法是 混合方面样式 是迁移甚至永久继续的好方法,只要您没有任何引人注目的问题(例如性能)与春天.

I think Jigish's idea to mix aspect styles is a good way to migrate or even to continue on a permanent basis, as long as you do not have any compelling issues (e.g. performance) with Spring.

现在,话虽如此,我为您提供了一种解决方法,以防万一您出于任何原因确实需要在成熟的 AspectJ 中排除内部调用.我不认为它是优雅的,但它的工作原理.这是一个概念证明:

Now, having said that, I have a workaround for you just in case you do need to exclude internal calls in full-blown AspectJ for any reason. I do not think it is elegant, but it works. Here is a proof of concept:

两个示例应用程序类:

这些类在内部调用自己的方法,但也会调用其他类的方法.例如.Foo.fooOne(Bar) 在外部调用 Bar.doSomethingBarish(),但在内部调用 Foo.fooTwo(int).

These classes call their own methods internally, but also other classes' methods. E.g. Foo.fooOne(Bar) calls Bar.doSomethingBarish() externally, but also Foo.fooTwo(int) internally.

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;
    }
}

package de.scrum_master.app;

public class Bar {
    public void doSomethingBarish() {
        barTwo(22);
    }

    public void barOne(Foo foo) {
        foo.doSomethingFooish();
        barTwo(11);
    }

    public String barTwo(int number) {
        return barThree("xxx");
    }

    public String barThree(String text) {
        return text + " " + text + " " + text;
    }
}

使用 main 方法的驱动程序应用程序:

Driver application with main method:

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);
    }
}

示例方面包括内部调用:

Sample aspect including internal calls:

这是编写方面的常用方法.它重现了您的问题.我在这里使用 call() 切入点而不是 execution() 以便同时访问调用者 (JoinPoint.EnclosureStaticPart) 和被调用者(JoinPoint) 连接点并能够打印它们以供说明.在 execution() 切入点中,两个值都是相同的.

This is the usual way to write aspects. It reproduces your problem. I am using a call() pointcut here instead of execution() in order to have access to both caller (JoinPoint.EnclosingStaticPart) and callee (JoinPoint) joinpoints and be able to print them for illustration. In an execution() pointcut both values would be identical.

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);
    }
}

控制台输出:

在这里你可以很好地看到如何

Here you can see nicely how

  • Application 调用 FooBar 方法,
  • Foo 调用一个 Bar 方法,但在内部也调用它自己的方法,
  • Bar 调用一个 Foo 方法,但在内部也调用它自己的方法.
  • Application calls both Foo and Bar methods,
  • Foo calls a Bar method, but also its own ones internally,
  • Bar calls a Foo method, but also its own ones internally.
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))

动态改进方面排除内部调用:

Improved aspect dynamically excluding internal calls:

现在我们需要比较调用者(原生aspectJ语法中的this()切入点)是否与被调用者(target()切入点)相同.如果是这样,我们想跳过通知执行.在 AspectJ 中有两种方法可以获取调用方/被调用方引用:

Now we need to compare if the caller (this() pointcut in native aspectJ syntax) is identical to the callee (target() pointcut). If so, we want to skip advice execution. There are two ways to get caller/callee references in AspectJ:

  • 通过 this() 和/或 target() 将它们绑定到 poinctut 中的参数.不过,这里有一个警告:如果 this()target()null,则 poinctut 将不匹配,即静态方法作为调用者或者被调用者被排除在外.在我的例子中,我想看到 Application.main(..) 的调用,所以我只会在切入点中绑定目标/被调用者,而不是调用者/这个对象.
  • 通过JoinPoint.getThis() 和/或JoinPoint.getTarget() 从正在执行的通知中动态确定它们.这很好用,但这里需要注意的是,它可能会慢一点,即使在您想立即排除静态调用方/被调用方的情况下,建议也会执行.
  • binding them to parameters in the poinctut via this() and/or target(). There is one caveat here, though: If this() or target() are null the poinctut will not match, i.e. static methods as callers or callees are ruled out. In my example I want to see the calls by Application.main(..) though, so I will only bind the target/callee in the pointcut, but not the caller/this object.
  • determining them dynamically from within an executing advice via JoinPoint.getThis() and/or JoinPoint.getTarget(). This works nicely, but the caveat here is that it is probably a bit slower and that the advice will execute even in cases in which you would like to exclude static callers/callees right away.

这里我们选择了一种混合方法,包括静态调用者,但不包括静态被调用者,以演示这两种变体:

Here we choose a mixed approach, including static callers, but excluding static callees in order to demonstrate both variants:

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() 直接.

As you can see, we have reduced the output to only the calls we are interested in. If you also want to exclude the static caller Application.main(..), just bind this() directly.

execution(void de.scrum_master.app.Application.main(String[])) -> call(void de.scrum_master.app.Foo.fooOne(Bar))
  caller = null
  callee = de.scrum_master.app.Foo@6a5c2445
execution(void de.scrum_master.app.Foo.fooOne(Bar)) -> call(void de.scrum_master.app.Bar.doSomethingBarish())
  caller = de.scrum_master.app.Foo@6a5c2445
  callee = de.scrum_master.app.Bar@47516490
execution(void de.scrum_master.app.Application.main(String[])) -> call(void de.scrum_master.app.Bar.barOne(Foo))
  caller = null
  callee = de.scrum_master.app.Bar@47516490
execution(void de.scrum_master.app.Bar.barOne(Foo)) -> call(void de.scrum_master.app.Foo.doSomethingFooish())
  caller = de.scrum_master.app.Bar@47516490
  callee = de.scrum_master.app.Foo@6a5c2445

如果在特殊情况下您知道通知所针对的确切类名,您可能可以使用 cflow() 来排除内部调用而不会出现丑陋的 if构造,但我还没有考虑清楚,也没有尝试过.无论如何,它在一般情况下不起作用.

If in special cases you know the exact class name targeted by an advice, you might be able to use cflow() in order to exclude internal calls without ugly if constructs, but I have not thought it through and not tried either. It does not work in general cases anyway.

更新 1:

我又玩了一些.这是使用 execution() 代替 call() 的替代方法.因此,它不能依赖于封闭的连接点,而是需要分析当前的调用堆栈.我没有针对上述解决方案对性能进行基准测试,但它肯定会编织更少的连接点.此外,它在其切入点中使用 if() 而不是在通知中使用 if 语句.条件仍然是在运行时动态确定的,而不是在编织代码时静态确定的,但我想这在这里无论如何都是不可能的,因为它取决于控制流.

I played around some more. Here is an alternative using execution() instead of call(). Thus, it cannot rely on an enclosing joinpoint but needs to analyse the current callstack. I have not benchmarked the performance against the solution described above, but it will definitely weave fewer joinpoints. Furthermore, it uses if() right within its pointcut instead of using an if statement inside the advice. The condition is still determined dynamically during runtime, not statically while weaving the code, but I guess that is impossible here anyway because it depends on the control flow.

只是为了好玩,我用本机语法和基于注释的语法来介绍解决方案.我个人更喜欢原生语法,因为它更具表现力恕我直言).

Just for the fun of it I am presenting the solution in both native and annotation-based syntax. I personally prefer native syntax because it it more expressive IMHO).

原生 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) 反射的变体,它也适用于类层次结构和接口:

Here is another variant using execution() plus some reflection via Class.isAssignableFrom(Class) which should also work with class hierarchies and interfaces:

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);
    }
}

这篇关于从 Spring AOP 到 AspectJ 的转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆