Я должен обнаруживать изменения значений полей. Я хочу сравнить предыдущее значение с новым. Я не знаю ни имени поля, ни его типа. (Дополнительная информация здесь.) Для примера данного класса:
package eu.zacheusz.aspectjtries;
@eu.zacheusz.aspectjtries.MyAnnotation
public class Sample {
private String field;
public void modify(){
this.field = "new";
}
public static void main(String[] a){
new Sample().modify();
}
}
У меня есть этот аспект:
package eu.zacheusz.aspectjtries.aspects;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class SampleAspect {
@After(" set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *) && args(value) && target(m) ")
public void afterSetField(Object m, Object value){
System.out.println("After set field. value=" + value + " target=" + m.getClass());
}
}
Проблема в том, что args
выставляет значение, переданное в точке соединения набора полей, а не текущее значение поля. В этой презентации на стр. 27 Я нашел:
sets(int p._x)[oldVal] [newVal]
но, похоже, он вообще не компилируется с моим кодом (аннотациями). Когда я попытался:
@After(" set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *)[oldVal] [newVal] && target(m) ")
public void afterSetField(Object m, Object oldVal, Object newVal){
Затем я получил:
Syntax error on token " set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *)[oldVal] [newVal] && target(m)", "unexpected pointcut element: '['@53:53" expected
Это рабочее решение с использованием отражения:
@Around(" set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *) && args(newVal) && target(t) ")
public void aroundSetField(ProceedingJoinPoint jp, Object t, Object newVal) throws Throwable{
Signature signature = jp.getSignature();
String fieldName = signature.getName();
Field field = t.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
Object oldVal = field.get(t);
System.out.println("Before set field. "
+ "oldVal=" + oldVal + " newVal=" + newVal + " target.class=" + t.getClass());
//TODO compare oldVal with newVal and do sth.
jp.proceed();
}
Это решение с лучшей производительностью, чем отражение (я думаю). Но все еще есть большие накладные расходы (дополнительное поле и экземпляр аспекта привязки к каждой цели).
@Aspect("perthis(set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *))")
public class SampleAspect {
private final Map<String, Object> values = new HashMap<String, Object>();
@Around(" set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *) && args(newVal) && target(t) ")
public void beforeSetField(ProceedingJoinPoint jp, Object t, Object newVal) throws Throwable {
String fieldName = jp.getSignature().getName();
Object oldVal = this.values.get(fieldName);
System.out.println("Before set field. "
+ "oldVal=" + oldVal + " newVal=" + newVal + " target.class=" + t.getClass());
//TODO compare oldVal with newVal and do sth.
this.values.put(fieldName, newVal);
jp.proceed();
}
}
и вот решение с использованием объявления родителей:
@Aspect
public class AspectC {
public interface FieldTracker {
Map<String, Object> getValues();
}
// this implementation can be outside of the aspect
public static class FieldTrackerImpl implements FieldTracker {
private transient Map<String, Object> values;
@Override
public Map<String, Object> getValues() {
if (values == null) {
values = new HashMap<String, Object>();
}
return values;
}
}
// the field type must be the introduced interface. It can't be a class.
@DeclareParents(value = "@eu.zacheusz.aspectjtries.MyAnnotation *", defaultImpl = FieldTrackerImpl.class)
private FieldTracker implementedInterface;
@Around("set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *) && args(newVal) && target(t)")
public void beforeSetField(final ProceedingJoinPoint jp, final FieldTracker t, final Object newVal) throws Throwable{
final Map<String, Object> values = t.getValues();
final String fieldName = jp.getSignature().getName();
final Object oldVal = values.get(fieldName);
System.out.println("Before set field " + fieldName
+ " oldVal=" + oldVal + " newVal=" + newVal + " target.class=" + t.getClass());
//TODO compare oldVal with newVal and do sth.
values.put(fieldName, newVal);
jp.proceed();
}
Резюмируя, есть три альтернативы:
- pertarget/perthis вокруг набора с картой значений поля
- синглтон вокруг набора с отражением
- синглтон вокруг набора с объявленными родителями и картой значений полей
Лучшим решением было бы получить предыдущее значение непосредственно из pointcut (без отражения или запоминания значений полей между pointcut). Является ли это возможным? Если нет, то какой вариант имеет лучшую производительность?
Дополнительные замечания
Я нашел это обсуждение предыдущего значения в set pointcut, но оно вполне Старый.
Весь этот механизм предназначен для обнаружение изменений внутреннего состояния компонента JSF в области сеанса — исправление для Google App Engine. Такой компонент обычно имеет менее 100 полей. Все вызывается из одного потока.