выставляя предыдущее значение в AspectJ set-pointcut

Я должен обнаруживать изменения значений полей. Я хочу сравнить предыдущее значение с новым. Я не знаю ни названия поля, ни его типа. (Более подробная информация здесь .) Для примера данного класса:

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 values = new HashMap();            
        @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 getValues();
    }
    // this implementation can be outside of the aspect

    public static class FieldTrackerImpl implements FieldTracker {

        private transient Map values;

        @Override
        public Map getValues() {
            if (values == null) {
                values = new HashMap();
            }
            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 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 вокруг набора значений полей map
  • singleton вокруг набора с отражением
  • singleton вокруг набора с объявлением родителей и значений полей map

Лучшим решением было бы получить предыдущее значение непосредственно из pointcut (без отражения или запоминания значений полей между pointcut). Является ли это возможным? Если нет, то какая альтернатива имеет лучшую производительность?

Дополнительные примечания

Я нашел это обсуждение предыдущего значения в set pointcut, но оно довольно старое.

Весь этот механизм предназначен для ] обнаружение изменений внутреннего состояния компонента JSF в области сеанса - исправление для Google App Engine. У такого bean-компонента обычно меньше 100 полей. Все вызывается из одного потока.

9
задан 16 revs 25 July 2011 в 03:36
поделиться