【问题标题】:Aspectj Pointcut for matching public method calls on annotated field用于匹配注释字段上的公共方法调用的 Aspectj 切入点
【发布时间】:2013-02-28 15:02:31
【问题描述】:

我想编写一个切入点,以匹配在注释字段上执行公共方法。这似乎不起作用。 get(@Important) 可以按您的预期工作(单独),但它当然会匹配对该字段的所有访问权限。我想将此限制为仅执行公共方法。

这可能吗?我没有收到编译错误,但另一方面它似乎不起作用..


public class Counter {
  private int count = 0;

  public void add(int value) {
    count = count + value;
  }
}

public class Visitors {
  @Important
  Counter counter = new Counter()

  public void increaseCounter() {
    counter.add(1);
  }
}

作品:

@Pointcut(value = "get(@Important * *)")
void testPointCut() {
}

不起作用:

@Pointcut(value = "get(@Important * *) && execution(public * *(..))")
void testPointCut() {
}

【问题讨论】:

    标签: java aop aspectj


    【解决方案1】:

    对于您想要的,没有开箱即用的 AspectJ 解决方案,因为如果您拦截任何对象的方法执行,则与可能指向这些对象的注释字段没有连接。拦截带注释的类或带注释的方法的方法执行会更容易,但这不是您想要做的。

    这是一个小代码示例,它向您展示了一种解决方法,但也说明了它的局限性:

    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Important {}
    
    public class Counter {
        private int count = 0;
    
        public void add(int value) {
            count = count + value;
        }
    
        @Override
        public String toString() {
            return super.toString() + "[count=" + count + "]";
        }
    }
    
    public class Visitors {
        @Important
        Counter counter = new Counter();
    
        public void increaseCounter() {
            counter.add(1);
        }
    
        public static void main(String[] args) {
            Visitors visitors = new Visitors();
            visitors.increaseCounter();
            visitors.counter.add(3);
            System.out.println("visitors.counter = " + visitors.counter);
            System.out.println("--------------------");
    
            Counter unimportantCounter = new Counter();
            unimportantCounter.add(11);
            unimportantCounter.add(22);
            System.out.println("unimportantCounter = " + unimportantCounter);
            System.out.println("--------------------");
    
            unimportantCounter = visitors.counter;
            unimportantCounter.add(5);
            System.out.println("visitors.counter = " + visitors.counter);
            System.out.println("unimportantCounter = " + unimportantCounter);
            System.out.println("--------------------");
    
            visitors.counter = new Counter();
            visitors.increaseCounter();
            visitors.counter.add(3);
            unimportantCounter.add(100);
            System.out.println("visitors.counter = " + visitors.counter);
            System.out.println("unimportantCounter = " + unimportantCounter);
            System.out.println("--------------------");
    
            Visitors otherVisitors = new Visitors();
            otherVisitors.increaseCounter();
            otherVisitors.counter.add(50);
            System.out.println("otherVisitors.counter = " + otherVisitors.counter);
            System.out.println("--------------------");
    
            otherVisitors.counter = visitors.counter;
            System.out.println("visitors.counter = " + visitors.counter);
            System.out.println("otherVisitors.counter = " + otherVisitors.counter);
            System.out.println("--------------------");
    
            otherVisitors.counter = new Counter();
            visitors.increaseCounter();
            otherVisitors.increaseCounter();
            System.out.println("visitors.counter = " + visitors.counter);
            System.out.println("otherVisitors.counter = " + otherVisitors.counter);
        }
    }
    
    import java.lang.reflect.Field;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.SoftException;
    
    public aspect ImportantMethodInterceptor {
        Map<Object, Set<Object>> importantObjects = new HashMap<Object, Set<Object>>(); 
    
        pointcut importantSetter(Object newValue, Object target) :
            set(@Important * *) && args(newValue) && target(target);
        pointcut unimportantSetter(Object newValue, Object target) :
            !set(@Important * *) && set(* *) && !withincode(*.new(..)) && args(newValue) && target(target);
        pointcut publicMethod(Object target) :
            execution(public * *(..)) && target(target) && !execution(public String *..toString());
    
        before(Object newValue, Object target) : importantSetter(newValue, target) {
            Object oldValue = getFieldValue(thisJoinPoint.getSignature(), target);
            System.out.println("Important object for target " + target + ": " + oldValue + " -> " + newValue);
            synchronized (importantObjects) {
                Set<Object> referrers;
                if (oldValue != null) {
                    referrers = importantObjects.get(oldValue);
                    if (referrers != null) {
                        referrers.remove(target);
                        if (referrers.size() == 0)
                            importantObjects.remove(oldValue);
                    }
                }
                if (newValue != null) {
                    referrers = importantObjects.get(newValue);
                    if (referrers == null) {
                        referrers = new HashSet<Object>();
                        importantObjects.put(newValue, referrers);
                    }
                    referrers.add(target);
                }
            }
        }
    
    //  before(Object newValue, Object target) : unimportantSetter(newValue, target) {
    //      Object oldValue = getFieldValue(thisJoinPoint.getSignature(), target);
    //      System.out.println("Unimportant object for target " + target + ": " + oldValue + " -> " + newValue);
    //  }
    
        before(Object target) : publicMethod(target) {
            synchronized (importantObjects) {
                if (importantObjects.get(target) != null)
                    System.out.println("Important method on " + target + ": " + thisJoinPointStaticPart);
                else
                    System.out.println("Unimportant method on " + target + ": " + thisJoinPointStaticPart);
            }
        }
    
        private Object getFieldValue(Signature signature, Object target) {
            try {
                Field field = signature.getDeclaringType().getDeclaredField(signature.getName());
                field.setAccessible(true);
                return field.get(target);
            }
            catch (Exception e) { throw new SoftException(e); }
        } 
    }
    

    如您所见,我的方面保留了一组“重要对象”。更准确地说,它是一个Map,其中键是“重要对象”,值是引用者集。这是必要的,因为理论上多个引用者(例如Visitors 对象)可以指向相同的“重要对象”(例如特定的Counter)。在我的示例代码的早期版本中,当我将“重要对象”记录在一个简单的集合中时,我可以选择要么永远不从集合中删除以前的“重要对象”,即使它们不再被引用,或者总是删除它们如果第二个引用者仍然指向“重要对象”。地图方法使我能够为每个“重要对象”记录多个引用者。

    如果您运行Visitors.main(String[]),您将看到以下输出(如果您希望查看更多日志输出,请取消注释before ... : unimportantSetter ... 建议):

    Important object for target Visitors@1404536: null -> Counter@7fdcde[count=0]
    Unimportant method on Visitors@1404536: execution(void Visitors.increaseCounter())
    Important method on Counter@7fdcde[count=0]: execution(void Counter.add(int))
    Important method on Counter@7fdcde[count=1]: execution(void Counter.add(int))
    visitors.counter = Counter@7fdcde[count=4]
    --------------------
    Unimportant method on Counter@18ac738[count=0]: execution(void Counter.add(int))
    Unimportant method on Counter@18ac738[count=11]: execution(void Counter.add(int))
    unimportantCounter = Counter@18ac738[count=33]
    --------------------
    Important method on Counter@7fdcde[count=4]: execution(void Counter.add(int))
    visitors.counter = Counter@7fdcde[count=9]
    unimportantCounter = Counter@7fdcde[count=9]
    --------------------
    Important object for target Visitors@1404536: Counter@7fdcde[count=9] -> Counter@1d6096[count=0]
    Unimportant method on Visitors@1404536: execution(void Visitors.increaseCounter())
    Important method on Counter@1d6096[count=0]: execution(void Counter.add(int))
    Important method on Counter@1d6096[count=1]: execution(void Counter.add(int))
    Unimportant method on Counter@7fdcde[count=9]: execution(void Counter.add(int))
    visitors.counter = Counter@1d6096[count=4]
    unimportantCounter = Counter@7fdcde[count=109]
    --------------------
    Important object for target Visitors@b02e7a: null -> Counter@bb6ab6[count=0]
    Unimportant method on Visitors@b02e7a: execution(void Visitors.increaseCounter())
    Important method on Counter@bb6ab6[count=0]: execution(void Counter.add(int))
    Important method on Counter@bb6ab6[count=1]: execution(void Counter.add(int))
    otherVisitors.counter = Counter@bb6ab6[count=51]
    --------------------
    Important object for target Visitors@b02e7a: Counter@bb6ab6[count=51] -> Counter@1d6096[count=4]
    visitors.counter = Counter@1d6096[count=4]
    otherVisitors.counter = Counter@1d6096[count=4]
    --------------------
    Important object for target Visitors@b02e7a: Counter@1d6096[count=4] -> Counter@5afd29[count=0]
    Unimportant method on Visitors@1404536: execution(void Visitors.increaseCounter())
    Important method on Counter@1d6096[count=4]: execution(void Counter.add(int))
    Unimportant method on Visitors@b02e7a: execution(void Visitors.increaseCounter())
    Important method on Counter@5afd29[count=0]: execution(void Counter.add(int))
    visitors.counter = Counter@1d6096[count=5]
    otherVisitors.counter = Counter@5afd29[count=1]
    

    请仔细对比main中的代码和日志输出,看看我测试了哪些常规和特殊情况。

    正如我所说,这种方法有其局限性:

    • 我还没有测试过如果重要字段具有像intStrings 这样的原始类型会发生什么,理论上可以多次作为“重要对象”出现,因为几个不相关的重要成员创建了相同的对象。我也没有测试过自动(取消)装箱会发生什么,请自行尝试。
    • 方面代码有些复杂,可能速度不是很快。
    • 我不能保证可能没有其他我还没有想到的问题。

    但是,如果您能够控制边界条件和用例,您可以做出明智的决定,并按原样使用代码或其变体,以实现您所需要的。代码可能有改进的潜力,我只是好奇,想破解一个概念证明。

    【讨论】:

    • 感谢您确认我的怀疑,开箱即用的 AJ 无法实现我想要的。尽管我对 AJ 很陌生,但我可以看到您在 POC 中尝试做什么。非常有趣和有教育意义!谢谢!
    【解决方案2】:

    您想使用withinCode 切入点,如下所示:

    @Pointcut(value = "get(@Important * *) && withinCode(public * *(..))")
    void testPointCut() {
    }
    

    看看AspectJ programming guide

    【讨论】:

    • 这仍然会捕获对注释字段的所有访问。我只想匹配注释字段上公共方法的执行。
    猜你喜欢
    • 2018-05-08
    • 2013-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多