Sunday, September 18, 2011

Intercepting Inherited Annotations with AspectJ

There seems to be a documented limitation in AspectJ that limits the ability to create pointcuts based on the inherited annotations of a class from an interface. What is meant by the inherited annotation is the following:

Let's say you have the following annotation in your project:
@Retention(RetentionPolicy.RUNTIME)
public @interface ImportantMethod { }
Also, you have the following interface with one method marked as "important":
public interface SimpleInterface {

 @ImportantMethod()
 void importantMethod(int a);
 
 void someOtherMethod();
}
Finally, you have a class that implements this interface:
public class SimpleClass implements SimpleInterface {

 public void importantMethod(int a) { }

 public void someOtherMethod() { }
}

The method "void importantMethod(int)" has the "@ImportantAnnotation()" annotation at the "SimpleInterface" level; however this annotation is not inherited in the "SimpleClass" from AspectJ perspective. That means, it is NOT possible to intercept calls to the methods that have the annotation "@ImportantMethod()".
SimpleInterface simpleInterface = new SimpleClass();
simpleInterface.importantMethod();
For the code above, the following pointcut will NOT work:
pointcut importantMethodIsExecuted(): 
                       execution(@ImportantMethod() * SimpleInterface+.*(..));

This pointcut will not be able to intercept the call made to the "importantMethod()" method. The reason is simple and it is well-documented in the AspectJ 5 documentation (http://www.eclipse.org/aspectj/doc/released/adk15notebook/annotations-pointcuts-and-advice.html, go to section "Annotation Inheritance and pointcut matching"). The only workaround is using the annotation again at the class level; that means re-marking the method with the "@ImportantMethod()" annotation in the "SimpleClass" class:

public class SimpleClass implements SimpleInterface {

 @ImportantMethod() // annotation is used here again!
 public void importantMethod(int a) {
 }

 public void someOtherMethod() { }
}

This is a bit disappointing as it defeats the purpose of using an interface; if each implementation has to contain the annotation, then placing the annotation at the interface level becomes useless.

If you have a relatively large codebase, where interfaces enforce the high-level business logic and classes contain the low-level implementations (see Uncle Bob Martin's Stable Dependencies Principle [SDP] and Stable Abstractions Principle [SAP]), then this small annotation limitation may actually become an issue. Actually, the limitation has already been brought up by several people; the relevant threads can be found here, here, and here.

For this problem, I have written a very small method/library that offers a workaround. Here is how it works:

// Intercept calls to the method(s) of the SimpleInterface that have the 
// @ImportantMethod annotation
pointcut importantMethodIsExecuted(): execution(* * SimpleInterface+.*(..)) &&
 if(implementsAnnotation("@ImportantMethod()", thisJoinPoint));

// ALTERNATIVELY

// Intercept any call within the "application" package that targets an implemented 
// interface method with the @ImportantMethod annotation
pointcut importantMethodIsExecuted(): execution(public * application.*.*(..)) &&
 if(implementsAnnotation("@ImportantMethod()", thisJoinPoint));

The library offers the "boolean implementsAnnotation(String,JoinPoint)" method that performs the annotation check. The algorithm within the method works as follows:

1. From the join point object, get the target class. (It would be SimpleClass in our example).
2. Collect the methods of all the interfaces that either this class or its superclasses implement. (importantMethod(int) && someOtherMethod() )
3. Compare the currently executing method with the collected methods from last step. (importantMethod(int) is being executed).
4. Check to see if this method has the specified annotation. (importantMethod(int) does have the "@ImportantMethod" annotation at the interface level).

(Needless to say, performance isn't the primary concern of this method/library.)

You may check out and download the whole code from here (https://github.com/5fcgdaeb/AspectJ-Annotations-Enhanced).

This small method/library could be improved in many different ways. Here is an idea for the interested:

- Currently, the boolean implementsAnnotation(String,JoinPoint) method expects the passed-in annotation to be in the exact format that is specified in the interface. For example, if the interface method contains the following annotation:
@ImportantMethod(priority=4,transactional=true)
then the method should be invoked as:
if(implementsAnnotation("@ImportantMethod(priority=4,transactional=true)", thisJoinPoint)).
This could be improved by allowing more flexible searches:
if(implementsAnnotation("@ImportantMethod(*,transactional=true)", thisJoinPoint)). Basically, the user should be able to provide criteria based on the values of the annotation.

4 comments:

  1. Hi, very nice enhancement.
    I found a little bug though:
    in MethodAnnotationChecker::extractAnnotationNameFromMethod(), you do:
    upToAnnotationName.substring(upToAnnotationName.indexOf(".") + 1);

    This should be upToAnnotationName.substring(upToAnnotationName.lastIndexOf(".") + 1);

    That way packages like com.foo.bar will be cut correctly from the annotation.

    ReplyDelete
  2. Hello Erik,

    This is what happens when you don't write tests! Thanks for the notice, the bug has been fixed in the repository.

    ReplyDelete
  3. An important focus for web search engine companies.

    Also consider how the Web Search Engine India Services comes up with solutions
    for your website slowly and steadily.

    Also visit my web-site: search engine marketing agencies

    ReplyDelete
  4. Placing the stormchase.net Station in CanadaAfter receiving much damage from a mid-Atlantic storm, the U-537 was found and attacked unsuccessfully at least three times by Royal Canadian Air Force Catalina and Hudson bombers.

    The Cryoscope is the handiwork of industrial design student Robb Godshaw, and it's possible a piece of string. There are lots of stormchase.net websites out there to see. They grow longer, thicker coats that insulate them against the cold, and temperamental stormchase.net patterns.

    Also visit my weblog :: storm chasing live

    ReplyDelete