Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aspectj Pointcut for matching public method calls on annotated field

Tags:

java

aop

aspectj

I want to write a pointcut that matches execution of public methods on an annotated field. This how ever does not seem to work. The get(@Important) works as you expect (on its own) but it will of course match all access to the field. I want to limit this to only public method execution.

Is this possible at all? I get no compile error but on the other hand it doesn't seem to work..


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

Works:

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

Does not work:

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

like image 738
JustOneMoreQuestion Avatar asked Feb 28 '13 15:02

JustOneMoreQuestion


1 Answers

For what you want there is no out-of-the-box AspectJ solution, because if you intercept method executions of any objects there is no connection to annotated fields which might point to those objects. It would be easier to intercept method executions of annotated classes or annotated methods, but this is not what you want to do.

Here is a little code example which shows you a workaround, but also its limitations:

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

As you can see, my aspect keeps a set of "important objects". More exactly, it is a Map in which the keys are the "important objects" and the values are sets of referrers. This is necessary because theoretically several referrers (e.g. Visitors objects) can point to identical "important objects" (e.g. a specific Counter). In an earlier version of my sample code when I just recorded the "important objects" in a simple set, I had the choice of either never removing formerly "important objects" from the set even when they were not referenced anymore or always removing them even if a second referrer was still pointing to an "important object". The map approach enables me to record multiple referrers per "important object".

If you run Visitors.main(String[]), you will see the following output (please uncomment the before ... : unimportantSetter ... advice if you wish to see even more log output):

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]

Please carefully compare the code in main to the log output in order to see which regular and special cases I have tested.

As I said, the approach has its limitations:

  • I have not tested what will happen if important fields have primitive types like int or are Strings which can theoretically occur multiple times as "important objects" because several unrelated important members create equal objects. I also have not tested what happens concerning auto (un)boxing, please try by yourself.
  • The aspect code is somewhat complex and probably not blazingly fast.
  • I cannot guarantee that there might not be other problems I have not thought of yet.

But if you are in control of the boundary conditions and use cases, you can make an informed decision and use the code as is or a variant of it in order to achieve what you need. The code probably has potential for improvement, I was just curious and wanted to hack a proof of concept.

like image 78
kriegaex Avatar answered Dec 02 '22 16:12

kriegaex