Aspectj跨线程切入点 [英] aspectj cross-thread pointcut

查看:113
本文介绍了Aspectj跨线程切入点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

这是代码:

 公共类App {公共静态void main(String [] args){new Connector().getStart("testtest");}} 

 公共类连接器{公共无效getStart(String s1){处理程序h =新处理程序(s1);h.start();}} 

 公共类处理程序扩展了线程{字符串s1;公共处理程序(字符串s1){this.s1 = s1;}公共无效run(){new Plain().getValue(s1);}} 

 公共类Plain {公共无效的getValue(String s1){System.out.println("Plain getValue:" + s1);}} 

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

有可能吗?谢谢.

解决方案

您误以为 Plain.getValue(..)是由 Connector.getStart(..),因为在多线程环境中则不是.让我稍微调整一下 getValue(..)方法,以显示堆栈跟踪:

 程序包de.scrum_master.app;公共课普通{公共无效的getValue(String s1){System.out.println("Plain getValue:" + s1);新的Exception().printStackTrace(System.out);}} 

顺便说一句,我已经将所有类移到了 de.scrum_master.app 包中,因为在Java中不鼓励使用默认包,并且在尝试匹配切入点时,AspectJ也不太喜欢它.

控制台日志(多线程):

 普通getValue:testtestjava.lang.Exception在de.scrum_master.app.Plain.getValue(Plain.java:4)在de.scrum_master.app.Handler.run(Handler.java:9) 

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

 程序包de.scrum_master.app;公共类连接器{公共无效getStart(String s1){处理程序h =新处理程序(s1);h.run();}} 

控制台日志(单线程):

 普通的getValue:testtestjava.lang.Exception在de.scrum_master.app.Plain.getValue(Plain.java:4)在de.scrum_master.app.Handler.run(Handler.java:9)在de.scrum_master.app.Connector.getStart(Connector.java:4)在de.scrum_master.app.App.main(App.java:3) 

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

  package de.scrum_master.aspect;导入org.aspectj.lang.JoinPoint;导入org.aspectj.lang.annotation.Aspect;导入org.aspectj.lang.annotation.Before;@方面公共类SingleThreadAspect {@Before("execution(* de.scrum_master.app.Plain.getValue(..))&& cflow(execution(* de.scrum_master.app.Connector.getStart(..)))")公共无效interceptControlFlow(JoinPoint thisJoinPoint){System.out.println(thisJoinPoint);}} 

该建议将根据您的意愿触发. 但是 出于我在回答开头提到的原因, cflow()不能(也不能)跨线程工作,因为没有这样的东西作为跨线程的直接控制流.每个线程的控制流都以其 run()方法开始,而且没有更早.那就是多线程的整个概念.

因此,如果出于任何可疑原因,如果您真的想模拟诸如跨线程控制流之类的东西,则需要进行一些手动记账.

但是首先让我们将经过调整的 h.run()恢复为原始的 h.start(),以恢复多线程情况.让我们还从 Plain.getStart(..)中删除 printStackTrace(..)行.

解决方案:

免责声明:我不喜欢注释样式@AspectJ语法,因此我要切换到本机语法.它更具表现力,而且我们可以在ITD(类型间定义)方面更轻松地实现我们想要的目标,因为

  • 以本机语法,我们可以同时为给定的类声明一个额外的实例成员变量
  • 使用@AspectJ语法,我们必须声明目标类以实现具有默认实现的接口,该接口又将携带用于手动记账的成员变量.

让我们修改 App ,以便直接启动 Handler 线程.这是我们的否定测试用例,因为当线程在 Plain.getValue(..):

之外启动时,我们不想在那里触发建议.

 程序包de.scrum_master.app;公共类应用{公共静态void main(String [] args)抛出InterruptedException {//方面应忽略此线程新的Handler("foo").start();//等待一会儿,以免弄乱日志输出Thread.sleep(250);new Connector().getStart("testtest");}} 

没有方面的控制台日志:

  Plain getValue:foo普通的getValue:testtest 

方面:

  package de.scrum_master.aspect;导入de.scrum_master.app.*;公共方面CrossThreadAspect {//声明一个新的实例成员进行簿记私有布尔Handler.cflowConnectorGetStart = false;//如果处理程序线程是从Connector.getStart(..)启动的,则设置一个标记before(Handler handler):呼叫(void Handler.start())&&cflow(execution(* Connector.getStart(..)))&&目标(处理者){System.out.println(thisJoinPoint +"\ n做簿记");handler.cflowConnectorGetStart = true;}//如果当前线程是标记的处理程序,则将其记录前() :执行(* Plain.getValue(..))&&if(Handler的Thread.currentThread()实例)&&if((((Handler)Thread.currentThread()).cflowConnectorGetStart){System.out.println(thisJoinPoint +"\ n通过Connector.getStart(..)从父线程触发");}} 

具有方面的控制台日志:

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

  Plain getValue:foo呼叫(void de.scrum_master.app.Handler.start())做簿记执行(避免de.scrum_master.app.Plain.getValue(String))通过Connector.getStart(..)从父线程触发普通的getValue:testtest 

I am new with AspectJ annotation for Java, and I am wondering if it is possible to put pointcut on a cross thread invocation.

Here is the code:

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

I would like to have a pointcut that only triggers when Plain.getValue() is called by Connector.getStart().

Is it possible? Thanks.

解决方案

You are making a mistake believing that Plain.getValue(..) is called by Connector.getStart(..) because in a multi-threaded environment it is not. Let me prove it with a little tweak to the getValue(..) method, printing a stack trace:

package de.scrum_master.app;

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

By the way, I have moved all your classes to package de.scrum_master.app because using the default package is discouraged in Java and also AspectJ does not like it when trying to match pointcuts.

Console log (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)

See? There is no trace of Connector.getStart(..) in the log. If we also tweak getStart(..) so as to call the thread's run() method directly (i.e. not starting a new thread but executing in the same thread) instead of start(), the situation changes:

package de.scrum_master.app;

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

Console log (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)

In this situation we could use AspectJ's dynamic cflow() (control flow) pointcut like this:

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

The advice would be triggered just as you wish. But for the reason explained at the beginning of my answer cflow() does not (and cannot) work across threads because there is no such thing as a direct control flow across threads. Each thread's control flow starts with its run() method, no earlier. That is the whole concept of multi-threading.

So if you really want to emulate something like a cross-thread control flow for whatever doubtful reason, you need to do some manual bookkeeping.

But first let us revert the tweaked h.run() back to the original h.start() so as to reinstate the multi-threading situation. Let us also remove the printStackTrace(..) line from Plain.getStart(..).

Solution:

Disclaimer: I do not like annotation-style @AspectJ syntax, so I am switching over to native syntax. It is much more expressive and we can achieve what we want more easily in terms of ITD (inter-type definition) because

  • in native syntax we can just declare an additional instance member variable for a given class while
  • in @AspectJ syntax we would have to declare the target class to implement an interface with a default implementation which in turn would carry the member variable for our manual bookkeeping.

Let us modify App so as to also start a Handler thread directly. This is our negative test case because we do not want to trigger our advice there as the thread is started outside of 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");
    }
}

Console log without aspect:

Plain getValue: foo
Plain getValue: testtest

Aspect:

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(..)");
    }
}

Console log with aspect:

As you can see, the Handler thread started from App.main(..) is ignored by the aspect as expected. The Handler started from Connector.getStart(..) triggers the aspect.

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

这篇关于Aspectj跨线程切入点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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