AspectJ中的cflow可以检测跨线程执行吗?

时间:2015-02-25 17:53:06

标签: java multithreading aspectj

问题

我想在运行对方法

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

该方法将在Test类中调用:

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来检测穿越线程的呼叫吗?

1 个答案:

答案 0 :(得分:2)

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

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(..)在您的示例中是私有的,所以我故意添加了一个公共方法doSomething(),可以由测试类调用,我还在其中添加了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_TestUpdate$1$33fbc0c(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$1.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.access$301(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$1,它是您在代码中定义的匿名内部Runnable子类。您还会看到Test.testUpdate()实际上并不在控制流中,因为在堆栈跟踪中无处可见。您可以像这样修改切入点:

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

这意味着:在

的控制流程中
  • Runnable
  • 的一个实例
  • public void run(..)(内部类)下面定义的Test方法的代码中的某处,
  • 调用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.*(..))
    );

或者,为了避免使用匿名内部类,您可以创建一个命名的内部类:

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_TestUpdate$1$9c6f966b(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.access$301(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'

现在,您可以将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语句。