2016-07-09 2 views
1

Я хочу, чтобы AspectJ вводил измерительный код вокруг всех вызовов любого метода, в полях, аннотированных с помощью @Measured, и фиксировал имя метода. Это то, что у меня есть:Аспект вокруг вызова аннотированного поля

@Pointcut("get(@my.annotation.Measured * *) && @annotation(measured)") 
public void fieldAnnotatedWithMeasured(Measured measured) {} 

@Around(value = "fieldAnnotatedWithMeasured(measured)", argNames = "joinPoint,measured") 
public Object measureField(ProceedingJoinPoint joinPoint, Measured measured) throws Throwable {...} 

использования:

public class A { 

    @Measured private Service service; 
    ... 
    void call(){ 
    service.call(); // here I want to measure the call() time and capture its name 
    } 

Это, кажется, окружают только доступ к полю, а не метод вызова. Я хочу захватить имя вызываемого метода, чтобы он посоветовал.

ответ

1

Это не то, что вы можете сделать непосредственно с помощью pointcut, потому что get(), как вы заметили, полностью отличается от call() или execution() pointcuts. Точка соединения get() полностью прошла до момента завершения call(). Кроме того, call() не имеет представления о том, назначается ли целевой объект, который он вызывается, одному или нескольким (аннотированным) членам класса.

Я думаю, что то, что вы хотите достичь, концептуально проблематично. Вы должны скорее комментировать классы или методы, которые вы хотите измерить, а не члены класса. Но для чего это стоит, я собираюсь представить вам решение. Предостережение. Решение включает ручную бухгалтерскую отчетность, а также отражение. Таким образом, это довольно медленно, но, возможно, еще достаточно быстро для вашей цели. Вы можете решить, попробовать ли вы. Обратите внимание, что это решение - это то, что я чувствую себя неловко, потому что оно не похоже на хорошее применение АОП.

Итак, вот наша испытательная установка:

поле аннотация:

package de.scrum_master.app; 

import java.lang.annotation.ElementType; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.FIELD) 
public @interface Measured {} 

Sample класс играть с позже:

package de.scrum_master.app; 

public class MyClass { 
    private String name; 

    public MyClass(String name) { 
     super(); 
     this.name = name; 
    } 

    @Override 
    public String toString() { 
     return "MyClass[" + name + "]"; 
    } 

    void waitForAWhile() throws InterruptedException { 
     Thread.sleep(200); 
    } 
} 

приложение Driver с помощью класс образца:

Обратите внимание, что только два из четырех членов - один примитивный и один тип объекта - аннотируются @Measured, а два других - нет. Я сделал это, чтобы иметь как положительные, так и отрицательные примеры, чтобы убедиться, что аспект работает правильно.

Еще одна важная вещь заключается в том, что объект, ранее присвоенный аннотированному члену класса, больше не должен сообщаться по аспекту, как только он больше не назначен этому члену. То есть oldMyClass.waitForAWhile(); не следует измерять.

package de.scrum_master.app; 

public class Application { 
    String foo = "unmeasured"; 
    @Measured String bar = "measured"; 
    MyClass myClass1 = new MyClass("unmeasured"); 
    @Measured MyClass myClass2 = new MyClass("measured"); 

    void doSomething() throws InterruptedException { 
     foo.length(); 
     bar.length(); 
     myClass1.waitForAWhile(); 
     myClass2.waitForAWhile(); 

     MyClass oldMyClass = myClass2; 
     myClass2 = new MyClass("another measured"); 
     // This call should not be reported by the aspect because the object 
     // is no longer assigned to a member annotated by @Measured 
     oldMyClass.waitForAWhile(); 
     // This call should be reported for the new member value 
     myClass2.waitForAWhile(); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     new Application().doSomething(); 
    } 
} 

Аспект:

аспект заботится о двух вещах: бухгалтерский учет и измерение. Подробно:

  • Всякий раз, когда значение получает назначение на @Measured поле, он записывается в наборе measuredObjects, потому что это единственный способ позже знать, что, когда вызывается метод этого объекта, это действительно должно быть измеряется.
  • Хотя легко получить новое значение в совете before() : set(), к сожалению, нет простого способа получить старое значение. Вот почему нам нужен уродливый маленький вспомогательный метод getField(Signature signature) с использованием отражения, чтобы это выяснить.
  • Зачем нам нужна старая ценность? Потому что для того, чтобы иметь чистую бухгалтерскую отчетность, нам нужно удалить неназначенные объекты из набора measuredObjects.
  • Также обратите внимание, что measuredObjects не является потокобезопасным, так как я его реализовал, но вы можете просто использовать синхронизированную коллекцию, если вам это нужно.
  • Совет call() сначала проверяет, может ли он найти целевой объект в measuredObjects и прекращает выполнение, если он не может. В противном случае он измеряет время выполнения вызова метода. Это просто.

О, и, кстати, я использую более чистый и более выразительный родной синтаксис AspectJ здесь, а не уродливый стиль аннотации. Если у вас есть какие-либо проблемы с этим, сообщите мне.

package de.scrum_master.app; 

import java.lang.reflect.Field; 
import java.util.HashSet; 
import java.util.Set; 

import org.aspectj.lang.Signature; 
import org.aspectj.lang.SoftException; 

import de.scrum_master.app.Measured; 

public aspect MyAspect { 
    private Set<Object> measuredObjects = new HashSet<>(); 

    before(Measured measured, Object newValue, Object object) : 
     set(* *) && 
     @annotation(measured) && 
     args(newValue) && 
     target(object) 
    { 
     try { 
      Field field = getField(thisJoinPoint.getSignature()); 
      Object oldValue = field.get(object); 
      System.out.println(thisJoinPoint); 
      System.out.println(" old value = " + oldValue); 
      System.out.println(" new value = " + newValue); 
      measuredObjects.remove(oldValue); 
      measuredObjects.add(newValue); 
     } 
     catch (Exception e) { 
      throw new SoftException(e); 
     } 
    } 

    Object around(Object object) : 
     call(* *(..)) && 
     target(object) && 
     !within(MyAspect) 
    { 
     if (!measuredObjects.contains(object)) 
      return proceed(object); 
     long startTime = System.nanoTime(); 
     Object result = proceed(object); 
     System.out.println(thisJoinPoint); 
     System.out.println(" object = " + object); 
     System.out.println(" duration = " + (System.nanoTime() - startTime)/1e6 + " ms"); 
     return result; 
    } 

    private Field getField(Signature signature) throws NoSuchFieldException { 
     Field field = signature.getDeclaringType().getDeclaredField(signature.getName()); 
     field.setAccessible(true); 
     return field; 
    } 
} 

консоль журнал:

set(String de.scrum_master.app.Application.bar) 
    old value = null 
    new value = measured 
set(MyClass de.scrum_master.app.Application.myClass2) 
    old value = null 
    new value = MyClass[measured] 
call(int java.lang.String.length()) 
    object = measured 
    duration = 0.080457 ms 
call(void de.scrum_master.app.MyClass.waitForAWhile()) 
    object = MyClass[measured] 
    duration = 200.472326 ms 
set(MyClass de.scrum_master.app.Application.myClass2) 
    old value = MyClass[measured] 
    new value = MyClass[another measured] 
call(void de.scrum_master.app.MyClass.waitForAWhile()) 
    object = MyClass[another measured] 
    duration = 200.461208 ms 

Как вы можете видеть, аспект ведет себя правильно. Он только сообщает вызов метода по объекту MyClass[measured] один раз, тогда как ему присваивается поле @Measured, но не тогда, когда метод вызывается после того, как он уже не назначен и заменен на MyClass[another measured]. Последнее верно сообщается впоследствии. Вы также видите, как этот аспект прекрасно работает даже для примитивов, таких как String "measured".

Наслаждайтесь!