2017-01-14 18 views
0

Я новичок в аннотации AspectJ для Java, и мне интересно, можно ли поставить pointcut на вызов перекрестного потока.aspectj cross-thread 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().

Возможно ли это? Благодарю.

ответ

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(..) так, чтобы позвонить run() метод Нить непосредственно (т.е. не запускается новый поток, но выполняется в том же потоке) вместо start(), ситуация меняется:

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) 

В этой ситуации мы могли бы использовать динамический cflow() (поток управления AspectJ,) Pointcut так:

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 аннотации стиля, поэтому я переключение на родной синтаксис. Это гораздо более выразительным, и мы можем добиться того, что мы хотим, чтобы более легко с точки зрения ВТД (определение межтипового), потому что

  • в родной синтаксис мы можем просто объявить дополнительную переменную-член экземпляра для данного класса в то время как
  • в синтаксисе @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