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


person Community    schedule 14.07.2011    source източник


Отговори (3)


за съжаление в момента няма вградена функция в 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);
    }
}

(Написах този код тук, така че може да има някои грешки)

Но това създава карта за проследяване на всеки екземпляр и допълнително поле във всеки екземпляр за задържане на тази карта, така че ще ви даде повече или по-малко същите режийни разходи за целевия аспект.

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

person Simone Gianni    schedule 21.07.2011
comment
Добра идея. Доколкото разбирам вашата концепция, целевите обекти вече трябва да имплементират TrackingField интерфейс? BTW С pertarget имаше проблем. oldVal от примера на Mikes може да съдържа стойности само за едно поле. Дадената цел има неизвестен брой полета. Както предложихте, добавих карта. И сигурни ли сте, че в момента няма вградена функция в AspectJ, за да видите старата стойност на полето? - person zacheusz; 22.07.2011
comment
Здравей Zacheusz, целевият обект не трябва да внедрява интерфейса, аспектът ще ги накара да внедрят интерфейса, така че да можем да инжектираме полето, което държи картата. Следя активно AspectJ само през последните 3 години, но от това, което мога да разбера, гледайки стари публикации, изглежда, че имаше начин да го направя, но беше премахнат заедно с редица други подобни функции за подобряване на представянето и защото това не беше много често използван случай. Но отново, това е само това, което мисля, че се е случило. - person Simone Gianni; 22.07.2011
comment
Благодаря много - това е страхотен отговор. Така че ще внедря три решения (pertarget/perthis, около с отражение, деклариране) и ще направя някои тестове за производителност. Въпросът все още остава отворен. Може би sb знае повече подробности за предишната стойност в set-pointcut. IMHO това би било най-доброто решение. - person zacheusz; 22.07.2011
comment
Благодаря, що се отнася до производителността, имайте предвид, че интерфейсът се инжектира по време на вълната, така че това не е проблем с производителността. Наличието на указател към hashmap е още едно поле, така че означава още 32/64 бита при разпределяне на обекта. Картата по подразбиране е нулева и се разпределя само когато трябва да съхрани стойността на полето. Ако имате стотици полета на вашите проследявани обекти, може би използването на TreeMap може да ускори нещата. Ако имате достъп до тези полета чрез множество нишки, трябва да напишете някакъв вид синхронизация на нишки на картата (заключването за четене и запис вероятно е най-доброто решение). - person Simone Gianni; 22.07.2011
comment
Добри точки. Това е фон: Той е за откриване на промени във вътрешното състояние на bean с обхват на JSF сесия - корекция за Google App Engine. Такова зърно обикновено има по-малко от 100 полета. Всичко се извиква от една нишка. - person zacheusz; 22.07.2011
comment
Знам, че това възкресява стара публикация, но как се справяте в случай на промяна в нещо като колекция? Не можете просто да създадете pointcut на сетера; трябва да създадете pointcut към getter().add/getter().remove на колекцията. Имате ли креативен начин да направите това? - person Eric B.; 16.07.2014
comment
@EricB. трябва да направите както правят повечето системи за устойчивост: посъветвайте получателя и върнете собствената си реализация на колекцията (вашата реализация на List например), която обвива оригиналната и прихваща добавяне, премахване или каквото друго ви трябва. Освен съветите за гетъра, това не е пряко свързано с AspectJ, Hibernate например използва други техники за съвети за гетри и сетери на обекти и използва org.hibernate.collection.internal.AbstractPersistentCollection, за да замени колекциите със свои собствени реализации. - person Simone Gianni; 17.07.2014
comment
@SimoneGianni. Благодаря; това е почти това, което планирах да направя за общите реализации (напр.: списък, колекция и т.н.). Но няма да работи за @Embedded класове, които също трябва да бъдат посъветвани относно техните методи getEmbedded().setField(), тъй като няма гаранция, че @Embedded клас прилага предварително дефиниран интерфейс и т.н. - person Eric B.; 18.07.2014

Има по-добро решение. Има по-добро представяне от отразяването.

    @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 няма друг начин.

person Mike    schedule 20.07.2011
comment
Благодаря много, но тук има един проблем. oldVal може да съдържа стойности за едно поле. Дадената цел има неизвестен брой полета. Актуализирах вашето решение с карта от концепцията на SimoneGiannis. - person zacheusz; 22.07.2011

Изглежда, че този слайд използва ранна версия на AspectJ. Едно ръководство за пренасяне казва, че премахването на '' от точките е необходими за по-стари съвети.

Ето един съвет от друг урок, който не използва анотациите в 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. Pointcut уточнява стария аргумент. За съжаление в този пример обаче и типът, и името на полето са известни. За да може да се посочи в съвета.

Трябва да се обвърже

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

person Atreys    schedule 14.07.2011
comment
Благодаря много. 1. Коригирах наборите за набор и оставих [] синтаксиса, както виждате в моя списък. 2. Значи без размисъл няма как? Ако не знам името на полето или неговия тип, тогава как мога да избера правилното поле с помощта на отражение? - person zacheusz; 14.07.2011
comment
Намерено решение с размисъл. Но все още се чудя как да го постигна без размисъл? - person zacheusz; 14.07.2011