暴露 AspectJ 设置切入点中的先前值

发布于 11-24 06:57 字数 5621 浏览 6 评论 0原文

我必须检测字段值的变化。我想将之前的值与新值进行比较。我不知道字段名称或其类型。 (更多背景信息此处。)对于给定的示例类:

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围绕集合映射
  • 单例,带有反射
  • 单例围绕带有声明父项和字段值映射的集合

最好的解决方案是获取先前的值直接来自切入点(无需反射或记住切入点之间的字段值)。是否可以?如果不是,哪种替代方案具有最佳性能?

我发现的附加说明

此讨论关于设置切入点中的先前值,但它已经很旧了。

所有这些机制都是针对 检测 JSF 会话范围的 bean 内部状态更改 - 针对 Google App Engine 的修复。这样的 Bean 通常具有不到 100 个字段。所有这些都是从一个线程调用的。

I have to detect fields value changes. I want to compare the previous value with the new one. I don't know the field name or its type. (More background here.) For sample given class:

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();
    }
}

I have this aspect:

    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());
        }
}

The problem is that the args is exposing the value passed at the field set joint point and not the current value of the field. In this presentation at page 27 I found:

sets(int p._x)[oldVal] [newVal]

but it doesn't seem to compile with my code (annotations) at all.
When I tried:

@After(" set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *)[oldVal] [newVal] && target(m) ")
    public void afterSetField(Object m, Object oldVal, Object newVal){

Then I got :

Syntax error on token " set(!static !final !transient * (@eu.zacheusz.aspectjtries.MyAnnotation *) . *)[oldVal] [newVal] && target(m)", "unexpected pointcut element: '['@53:53" expected

This is working solution using reflection:

@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();
}

This is solution with better performance than reflection (I think). But there is still large overhead (additional field, and binding aspect instance to each target).

    @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();
        }
    }

and here is solution using declare parents:

@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();
    }

Reasumming there are three alternatives:

  • pertarget/perthis around set with field values map
  • singleton around set with reflection
  • singleton around set with declare parents and field values map

The best solution would be getting the previous value directly from pointcut (without reflection or remembering field values between pointcuts). Is it possible? If not, which alternative has the best performance?

Additional notes

I found this discussion about previous value in set pointcut, but it's quite old.

All this mechanism is for detecting JSF session-scoped bean internal state changes - fix for Google App Engine. Such a bean usually have less than 100 fields. All is invoked from one thread.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

二手情话2024-12-01 06:57:23

不幸的是,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 序列化(它比使用反射快得多),并且查看旧值的能力只是一个副作用。

unfortunately there isn't currently a built-in feature in AspectJ to see the old value of the field.

The two solutions you already obtained are quite standard, and probably the reflection one is the best in this case.

Another option is :

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);
    }
}

(I have written this code here, so there could be some errors)

But this creates a map to track each instance, and an additional field in each instance to hold that map, so it will give you more or less the same overhead of the pertarget aspect.

I'm using an aspect similar to this one currently, but in that case I need that map for json serialization (it is much faster than using reflection), and the ability to see old values is just a side effect.

﹏雨一样淡蓝的深情2024-12-01 06:57:23

有更好的解决方案。它比反射具有更好的性能。

    @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没有其他办法。

There is better solution. It has better performance than reflection.

    @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;
        }
    }

I know that you wrote that you "don't want to remember field values between pointcuts". AFAIK there is no other way.

邮友2024-12-01 06:57:23

该幻灯片看起来使用的是 AspectJ 的早期版本。 移植指南告诉我们删除切入点上的 's' 是对于较早的建议是必要的。

这是另一个教程的建议不使用 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();
      }
  }

关于您的代码:

  • 在集合发生之后应用您的建议对我来说有点奇怪。将建议应用为“之前”似乎更容易理解。
  • 新值是连接点的参数,而不是切入点。切入点指定旧参数。不幸的是,在这个例子中,类型和字段名都是已知的。以便可以在建议中引用它。

需要绑定

来自另一个讨论 ,看起来没有办法在不绑定连接点签名中的信息的情况下获取正在设置的字段(或其类型)的当前值。

It looks like that slide is using an early version of AspectJ. A porting guide tells that the removal of 's' on the pointcuts is necessary for older advice.

Here's a piece of advice from another tutorial that doesn't use the annotations in 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();
      }
  }

Regarding your code:

  • Applying your advice after the set occurs feels a bit odd to me. Applying the advice as a 'before' seems more understandable.
  • The new value is an argument to the joinpoint, not the pointcut. The pointcut specifies the old argument. Unfortunately in this example both the type and the fieldname are known, though. So that it can be referenced int the advice.

Need to bind

From another discussion, it looks like there's not a way to get the current value of the field being set (or its type) without binding the information in the signature of the joinpoint.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文