Tuesday, December 18, 2007

Static crosscutting with AspectJ

Crosscutting concerns are the features of the system that are applicable across the system. They need to be applied in the system-wide manner in multiple modules. Examples of the typical crosscutting concerns in typical system are authentication, security, error handling, logging etc.
Crosscutting concerns can be addressed in two ways - Dynamic and static. Dynamic crosscutting modifies the runtime behaviour of system and static crosscutting helps to change the static stracture of program. Here, I will only discuss about static crosscutting, and will blog about dynamic crosscutting seperately.

Lets look at way in which static crosscutting can be applied.

## One of the feature of Ruby is that all the classes in Ruby are open. That means you can add new members to the existing class. Its a native feature of Ruby language. However, same feature can be achieved in Java with AspectJ. By using aspects, you can add new members to the existing class or interface. We can add new field, method, constructor into existing class. This is called as static member introduction.

e.g. To add new members in the class Foo, we will write as -

public aspect FooAspect {
public float Foo._newField = 9.0;

public void Foo.newMethod() {
System.out.println("Crosscutting - The static way !");
System.out.println("Value of field : " + _newField);
}
}

These new members will be available to use when we weave(compile) this aspect with Java class Foo.

New methods can be added in interface with implementation, you can not add just methods signature ending in semicolon. This implementation acts as default for all implementing classes. If we want to modified method for some class, we need to define that method either in aspect or directly in our class.

The following aspect adds a new method in FooInterface interface.

public aspect FooInterfaceAspect {

public void FooInterface.blue(){
System.out.println("In new method from Aspect");
}
}


## We can change the class hierarchy with declare parent clause. We can add new parent to the class or add an interface to the class or set of classes.

e.g. To implement FooInterface for Foo class -

public aspect FooInterfaceAspect {
declare parents : Foo implements FooInterface;
}

Following piece of code implements the interface to group of classes and implements the method in individual classes.

public aspect MyAspect {
declare parents : com.webservices.messages.* implements SomeInterface;
public Object ClassOne.method() {
// do something
}
public Object ClassTwo.method() {
// do something else
}
}

Instead of writing separate method for each class, We can also write a single method which will be used by all classes. This can be done by defining the method on interface rather than on class.

public aspect MyAspect {
declare parents : com.webservices.messages.* implements SomeInterface;
public Object SomeInterface.method() {
// do something
}
}


This feature along with member introduction can be used intelligently to achieve modular solutions when using with third party API. An excellant example of this is given in article AOP banishes the tight-coupling blues.

Similar to class, We can also extend an interface.

public aspect FooInterfaceAspect {
declare parents : FooInterface extends ShooInterface;
}


## We can give out compilation errors or warnings by using -

declare error : pointcut : message;
declare warning : pointcut : message;

For Example, following aspect gives compile error when method method() of Foo class is used and gives warning when anotherMethod() is used -

public aspect FooAspect {
declare error : call(void Foo.method()) : "No way man !";
declare warning : call(void Foo.anotherMethod()) : "You shouldn't do this !";
}


We can use this to give out compilation errors or warnings when some deprecated or unsupported methods are called in code. Or we can use this to recomment usage for methods.
This is useful when we have to use third party code or API and we want to regulate the usage pattern.


## We can use AspectJ to soften the checked exceptions.
There are two types of exceptions in Java. For Checked exceptions, the caller of the method which throws exception need to handle it explicitly by using try-catch or by re-throwing it. And other is Unchecked exceptions which need not be handled explicitly. They are subclasses of RuntimeException or Error.

This piece of code softens i.e. converts the checked exception into unchecked exception. So that the caller of the method do not need to handle it.

public aspect FooAspect {
declare soft : java.rmi.RemoteException : call(void Foo.method());
}

Now the caller of method method() do not need to handle the exception explicitly. This is achieved by adding try-catch block around the method call in code during weaving process. In the catch block, actual exception is wrapped in org.aspectj.lang.SoftException and throw again.SoftException is a subclass of RumtimeException.


## We can define the precedence of advice with the declare precedence clause.
e.g.
declare precedence : LoggingAspect, ErrorCheckAspect;

Only concrete aspects are meaningful in the list of aspects. Having abstract aspects in the list has no effect. However, we can use wildcard characters. e.g. If Logging and ErrorCheck are two abstract aspects, you can give precedence to aspects that extend logging over the aspects that extend Security as-
declare precedence : Logging+, ErrorCheck+;

The following declaration gives precedence to all the aspects that have 'Logging' in their name.
declare precedence : *Logging*, *Error*;

Having a same aspect more than once in the list,which makes cyclic precedence, is not allowed and it gives error. e.g.
declare precedence : LoggingAspect, ErrorAspect, LoggingAspect; // Error !

However,

declare precedence : LoggingAspect, ErrorAspect;
declare precedence : ErrorAspect, LoggingAspect;

is allowed as long as the pointcuts in two aspects are not matching or overlapping. If any pointcut in two aspects are same then we get an error saying "conflicting declare precedence orderings for aspects".


Finally, one last point. The pointcuts that are used in static crosscutting has a limitation. All the static crosscutting pointcuts should be such that they can de determined at compile time. So pointcuts can not be determined in terms of cflow, this, if, target, args.

No comments: