выставление предыдущего значения в AspectJ set-pointcut - PullRequest
9 голосов
/ 14 июля 2011

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

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 полей.Все вызывается из одного потока.

Ответы [ 3 ]

5 голосов
/ 22 июля 2011

К сожалению, в настоящее время в AspectJ нет встроенной функции для просмотра старого значения поля.

Два решения, которые вы уже получили, вполне стандартны, и, вероятно, лучшее в этом случае - отражение.

Другой вариант:

public aspect FieldTracker {

    public interface TrackingField {};
    public Map<String,Object> TrackingField.fields;

    declare parents : @Deprecated * : implements TrackingField;

    void around(TrackingField t, Object val) :
        set(!static !final !transient * TrackingField.*) 
        && args(val) 
        && target(t) 
    {

        String fieldName = thisJoinPointStaticPart.getSignature().getName();
        Object oldVal = t.fields == null ? null : t.fields.get(fieldName);

        // do whatever

        if (val != null) {
            if (t.fields == null) t.fields = new HashMap<String,Object>();
            t.fields.put(fieldName, val);
        }
        proceed(t,val);
    }
}

(я написал этот код здесь, поэтому могут быть некоторые ошибки)

Но при этом создается карта для отслеживания каждого экземпляра и дополнительное поле в каждом экземпляре для хранения этой карты, так что это даст вам более или менее одинаковые накладные расходы для аспекта pertarget.

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

3 голосов
/ 20 июля 2011

Есть лучшее решение.Он имеет лучшую производительность, чем рефлексия.

    @Aspect("pertarget(set(!static !final !transient * (@Deprecated *) . *))")
    public class SampleAspect {

        private Object oldVal;

        @Before(" set(!static !final !transient * (@Deprecated *) . *) && args(newVal) && target(t) ")
        public void beforeSetField(Object t, Object newVal) throws Throwable{
            System.out.println("Before set field. "
                    + "oldVal=" + oldVal + " newVal=" + newVal + " target.class=" + t.getClass());
            this.oldVal = newVal;
        }
    }

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

2 голосов
/ 14 июля 2011

Похоже, что на этом слайде используется ранняя версия AspectJ. руководство по портированию говорит, что удаление 's' на pointcuts необходимо для старых советов.

Вот совет из другого учебника , который не делаетиспользуйте аннотации в AspectJ:

  aspect GuardedX {
      static final int MAX_CHANGE = 100;
      before(int newval): set(static int T.x) && args(newval) {
      if (Math.abs(newval - T.x) > MAX_CHANGE)
          throw new RuntimeException();
      }
  }

Относительно вашего кода:

  • Применение вашего совета после происходящего набора кажется мне немного странным.Применение совета как «до» кажется более понятным.
  • Новое значение - это аргумент точки соединения, а не точка.Pointcut указывает старый аргумент.К сожалению, в этом примере и тип, и имя поля известны.Так что на него можно ссылаться в совете.

Необходимо связать

Из другого обсуждения , похоже, что нетспособ получения текущего значения устанавливаемого поля (или его типа) без привязки информации в подписи точки соединения.

...