Это не то, что вы можете сделать непосредственно с помощью 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"
.
Наслаждайтесь!