Advanced AspectJ part III : non-kinded pointcuts

Today we will focus on non-kinded poincuts of AspectJ.

Unlike kinded pointcuts (method execution, field access, object initialization …), non-kinded pointcuts select join point on some criteria other than the join point signature (no kind). For example it is possible to select join points inside the source code(lexical structure) of a Java class or method.

I Case study

Below is a simple case study.

Let’s say you need to code an exception logger for your application. This is a cross-cuting concern so you naturally think about AspectJ and around() pointcut, good thing !

	...
	pointcut exceptionPointCut(): execution(* com.myapp..*(..)) 
	...

	Object around() : exceptionPointCut()
	{
		Object retValue = null;
		
		try
		{
			retValue = proceed();
		}
		catch(RuntimeException rte)
		{
			// Do something to process exception
		}
		
		return retValue;
	}

What is wrong in the above aspect is not the around() advice itself, which is perfectly valid and sensitive. No, what is wrong is the design of pointcut.

The problem with the exceptionPointCut() is that it is capturing far too many join points, more than necessary. But how can it be possible ? The pointcut restricts itself to execution of any method in the com.myapp package and all its sub-packages !

Yes, but this restriction is not enough. It means that all methods execution in your application will be avised by this pointcut, which is quite alot.

Ask yourself, do I really need to track exceptions when calling getters/setters for entities or DTO objects ? Do I really need to track exceptions in stateless helper classes with a high unit test coverage ?

The idea is to restrict further the method execution join points. For this you have some options:

  1. be more specific in the execution join point definition, by listing all classes you’ll like to monitor, it can be tedious: execution(* com.myapp.xxx.Class1(..)) || execution(* com.myapp.xxx.Class1(..))
  2.  

  3. be more specific by giving a method name pattern. This is the most efficient strategy but it is not always applicable and is very use case-dependent : execution(* com.myapp..do*(..))
  4.  

  5. rely on non-kinded pointcuts, discussed below

 

II Non-kinded poincuts

There are 3 families of non-kinded pointcuts:

  • control flow based: cflow([PoinCut]) & cflowbelow([PoinCut]) : select all poincuts in the execution flow of the [PointCut]. The semantic of this pointcut has been discussed here (Chapter I section C)
  •  

  • source code based: within([TypeSignature]) & withincode([MethodSignature]). within() will select all possible join points in the source code of the object(class, interface or aspect) specified by [TypeSignature]. withincode() selects all possible join points in the source code of the method defined by [MethodSignature]
  •  

  • target-object based: this([TypeSignature]) & target([TypeSignature]). this() selects all join points having this of type [TypeSignature] associated with them whereas target() selects all join points having target object of type [TypeSignature]

A within()

Examples are better than words, let’s look into the piece of code below:

public class DummyClass
{
	private String name = "default";
	private Date now;

	public void initialize()
	{
		this.name = this.getClass().getCanonicalName();
		this.now = new Date();
	}

	public String getData()
	{
		return this.name + this.now.toString();
	}
}

// Runner class
public class TrackDummyClass
{

	public static void main(String[] args)
	{
		DummyClass dummy = new DummyClass();

		dummy.initialize();

		dummy.getData();
	}
}

Now we define an aspect using within() construct:

public aspect WithinAspects
{
	// Matches all poincut in source code of com.techweb.test.DummyClass
	// static init, init, method execution & field access
	pointcut withinPoincut() : within(com.techweb.test.DummyClass);
	
	after() : withinPoincut()
	{
		System.out.println("Join Point : "+thisJoinPoint.toLongString());
	}
}

Running the code will give the following output:


// Init phase
Join Point : staticinitialization(static com.test.DummyClass.)
Join Point : preinitialization(public com.test.DummyClass())
Join Point : initialization(public com.test.DummyClass())

// Constructor call, line 24 in TrackDummyClass
Join Point : execution(public com.test.DummyClass())

// line 3 in DummyClass
Join Point : set(private java.lang.String com.test.DummyClass.name)

// line 26 in TrackDummyClass
Join Point : execution(public void com.test.DummyClass.initialize())

// line 8 in DummyClass
Join Point : call(public final native java.lang.Class java.lang.Object.getClass())
Join Point : call(public java.lang.String java.lang.Class.getCanonicalName())
Join Point : set(private java.lang.String com.test.DummyClass.name)

// line 9 in DummyClass
Join Point : call(public java.util.Date())
Join Point : set(private java.util.Date com.test.DummyClass.now)

// line 28 in TrackDummyClass
Join Point : execution(public java.lang.String com.test.DummyClass.getData())

// line 14 in DummyClass
Join Point : get(private java.lang.String com.test.DummyClass.name)
Join Point : call(public static java.lang.String
             java.lang.String.valueOf(java.lang.Object))
Join Point : call(public java.lang.StringBuilder(java.lang.String))
Join Point : get(private java.util.Date com.test.DummyClass.now)
Join Point : call(public java.lang.String java.util.Date.toString())
Join Point : call(public java.lang.StringBuilder
             java.lang.StringBuilder.append(java.lang.String))
Join Point : call(public java.lang.String java.lang.StringBuilder.toString())

withincode() is similar to within() except that it only applies to a method source code, not a type.
 

B this()

New let’s change within(com.techweb.test.DummyClass) by this(com.techweb.test.DummyClass) and look at the new output:


// Init phase
Join Point : initialization(public com.test.DummyClass())

// Constructor call, line 24 in TrackDummyClass
Join Point : execution(public com.test.DummyClass())

// line 3 in DummyClass
Join Point : set(private java.lang.String com.test.DummyClass.name)

// line 26 in TrackDummyClass
Join Point : execution(public void com.test.DummyClass.initialize())

// line 8 in DummyClass
Join Point : call(public final native java.lang.Class java.lang.Object.getClass())
Join Point : call(public java.lang.String java.lang.Class.getCanonicalName())
Join Point : set(private java.lang.String com.test.DummyClass.name)

// line 9 in DummyClass
Join Point : call(public java.util.Date())
Join Point : set(private java.util.Date com.test.DummyClass.now)

// line 28 in TrackDummyClass
Join Point : execution(public java.lang.String com.test.DummyClass.getData())

// line 14 in DummyClass
Join Point : get(private java.lang.String com.test.DummyClass.name)
Join Point : call(public static java.lang.String
             java.lang.String.valueOf(java.lang.Object))
Join Point : call(public java.lang.StringBuilder(java.lang.String))
Join Point : get(private java.util.Date com.test.DummyClass.now)
Join Point : call(public java.lang.String java.util.Date.toString())
Join Point : call(public java.lang.StringBuilder
             java.lang.StringBuilder.append(java.lang.String))
Join Point : call(public java.lang.String java.lang.StringBuilder.toString())

We can see that the output is almost identical to the one of within() except for the static initialization part, which is perfectly sensible.
 

C target()

Last experiment, let’s turn this(com.techweb.test.DummyClass) into target(com.techweb.test.DummyClass) and see how it modifies the output:


// Init phase
Join Point : initialization(public com.techweb.test.DummyClass())

// Constructor call, line 24 in TrackDummyClass
Join Point : execution(public com.techweb.test.DummyClass())

// line 3 in DummyClass
Join Point : set(private java.lang.String com.techweb.test.DummyClass.name)

// line 26 in TrackDummyClass
Join Point : call(public void com.techweb.test.DummyClass.initialize())
Join Point : execution(public void com.techweb.test.DummyClass.initialize())

// line 8 in DummyClass
Join Point : call(public final native java.lang.Class java.lang.Object.getClass())
Join Point : set(private java.lang.String com.techweb.test.DummyClass.name)

// line 9 in DummyClass
Join Point : set(private java.util.Date com.techweb.test.DummyClass.now)

// line 28 in TrackDummyClass
Join Point : call(public java.lang.String com.techweb.test.DummyClass.getData())

// line 14 in DummyClass
Join Point : execution(public java.lang.String com.techweb.test.DummyClass.getData())
Join Point : get(private java.lang.String com.techweb.test.DummyClass.name)
Join Point : get(private java.util.Date com.techweb.test.DummyClass.now)

The produced output is quite interesting to analyze.


// line 26 in TrackDummyClass : dummy.initialize();
this() version : execution(public void com.techweb.test.DummyClass.initialize())

target() version : call(public void com.techweb.test.DummyClass.initialize())
target() version : execution(public void com.techweb.test.DummyClass.initialize())

With the target() construct, we capture not only the method call on the DummyClass but also the method execution.


// line 8 in DummyClass : this.name = this.getClass().getCanonicalName();
this() version : call(public final native java.lang.Class java.lang.Object.getClass())
this() version : call(public java.lang.String java.lang.Class.getCanonicalName())
this() version : set(private java.lang.String com.test.DummyClass.name)

target() version : call(public final native java.lang.Class java.lang.Object.getClass())
target() version : set(private java.lang.String com.techweb.test.DummyClass.name)

This time, with the this() construct, we can capture the call to java.lang.Class.getCanonicalName(). The target() construct cannot capture this call because indeed, the target object for the method call is the class java.lang.Class and not DummyClass.
 

III Back to case study

Now that we’ve explained the non-kinded poincuts, let’s see how they can be applied to our exception tracker use case.

To restrict further matching join points, we could use the within() construct to only select join points inside particular packages:

	...
	pointcut exceptionPointCut(): execution(* com.myapp..*(..)) && (
		within(com.myapp.service..*)
		|| within(com.myapp.web..*)
		|| within(com.myapp.dao..*)
		);
	...

The above poincut selects:

  • all method executions in the package com.myapp and its sub-package
  • restricted to the packages com.myapp.service or com.myapp.web or com.myapp.dao

In a nutshell, we restrict the exception tracking to all method execution in service, controller or dao layer.

Of course it is possible to define finer filtering based on method name. This example just show the convenience of non-kinded pointcuts.

1 Comment

  1. Alex

    Hey man, great synopsis!

    Reply

Leave a Reply to Alex Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.