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

查看:55
本文介绍了从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.EnclosingStaticPart )和被调用者( 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 调用 Foo Bar 方法,
  • 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天全站免登陆