Вы ошибаетесь, полагая, что 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(..)
так, чтобы вместо start()
вызывался метод run()
потока напрямую (то есть не запуская новый поток, а выполняя его в том же потоке), ситуация изменится:
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()
, чтобы восстановить ситуацию с многопоточностью. Давайте также удалим строку printStackTrace(..)
из Plain.getStart(..)
.
Решение:
Отказ от ответственности: мне не нравится синтаксис @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(..)");
}
}
Журнал консоли с аспектом:
Как видите, поток Handler
, начатый с App.main(..)
, игнорируется аспектом, как и ожидалось. Handler
, начинающийся с Connector.getStart(..)
, запускает аспект.
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
person
kriegaex
schedule
19.01.2017