6

I'm really rolling up my sleeves and trying to understand Java annotations for the first time, and have read the Sun, Oracle and Wikipedia articles on the subject. They're easy to understand conceptually, but am finding it difficult putting all the pieces of the puzzle together.

The following example is probably terrible engineering, but just humor me (it's an example!).

Let's say I have the following class:

public Widget
{
    // ...

    public void foo(int cmd)
    {
        switch(cmd)
        {
        case 1:
            function1();
            break;
        case 2:
            function2();
            break;
        case 3:
        default:
            function3();
            break;
        }
    }
}

Now, somewhere else in my project, I have another class, SpaceShuttle, that has a method called blastOff():

public class SpaceShuttle
{
    // ...

    public void blastOff()
    {
        // ...
    }
}

Now then, I want to configure an annotation called Widgetize so that any methods annotated with @Widgetize will have Widget::foo(int) invoked prior to their own call.

@interface Widgetize
{
    int cmd() default 2;
}

So now let's revisit SpaceShuttle:

public class SpaceShuttle
{
    // ...

    @Widgetize(3)
    public void blastOff()
    {
        // Since we pass a cmd of "3" to @Widgetize,
        // Widget::function3() should be invoked, per
        // Widget::foo()'s definition.
    }
}

Alas, my questions!

  1. I assume that somewhere I need to define an annotation processor; a Java class that will specify what to do when @Widgetize(int) annotations are encountered, yes? Or does this happen in, say, XML config files that get fed into apt (like the way ant reads build.xml files)?

  2. Edit: If I was correct about these annotation processors in question #1 above, then how do I "map"/"register"/make known these processors to the apt?

  3. In buildscripts, is apt typically ran before javac, so that annotation-based changes or code generation takes place prior to the compile? (This is a best practices-type question).

Thanks and I apologize for my code samples, they turned out a lot bulkier than I intended them to (!)

1
  • For the record, your question is very clear. I think the problem you're going to run into once you've got apt configured (and the reason I suggested AOP) is this. It is easy to generate code. However, it is considerably more difficult to actually modify code. The tricky part here is going to be modifying the code that calls ''spaceShuttle.blastOff()'' to actually call the correct proxy method. Commented Jan 28, 2011 at 15:24

5 Answers 5

5

This sounds more like AOP (Aspect oriented programming) than annotations. The topics are often confused since AOP uses annotations to achieve it's goals. Rather than reinvent AOP from scratch, I would recommend looking up and existing AOP library such as AspectJ.

However, to answer your specific question, there are two possible approaches to achieve your goal.

Runtime Approach

This is the approach typically taken by container frameworks (like Spring). The way it works is that instead of instantiating your classes yourself, you ask a container for an instance of your class.

The container has logic to examine the class for any RuntimeAnnotations (like @Widgetize). The container will then dynamically create a proxy of your class that calls the correct Widgetize method first and then calls the target method.

The container will then return that proxy to the original requester. The requester will still thing he got the class (or interface) that he asked for and be completely unaware of the proxying behavior added by the container.

This is also the behavior used by AspectJ.

Enhancement Approach

This is the approach taken by AspectJ. To be honest, I don't know a lot of the details of how it works. Somehow, AspectJ will scan your class files (the byte code), figure out where the annotations are, and then modify the byte code itself to call the proxy class instead of the actual class.

The benefit of this approach is that you don't need to use a container. The drawback is that you now have to do this enhancement step after you compile your code.

Sign up to request clarification or add additional context in comments.

4 Comments

I agree, AOP sounds like the better approach (+1)
& @Sean - what about APT wrappers like apt-jelly? These seem to do all the hard work that I am talking about here for you by providing a super-simple API. Thoughts?
Take a look at my comment to your original question. Generating the proxies is the easy part. The hard part is modifying the existing code to call the generated code and not the existing code. apt-jelly is only for artifact generation.
AspectJ also support load-time-weaving, which is a third approach to AOP. Instead of controlling the instance creation (proxy approach) or modifying compiled classes (enhancement approach), load-time-weaving uses a JVM agent to modify the classes while they are being loaded into JVM.
2

I assume that somewhere I need to define an annotation processor; a Java class that will specify what to do when @Widgetize(int) annotations are encountered, yes? Or does this happen in, say, XML config files that get fed into apt (like the way ant reads build.xml files)?

In Java 1.6, the standard way to define annotation processors its through the ServiceLoader SPI.

In buildscripts, is apt typically ran before javac, so that annotation-based changes or code generation takes place prior to the compile? (This is a best practices-type question).

APT must take place before compilation, as it operates on source files (actually on syntax trees).

Comments

1

I use method interceptors often with Hibernate. Hibernate requires that a transaction be started and committed round every query. Rather than have lots of duplicate code I intercept every Hibernate method and start the transaction in the interceptor.

I use AOP Alliance method interceptors in conjunction with Google Guice for this. Using these you use your Widgetise annotation and then use Guice to say where you see this annotation use this method interceptor. The following Guice snippet does this.

        bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), new TransactionalInterceptor);

The interceptor catches the method, you can then call foo and then the tell the method interceptor to proceed with invocation of the original method. For example (in a simplified form):

public class Interceptor implements MethodInterceptor {

    //PUT ANY DEPENDENCIES HERE IN A CONSTRUCTOR

    public Object invoke(MethodInvocation invocation) throws Throwable {

            //DO FOO HERE

            result = invocation.proceed();
        return result;
    }

}

This might be a little confusing and it took me a while to get my head around but it is quite simple once you understand it.

Comments

0

It seems that the basis of your confusion is an incorrect belief that Annotations are something more than just Metadata. Check out this page from the JSE 1.5 language guide. Here is a relevant snippet:

Annotations do not directly affect program semantics, but they do affect the way programs are treated by tools and libraries, which can in turn affect the semantics of the running program. Annotations can be read from source files, class files, or reflectively at run time.

In your code

    @Widgetize(3)
    public void blastOff()

does not cause Widget::function3() to execute (asside: in java we reference a member as Widget.function3()) The annotation is just the equivalent of a machine-readable comment (i.e. metadata).

2 Comments

DwB - thanks for the input. I do understand that annotations aren't supposed to alter semantics directly, my confusion rely lies in the following: you want a specific method (Widget.foo()) to be called whenever you encounter a method annotated with @Widgetize. Obviously you need some way to bind these two constructs together (the annotation and its "trigger"/interceptor method). I assume this is performed in something called an annotation processor, yes?
@Pam Honestly, I use annotations, but I dont write them. The answer to "you want a specific method ... to be called whenever ... @Widgetize" lies in the Aspect Oriented (AOP) space. Certainly, you will mark the method with an annotation, but the actual "call blah when blah" will result from AOP stuff (see Pace's answer). APT is nice for generating files based on Annotations, but it is not for AOP stuff (I believe).
0

Annotations can be processed both at compile time and at runtime. To process them at runtime requires using the reflection API, which will have a performance impact if it's not done smartly.

It's also possible to process annotations at compile time. The goal is to generate a class which can then be used by your code. There is a specific interface annotation processors have to satisfy, and they have to be declared in order to be executed.

Take a look at the following article for an example which is structurally simple to your use case:

Annotation processing 101

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.