aspectj 跨线程切入点

aspectj cross-thread pointcut

我是 Java 的 AspectJ 注释的新手,我想知道是否可以将切入点放在跨线程调用上。

代码如下:

public class App {
    public static void main( String[] args ) {
        new Connector().getStart("testtest");
    }
}
public class Connector {
    public void getStart(String s1) {
        Handler h = new Handler(s1);
        h.start();
    }
}
public class Handler extends Thread {
    String s1;

    public Handler(String s1) {
        this.s1 = s1;
    }

    public void run() {
        new Plain().getValue(s1);   
    }
}
public class Plain {
    public void getValue(String s1) {
        System.out.println("Plain getValue: " + s1);
    }
}

我想要一个仅在 Plain.getValue()Connector.getStart() 调用时触发的切入点。

可能吗?谢谢。

您认为 Plain.getValue(..)Connector.getStart(..) 调用是错误的,因为在 multi-threaded 环境中它不是。让我通过对 getValue(..) 方法进行一些调整来证明这一点,打印堆栈跟踪:

package de.scrum_master.app;

public class Plain {
    public void getValue(String s1) {
        System.out.println("Plain getValue: " + s1);
        new Exception().printStackTrace(System.out);
    }
}

顺便说一下,我已经将你所有的 classes 移动到包 de.scrum_master.app 因为在 Java 中不鼓励使用默认包而且 AspectJ 在尝试时也不喜欢它匹配切入点。

控制台日志(multi-threaded):

Plain getValue: testtest
java.lang.Exception
    at de.scrum_master.app.Plain.getValue(Plain.java:4)
    at de.scrum_master.app.Handler.run(Handler.java:9)

看到了吗?日志中没有 Connector.getStart(..) 的踪迹。如果我们还调整 getStart(..) 以便直接调用线程的 run() 方法(即不启动新线程而是在同一线程中执行)而不是 start(),情况会发生变化:

package de.scrum_master.app;

public class Connector {
    public void getStart(String s1) {
        Handler h = new Handler(s1);
        h.run();
    }
}

控制台日志(single-threaded):

Plain getValue: testtest
java.lang.Exception
    at de.scrum_master.app.Plain.getValue(Plain.java:4)
    at de.scrum_master.app.Handler.run(Handler.java:9)
    at de.scrum_master.app.Connector.getStart(Connector.java:4)
    at de.scrum_master.app.App.main(App.java:3)

在这种情况下,我们可以像这样使用 AspectJ 的动态 cflow()(控制流)切入点:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class SingleThreadAspect {
    @Before("execution(* de.scrum_master.app.Plain.getValue(..)) && cflow(execution(* de.scrum_master.app.Connector.getStart(..)))")
    public void interceptControlFlow(JoinPoint thisJoinPoint) {
        System.out.println(thisJoinPoint);
    }
}

建议会如您所愿触发。 但是 出于我回答开头解释的原因 cflow() 不能(也不能)跨线程工作,因为没有这样的作为跨线程的直接控制流。每个线程的控制流都从其 run() 方法开始,而不是更早。这就是 multi-threading.

的全部概念

因此,如果出于任何可疑的原因你真的想模拟 cross-thread 控制流之类的东西,你需要做一些 手动簿记 .

但首先让我们将调整后的 h.run() 恢复到原来的 h.start() 以恢复 multi-threading 的情况。让我们也从 Plain.getStart(..).

中删除 printStackTrace(..)

解法:

免责声明:我不喜欢 annotation-style @AspectJ 语法,所以我切换到本机语法。它更具表现力,我们可以根据 ITD(inter-type 定义)更轻松地实现我们想要的,因为

  • 在本机语法中,我们可以为给定的 class while
  • 声明一个额外的实例成员变量
  • 在@AspectJ 语法中,我们必须声明目标 class 来实现一个具有默认实现的接口,该接口又会携带我们手动记账的成员变量。

让我们修改 App 以便也直接启动一个 Handler 线程。这是我们的负面测试用例,因为我们不想在那里触发我们的建议,因为线程是在 Plain.getValue(..):

之外启动的
package de.scrum_master.app;

public class App {
    public static void main(String[] args) throws InterruptedException {
        // The aspect should ignore this thread
        new Handler("foo").start();
        // Wait a little while so as not to mess up log output
        Thread.sleep(250);
        new Connector().getStart("testtest");
    }
}

没有方面的控制台日志:

Plain getValue: foo
Plain getValue: testtest

看点:

package de.scrum_master.aspect;

import de.scrum_master.app.*;

public aspect CrossThreadAspect {
    // Declare a new instance member for our bookkeeping
    private boolean Handler.cflowConnectorGetStart = false;

    // If handler thread is started from Connector.getStart(..), set a mark
    before(Handler handler) :
        call(void Handler.start()) &&
        cflow(execution(* Connector.getStart(..))) &&
        target(handler)
    {
        System.out.println(thisJoinPoint + "\n  doing bookkeeping");
        handler.cflowConnectorGetStart = true;
    }

    // If current thread is a marked Handler, log it
    before() :
        execution(* Plain.getValue(..)) &&
        if(Thread.currentThread() instanceof Handler) &&
        if(((Handler) Thread.currentThread()).cflowConnectorGetStart)
    {
        System.out.println(thisJoinPoint + "\n  triggered from parent thread via Connector.getStart(..)");
    }
}

具有方面的控制台日志:

如您所见,从 App.main(..) 开始的 Handler 线程按预期被方面忽略。从Connector.getStart(..)开始的Handler触发了方面。

Plain getValue: foo
call(void de.scrum_master.app.Handler.start())
  doing bookkeeping
execution(void de.scrum_master.app.Plain.getValue(String))
  triggered from parent thread via Connector.getStart(..)
Plain getValue: testtest