Saturday, December 29, 2007

Using cflow() and cflowbelow() in pointcuts

Control flow is just the flow of execution of program within specific join point. cflow() and cflowbelow() constructs takes another join point as argument and allow us to define control flow based pointcuts - the pointcuts that captures all the join points in the control flow of specified join point.

cflow() captures all the join points e.g. call, execution, set and get field, error handlers in the control flow of join point specified as argument along with the specified join point itself.
cflowbelow() behaves same as cflow, however it doesnt captues the join point specified as argument, but captures all other join points that comes below that.

For example, Consider a class -

public class Test{
private int value;

private void method(){
value = 10;
}

public static void main(String[] args) {
new Test().method();
System.out.println("Exiting Test.main()");
}
}

And an aspect -

public aspect ControlTestAspect {

pointcut controlPointcut() : cflow(call(void Test.method())) && !within(ControlTestAspect);

before():controlPointcut(){
System.out.println("In Control pointcut : " + thisJoinPoint);
}
}

This aspect contains pointcut that captures all join points that are triggered during the control flow of method method() of class Test. The output of running the Test class is -

In Control pointcut : call(void Test.method())
In Control pointcut : execution(void Test.method())
In Control pointcut : set(int Test.value)
Exiting Test.main()

As we can see, the before advice is called for all the join points that comes in the control flow of method() execution.

If we use cflowbelow in the pointcut instead of cflow, the output will be -

In Control pointcut : execution(void Test.method())
In Control pointcut : set(int Test.value)
Exiting Test.main()

i.e. it captures all the join points that are below the specified join point.


When defining pointcut using cflow or cflowbelow, we need to ensure that the pointcut does not capture the calls that are made from the same aspect, otherwise it will invoke recursive method calls and we will get StackOverflowError. This happens because cflow() will also capture the method calls from the aspect itself and try to apply the advice to it.
This can be avoided by using within() construct. within() takes a type(class or interface) name as argument and captures all join points the are defined in that type.

To capture all the join points in the control flow of method foo of class Test and excluding the calls from the TestAspect, we can define the pointcut as -

pointcut controlPointcut() : cflow(call(void Test.foo())) && !within(TestAspect);

This pointcut will exclude the join points that are triggered directly by TestAspect, however if the join points are invoked indirectly in the control flow of advice then those join points will still be captured. e.g. If the advice in TestAspect calls method shoo() of class Test2 and this method calls method foo of Test, then this join point will NOT be excluded by above pointcut and we will get StackOverflowError because of recursive advice invocation. This can be avoided by using adviceexecution() construct.

pointcut controlPointcut() : cflow(call(void Test.foo())) && !cflow(adviceexecution());

This pointcut will happily exclude all the join points that are triggered directly or indirectly by the aspect.


Lets look at the places where cflow and cflowbelow() can be used effectively.

# One obvious use of cflowbelow is to capture non-recursive call to method.
e.g. consider a class Foo -

public class Foo {

private void count(int value){
if(value==0) return;
System.out.println("Hi!");
count(--value);
}

public static void main(String[] args) {
new Foo().count(2);
new Foo().count(2);
}
}

If we want to capture only the non-recursive calls to the method count(), then we will need an aspect like this -

public aspect FooAspect {

pointcut countMethod() : call(void Foo.count(..));
pointcut nonRecursiveCountMethod() : countMethod() && !cflowbelow(countMethod()) ;

before() : nonRecursiveCountMethod() && !cflow(adviceexecution()) {
System.out.println("In nonRecursiveCountMethod pointcut : " + thisJoinPoint);
}
}

The output of running the Foo class is -

In nonRecursiveCountMethod pointcut : call(void Foo.count(int))
Hi!
Hi!
In nonRecursiveCountMethod pointcut : call(void Foo.count(int))
Hi!
Hi!

As we can see, only calls that are made outside of method count() are captured and recursive calls are ignored.


# cflow and cflowbelow are useful if want to perform operation only in specific part of your application (or if you want to restrict the operation in specific part). e.g. if you want to add security check on method call of some class, say Foo only if the method is called from methods in web tier classes (and their subclasses), then we can write pointcuts like -

pointcut methodCalls() : call(public void Foo.*(..)) ;
pointcut methodCallFromWebTier() : methodCalls() && cflow(call(* com.webtier.*+.*(..)));

Now, we can write advice for methodCallFromWebTier() pointcut to perform specific operation.

The pointcut can be easily modified using unary NOT (!) to do operation when method is called from anywhere other than the control flow of methods in web tier classes.

pointcut methodCallFromWebTier() : methodCalls() && !cflow(call(* com.webtier.*+.*(..)));

No comments: