【问题标题】:aspectj cross-thread pointcutaspectj 跨线程切入点
【发布时间】:2017-01-14 12:16:11
【问题描述】:

我是 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() 调用时触发。

有可能吗?谢谢。

【问题讨论】:

    标签: java multithreading aspectj pointcut


    【解决方案1】:

    您错误地认为Plain.getValue(..) 是由Connector.getStart(..) 调用的,因为在多线程环境中它不是。让我通过对 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);
        }
    }
    

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

    控制台日志(多线程):

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

    控制台日志(单线程):

    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() 方法开始,而不是更早。这就是多线程的全部概念。

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

    但首先让我们将调整后的h.run() 恢复为原来的h.start() 以恢复多线程情况。让我们也从Plain.getStart(..) 中删除printStackTrace(..) 行。

    解决方案:

    免责声明:我不喜欢注释风格的@AspectJ 语法,所以我正在切换到本机语法。它更具表现力,我们可以更轻松地根据 ITD(类型间定义)实现我们想要的,因为

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

    让我们修改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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-25
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多