AspectJ - получить идентификатор потока родительского потока, который сгенерировал лямбда-функцию, используя aspectJ - PullRequest
0 голосов
/ 11 марта 2020

У меня есть следующий код:

@RequestMapping(method = RequestMethod.GET, path = "/execute")
public @ResponseBody
String execute(@RequestParam("name") String input) throws Exception {

    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.execute(() -> {
        try {
            // Do something...
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    });
    return "lambda call";
}

Я хочу использовать аспектJ, чтобы отследить выполнение лямбда-функции и определить идентификатор потока, который ее сгенерировал - потока, в котором выполняется моя функция «выполнить» Я знаю, как перехватить лямбда-функцию -

execution(void lambda$*(..)

Но уже слишком поздно для меня идентифицировать идентификатор потока, который создал этот поток (тот, который вызвал "execute"), потому что лямбда работает в новый поток. Как я могу получить «родительский» идентификатор потока / «выполнить» идентификатор потока?

1 Ответ

1 голос
/ 12 марта 2020

У вас есть несколько проблем:

  • В настоящее время AspectJ не может переплетаться с лямбдами с помощью execution() pointcut. Это в первую очередь связано с тем, что инструкция JVM invokedynamic игнорируется компилятором / ткачом AspectJ. Смотрите также AspectJ tickets # 471347 (создан мной) и # 364886 . Кроме того, если вы вместо этого используете анонимный класс Runnable, вы можете легко его перехватить.

  • Вы не создаете и не запускаете поток самостоятельно, а переносите его на классы и методы JDK, такие как ExecutorService.execute(Runnable), то есть вы также не можете вплетать в их execution(), только в их call(), сделанные из вашего собственного (аспектно-сплетенного) кода.

  • В Java есть нет такой общей концепции, как «родительские потоки», которую вы легко могли бы определить по исполняющему потоку с помощью вымышленного метода, такого как Thread.getParent() или аналогичного. Для групп потоков реализованы некоторые родительские функции, но это вам здесь не поможет.

Так что то, что у вас осталось, это косвенный способ, подобный этому:

Приложение драйвера:

package de.scrum_master.app;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Application {
  String execute(String input) throws Exception {
    ExecutorService executor = Executors.newSingleThreadExecutor();

    executor.execute(() -> {
      try {
        doSomething();
      } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
      }
    });

    return "lambda call";
  }

  private void doSomething() throws IOException {}

  public static void main(String[] args) throws Exception {
    new Application().execute("dummy");
  }
}

Формат:

package de.scrum_master.aspect;

import java.util.concurrent.ExecutorService;

public aspect MyAspect {
  // Catch-all advice for logging purposes
  before() : !within(MyAspect) {
    System.out.println("  " + thisJoinPoint);
  }

  // Intercept calls to ExecutorService.execute(*)
  before(Runnable runnable) : call(void ExecutorService.execute(*)) && args(runnable) {
    System.out.println(Thread.currentThread() + " | " + thisJoinPoint + " -> " + runnable);
  }

  // Intercept lambda executions
  before() : execution(private void lambda$*(..)) {
    System.out.println(Thread.currentThread() + " | " + thisJoinPoint);
  }
}

Журнал консоли:

  staticinitialization(de.scrum_master.app.Application.<clinit>)
  execution(void de.scrum_master.app.Application.main(String[]))
  call(de.scrum_master.app.Application())
  preinitialization(de.scrum_master.app.Application())
  initialization(de.scrum_master.app.Application())
  execution(de.scrum_master.app.Application())
  call(String de.scrum_master.app.Application.execute(String))
  execution(String de.scrum_master.app.Application.execute(String))
  call(ExecutorService java.util.concurrent.Executors.newSingleThreadExecutor())
  call(void java.util.concurrent.ExecutorService.execute(Runnable))
Thread[main,5,main] | call(void java.util.concurrent.ExecutorService.execute(Runnable)) -> de.scrum_master.app.Application$$Lambda$1/2046562095@2dda6444
  execution(void de.scrum_master.app.Application.lambda$0())
Thread[pool-1-thread-1,5,main] | execution(void de.scrum_master.app.Application.lambda$0())
  call(void de.scrum_master.app.Application.doSomething())
  execution(void de.scrum_master.app.Application.doSomething())
...