Monday, September 2, 2013

Differences between AspectJ call() and execute()



There are a lot of nuances with these two PointCuts: call() and execute(). If you do not understand them, you will truly be tearing your hair in despair from time to time. This PPT Call and Execution Semantics in AspectJ discusses the differences, but it losses me on all the mathematics – why things have to be explained in a complex way? So in this blog, I want to present my understanding in a much easier way.

For a call(), think of it as a telephone call, someone or something is initiating the call; for an execute(), the executor (in AspectJ terminology, also known as target) is executing the task (the code) inside itself:






Naturally, you can see, if use call() PointCut, the caller must be weaved.  If use execute() PointCut,  then you can weave just the executor.

Continue to use PerfSpyDemo as an example:



Show is the caller, it calls animal.playTricks(). At runtime, the executor is determined to be a Dolphin.

Recall in AOP.xml, Show is not weaved: 

<aspectj>
        <aspects>
                <aspect name="com.yourcompany.zoo.PerfSpyDemoAsepct"/>
        </aspects>
        <weaver options="-verbose -showWeaveInfo -debug">
                <include within="com.yourcompany.zoo.animal..*"/>
                <include within="com.yourcompany.zoo.people..*"/>      
               <dump beforeandafter="true" within="com.yourcompany.zoo.animal..*"/>
               <dump beforeandafter="true" within="com.yourcompany.zoo.people..*"/>       
        </weaver>
</aspectj>


If we change PerfSpyDemoAspect from:
@Pointcut("cflow(execution(*  com.yourcompany.zoo.animal.Animal.playTricks(..)  ) )")
        public void cflowOps() {
        }
To:

@Pointcut("cflow(call(*  com.yourcompany.zoo.animal.Animal.playTricks(..)  ) )")
        public void cflowOps() {
        }
The Aspect around Animal.playTricks() won’t be captured because the caller is not weaved. 

 If we change AOP.xml to weave Show:
<include within="com.yourcompany.zoo..*"/>
The Aspect around Animal.playTricks() will be captured. 

So this is the first nuance: if you use call(), the caller must be weaved.  

Let us use some other animals to explore other nuances.  
 

To make things simple, all these classes are under the same package:



public class Pet {
        public void accompany() {
        }
}
public class FurryPet extends Pet {
        public void cuddle() {
        }
}
public class Dog extends FurryPet {
        @Override
        public void accompany() {
                super.accompany();
                System.out.println("more than accompany, man's best friend foreever");
        }
}
public class PetMain {
        public void playWithPets() {
                Pet pet = new Pet();
                pet.accompany();

                FurryPet furryPet = new FurryPet();
                furryPet.accompany();

                Dog dog = new Dog();
                //if you follow "programing towards interface, not implementations"
                //you shouldn't do this
                dog.accompany();

                Pet petDog = new Dog();
                //instead you should do this
                petDog.accompany();
        }

        public static void main(String[] args) {
                new PetMain().playWithPets();
        }
}
 Let us write a very simple Aspect this time, nothing is simpler than a Before Aspect:

@Aspect
public class PetBeforeAspect {
                @Pointcut("call(* Pet.accompany())")
                public void callPetAccompanyPointcut() {
                }
       
                @Before("callPetAccompanyPointcut()")
                public void beforeCallPetAccompany(JoinPoint joinPoint) {
                        System.out.println("before:" + joinPoint);
                }
}

This Aspect uses “call()”, and it is aimed at  Pet.accompany() – the superclass’ method. 

Now check the PetMain class.

Notice the right turn arrows – they are indicators that the method at that line is being advised. Run PetMain.main(), you will see all 4 accompany() methods are advised:
before:call(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.PetMain@b4fb35d target:com.yourcompany.zoo.animal.Pet@1d88a478
before:call(void com.yourcompany.zoo.animal.FurryPet.accompany()) caller:com.yourcompany.zoo.animal.PetMain@b4fb35d target:com.yourcompany.zoo.animal.FurryPet@75e5d16d
before:call(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.PetMain@b4fb35d target:com.yourcompany.zoo.animal.Dog@43188793
more than accompany, man's best friend forever
before:call(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.PetMain@b4fb35d target:com.yourcompany.zoo.animal.Dog@7f6ce64e
more than accompany, man's best friend forever
This experiment shows: call() pointcut matching is done at compilation time. At compilation time, it can be determined pet, furryPet, dog, petDog are all of Pet, therefore their accompany() methods all match with call(* Pet.accompany()).

We can do another experiment to prove this conclusion. Let us change call() to aim at the subclass Dog’s accompany() method:


@Pointcut("call(* Dog.accompany())")
        public void callDogAccompanyPointcut() {
        }

        @Before("callDogAccompanyPointcut()")
        public void beforeCallDogAccompany(JoinPoint joinPoint) {
                System.out.println("before:" + joinPoint + " caller:" + joinPoint.getThis() + " target:"
                                + joinPoint.getTarget());
        }


Now only dog.accompany() is being advised, petDog.accompany() is not:

 

Run PetMain.main(), you can see the output:

before:call(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.PetMain@2a3fa87a target:com.yourcompany.zoo.animal.Dog@394df741
more than accompany, man's best friend forever
more than accompany, man's best friend forever

Only dog.accompany() is captured, petDog.accompany() is not. This is because, at compilation time, petDog can’t be determined to be a dog, and thus does not match with @Pointcut("call(* Dog.accompany())").
  
Let us now change the Aspect to use execute():
@Pointcut("execution(* Pet.accompany())")
        public void executionPetAccompanyPointcut() {
        }

        @Before("executionPetAccompanyPointcut()")
        public void beforeExecutionPetAccompany(JoinPoint joinPoint) {
                System.out.println("before:" + joinPoint + " caller:" + joinPoint.getThis() + " target:"
                                + joinPoint.getTarget());
        }

Run PetMain.main(), you can see the output:
before:execution(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.Pet@394df741 target:com.yourcompany.zoo.animal.Pet@394df741
before:execution(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.FurryPet@2da3e9cb target:com.yourcompany.zoo.animal.FurryPet@2da3e9cb
before:execution(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.Dog@644a25d5 target:com.yourcompany.zoo.animal.Dog@644a25d5
before:execution(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.Dog@644a25d5 target:com.yourcompany.zoo.animal.Dog@644a25d5
more than accompany, man's best friend forever
before:execution(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.Dog@3ff9f663 target:com.yourcompany.zoo.animal.Dog@3ff9f663
before:execution(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.Dog@3ff9f663 target:com.yourcompany.zoo.animal.Dog@3ff9f663
more than accompany, man's best friend forever
Again, accompany() methods of all pet, furryPet, dog, petDog match with execution(* Pet.accompany()), but there is a difference between call(* Pet.accompany()) and execution(* Pet.accompany()).  Dog.accompany() first calls super.accompany(), which is captured by execution(* Pet.accompany())  , but not call(* Pet.accompany()), which is a surprise to me – I’d expect call(* Pet.accompany()) to capture super.accompany(), as it should be able to determine at compilation time super is a pet

And also notice, with execute(), the caller and executor (target) are one and the same.

Let us change execute() to aim at the subclass Dog’s accompany() method:

         @Pointcut("execution(* Dog.accompany())")
        public void executionDogAccompanyPointcut() {
        }

        @Before("executionDogAccompanyPointcut()")
        public void beforeExecutionDogAccompany(JoinPoint joinPoint) {
                System.out.println("before:" + joinPoint + " caller:" + joinPoint.getThis() + " target:"
                                + joinPoint.getTarget());
        }
 The output is:
before:execution(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.Dog@2a3fa87a target:com.yourcompany.zoo.animal.Dog@2a3fa87a
more than accompany, man's best friend forever
before:execution(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.Dog@394df741 target:com.yourcompany.zoo.animal.Dog@394df741
more than accompany, man's best friend forever

accompany() methods of dog and petDog match with execution(* Dog.accompany()). This is very different from call(* Dog.accompany()). call(* Dog.accompany()) only captures dog.accompany(), while execution(* Dog.accompany()) captures both dog.accompany() and petDog.accompany().

This leads to the conclusion: execute() pointcut matching is done at executing time (no surprise, the pointcut is named execute()), at runtime, petDog is determined to be a dog, and thus matches with execution(* Dog.accompany()). 

PerfSpy uses this technique. PerfSpy is best used to monitor applications with defined interfaces: the abstract PerfSpyAspect monitors an abstract or interface method, while PerfSpy-Config.xml configures which concrete method to spy on. Most applications will use some sort of third-party frameworks with defined interfaces (e.g. Struts), so it is advised to use execute() to describe the pointcuts, as most of time, you do not care about third-party frameworks, and having less classes to weave is good for performance and memory footprint. 

The best usage of PerfSpy is:

  1. You write an Aspect inheriting from AbstractPerfSpyAspect. You useexecute(superclass.method)to capture all method executio
  2. You configure in PerfSpy-Config.xml which concrete subclass you want to capture
  3.   AbstractPerfSpyAspect checks the executor (in AspectJ terminology, target) at runtime, and only monitor the concrete subclass’ execution

Here are some tests, see if you can pass them.


-------------------Test 1-------------------
 Aspect:

        @Pointcut("execution(* Dog.cuddle())")
        public void executionCuddlePointcut() {
        }

        @Before("executionDogCuddlePointcut()")
        public void beforeExecutionCuddle(JoinPoint joinPoint) {
                System.out.println("before:" + joinPoint + " caller:" + joinPoint.getThis() + " target:"
                                + joinPoint.getTarget());
        }

Method invocations:

             furryPet.cuddle();
                dog.cuddle();


Test: What did the Aspect capture?


-------------------Test 2-------------------
 Aspect:

@Pointcut("execution(* FurryPet.cuddle())")
        public void executionCuddlePointcut() {
        }

        @Before("executionDogCuddlePointcut()")
        public void beforeExecutionCuddle(JoinPoint joinPoint) {
                System.out.println("before:" + joinPoint + " caller:" + joinPoint.getThis() + " target:"
                                + joinPoint.getTarget());
        }
Method invocations:

furryPet.cuddle();
                dog.cuddle();

Test: What did the Aspect capture?













1 comment: