aspectJ中的cflow能否检测跨线程执行?

Can cflow in aspectJ detect cross-thread execution?

问题

我想在 运行 所有调用方法

之前打印出请求 url 和响应
public class UpdateRequester {
   private void throwMessage(String requestUrl, String page) {
      //Some code inside
   }
}

将在测试中调用的方法class:

public class Test {
  public void testUpdate() {
    Executors.scheduleWithFixedDelay(new Runnable() {
        public void run() {
          //It will call throwMessage sometimes in the future
        }
    }, ...);
  }  
}

所以我设计了一个方面:

public aspect TestUpdate {
   static final void println(String s) {
     System.out.println(s);
   }

   pointcut testUpdateFlow() : cflow(this(Test) && execution(void testUpdate()));

   pointcut throwMessageCut(String url, String response) : this(UpdateRequester) && args(url, response) && execution(void throwMessage(String, String));

   before(String url, String response) : testUpdateFlow() && throwMessageCut( url,  response) {
    println("=============Url============");
    println(url);
    println("============Respnse=========");
    println(response);
   }
}

该方面不向控制台打印任何内容。如果我删除 testUpdateFlow(),它会打印到控制台。 我认为 aspectJ 中的 cflow 不会将 Executors.scheduleWithFixedDelay 的代码 运行 视为在 testUpdate() 的流程中。在这种情况下,有什么方法可以让 aspectJ 检测线程交叉调用吗?

让我们假设我们有这些 classes:

package de.scrum_master.app;

public class UpdateRequester {
    public void doSomething() {
        throwMessage("http://my.url.org/foo", "my page");
    }

    private void throwMessage(String requestUrl, String page) {
        System.out.println("Throwing message for request " + requestUrl + " on page '" + page + "'");
    }
}

因为throwMessage(..)在你的例子中是私有的,所以我特意添加了一个public方法doSomething(),可以被测试调用class,我还添加了一个main(..) 方法作为我测试的入口点:

package de.scrum_master.app;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Test {
    public void testUpdate() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
        executorService.scheduleWithFixedDelay(
            new Runnable() {
                public void run() {
                    new UpdateRequester().doSomething();
                }
            },
            500L,
            1000L,
            TimeUnit.MILLISECONDS
        );
    }

    public static void main(String[] args) {
        new Test().testUpdate();
    }
}

现在让我们从我们的 before() 建议中打印异常堆栈跟踪,以找出控制流的真正含义:

package de.scrum_master.app;

import de.scrum_master.app.UpdateRequester;

public aspect TestUpdate {
    pointcut throwMessageCut(String url, String response) :
        this(UpdateRequester) &&
        args(url, response) &&
        execution(void throwMessage(String, String));

    before(String url, String response) :
        /*testUpdateFlow() &&*/
        throwMessageCut(url,  response)
    {
        System.out.println(thisJoinPoint);
        new Exception().printStackTrace(System.out);
    }
}

您会看到这样的堆栈跟踪:

execution(void de.scrum_master.app.UpdateRequester.throwMessage(String, String))
java.lang.Exception
    at de.scrum_master.app.TestUpdate.ajc$before$de_scrum_master_app_TestUpdatefbc0c(TestUpdate.aj:16)
    at de.scrum_master.app.UpdateRequester.throwMessage(UpdateRequester.java:9)
    at de.scrum_master.app.UpdateRequester.doSomething(UpdateRequester.java:5)
    at de.scrum_master.app.Test.run(Test.java:13)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.runAndReset(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access1(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Throwing message for request http://my.url.org/foo on page 'my page'

this() 不是 Test,而是 Test,它是您在代码中定义的匿名内部 Runnable subclass。您还会看到 Test.testUpdate() 并不真正在控制流中,因为它在堆栈跟踪中无处可见。您可以像这样修改切入点:

pointcut testUpdateFlow() :
    cflow(
        this(Runnable) &&
        withincode(public void Test..*.run(..)) &&
        call(* UpdateRequester.*(..))
    );

表示:在

的控制流程中
  • Runnable
  • 的实例
  • 在下面定义的 public void run(..) 方法的代码中某处 Test(内部 class),
  • 调用UpdateRequester的任何方法。

即现在方面看起来像这样(控制台输出保持不变):

package de.scrum_master.app;

import de.scrum_master.app.UpdateRequester;
import java.lang.Runnable;

public aspect TestUpdate {
    pointcut testUpdateFlow() :
        cflow(
            this(Runnable) &&
            withincode(public void Test..*.run(..)) &&
            call(* UpdateRequester.*(..))
        );

    pointcut throwMessageCut(String url, String response) :
        this(UpdateRequester) &&
        args(url, response) &&
        execution(void throwMessage(String, String));

    before(String url, String response) :
        testUpdateFlow() &&
        throwMessageCut(url,  response)
    {
        System.out.println(thisJoinPoint);
        new Exception().printStackTrace(System.out);
    }
}

您还可以使用嵌套的 cflow() 语句,如下所示:

pointcut testUpdateFlow() :
    cflow(
        this(Runnable) &&
        cflow(execution(public void Test..*.run(..))) &&
        call(* UpdateRequester.*(..))
    );

或者,为了避免匿名内部 class 你可以创建一个命名内部 class:

package de.scrum_master.app;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Test {
    public static class UpdateRequesterStarter implements Runnable {
        public void run() {
            new UpdateRequester().doSomething();
        }
    }

    public void testUpdate() {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
        executorService.scheduleWithFixedDelay(
            new UpdateRequesterStarter(),
            500L,
            1000L,
            TimeUnit.MILLISECONDS
        );
    }

    public static void main(String[] args) {
        new Test().testUpdate();
    }
}

现在输出改变了,请注意调用堆栈的不同:

execution(void de.scrum_master.app.UpdateRequester.throwMessage(String, String))
java.lang.Exception
    at de.scrum_master.app.TestUpdate.ajc$before$de_scrum_master_app_TestUpdatec6f966b(TestUpdate.aj:24)
    at de.scrum_master.app.UpdateRequester.throwMessage(UpdateRequester.java:9)
    at de.scrum_master.app.UpdateRequester.doSomething(UpdateRequester.java:5)
    at de.scrum_master.app.Test$UpdateRequesterStarter.run(Test.java:10)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.runAndReset(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access1(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Throwing message for request http://my.url.org/foo on page 'my page'

现在您可以 refine/simplify testUpdateFlow() 切入点:

package de.scrum_master.app;

import de.scrum_master.app.UpdateRequester;
import de.scrum_master.app.Test.UpdateRequesterStarter;

public aspect TestUpdate {
    pointcut testUpdateFlow() :
        cflow(execution(public void UpdateRequesterStarter.run(..)));

    pointcut throwMessageCut(String url, String response) :
        this(UpdateRequester) &&
        args(url, response) &&
        execution(void throwMessage(String, String));

    before(String url, String response) :
        testUpdateFlow() &&
        throwMessageCut(url,  response)
    {
        System.out.println(thisJoinPoint);
        new Exception().printStackTrace(System.out);
    }
}

另请注意更改后的 import 语句。