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.
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:
To:@Pointcut("cflow(execution(* com.yourcompany.zoo.animal.Animal.playTricks(..) ) )")public void cflowOps() {}
@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 {@Overridepublic 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 thisdog.accompany();Pet petDog = new Dog();//instead you should do thispetDog.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:
@Aspectpublic 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@1d88a478before:call(void com.yourcompany.zoo.animal.FurryPet.accompany()) caller:com.yourcompany.zoo.animal.PetMain@b4fb35d target:com.yourcompany.zoo.animal.FurryPet@75e5d16dbefore:call(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.PetMain@b4fb35d target:com.yourcompany.zoo.animal.Dog@43188793more than accompany, man's best friend foreverbefore:call(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.PetMain@b4fb35d target:com.yourcompany.zoo.animal.Dog@7f6ce64emore 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@394df741more than accompany, man's best friend forevermore 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@394df741before:execution(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.FurryPet@2da3e9cb target:com.yourcompany.zoo.animal.FurryPet@2da3e9cbbefore:execution(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.Dog@644a25d5 target:com.yourcompany.zoo.animal.Dog@644a25d5before:execution(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.Dog@644a25d5 target:com.yourcompany.zoo.animal.Dog@644a25d5more than accompany, man's best friend foreverbefore:execution(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.Dog@3ff9f663 target:com.yourcompany.zoo.animal.Dog@3ff9f663before:execution(void com.yourcompany.zoo.animal.Pet.accompany()) caller:com.yourcompany.zoo.animal.Dog@3ff9f663 target:com.yourcompany.zoo.animal.Dog@3ff9f663more 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@2a3fa87amore than accompany, man's best friend foreverbefore:execution(void com.yourcompany.zoo.animal.Dog.accompany()) caller:com.yourcompany.zoo.animal.Dog@394df741 target:com.yourcompany.zoo.animal.Dog@394df741more 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:
- You write an Aspect inheriting from AbstractPerfSpyAspect. You useexecute(superclass.method)to capture all method executio
- You configure in PerfSpy-Config.xml which concrete subclass you want to capture
- 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?
No comments:
Post a Comment