Я должен обнаружить изменения значений полей.Я хочу сравнить предыдущее значение с новым.Я не знаю имени поля или его типа.(Больше фона здесь .) Для примера данного класса:
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 полей.Все вызывается из одного потока.