аспект j поперечная резьба pointcut

Я новичок в аннотации AspectJ для Java, и мне интересно, можно ли поместить pointcut в вызов перекрестного потока.

Вот код:

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

Я хотел бы иметь pointcut, который срабатывает только тогда, когда Plain.getValue() вызывается Connector.getStart().

Является ли это возможным? Спасибо.


person Irwan Hendra    schedule 14.01.2017    source источник


Ответы (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(..) так, чтобы вместо 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