Wednesday, February 7, 2007

Java 5 Annotations and You

I was able to introduce Java 5 annotations to a few people at work today and I think I hit a cord. I've always had a feeling that most people don't really understand the point of annotations, I know it look me a while. One of the big uses of annotations is try remove the restriction on defining your structures based on interface implementation.

Current Practice:

A good example is the swing api. Lets say you have a JButton and you want to receive events when that button is pressed. Current practice is to write an Class that implements EventListener and register your class with the JButton. You then will receive Event objects and you can inspect them and figure out what event occurred and if they occurred on the button you are interested. What happens is now in every EventListener you write you are now duplicating the code to inspect events, and route to the specific business logic.

Throw in Reflection:

To me this is weird, Java is one of the few languages that has inspection and reflection however it is still common practice to force concrete implementation on the users of our apis. What would make more sense is to tell a JButton "When this particular action occurs, call this method on this object." Your controllers then become POJOs with the methods containing specific segments of business logic.

One Step further with Annotations:

Now lets take that a step further and add the concept that I just want to give JButton my object and JButton will figure out what methods to call for what events and when, but still not requiring my controller to implement an interface. This can be done with annotations. Rather than giving the users of my API a set of interfaces they have to extend, I can just give them a set of annotations they can use to annotation their classes and the API will do the rest behind the scenes.

Example:
Lets take this annotation.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

enum EventType {BUTTON_PUSHED,MOUSE_OVER}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EventCallback {

EventType value();

}


Here I create an annotation that can decorate a method and takes an enumerated value of either BUTTON_PUSHED or MOUSE_OVER (example actions that could occur on a Button).

Now, lets say I'm writing my own GUI library, I can write a Button class to inspect any objects registering themselves for this annotation and remember them. In my Button class the registerActionListener method looks like this now.


public void registerActionListener(Object object)
{
Method[] methods = object.getClass().getMethods();
for (int i=0;i < methods.length;i++)
{
if ( methods[i].isAnnotationPresent(EventCallback.class))
{
EventCallback annotation = methods[i].getAnnotation(EventCallback.class);
EventType eventType = annotation.value();
if ( eventType == EventType.BUTTON_PUSHED)
{
// remember to call this method on this object if the button is pushed
}
else if ( eventType == EventType.MOUSE_OVER)
{
// remember to call this method on this object if the the mouse if over
}
}
}


}


Here is where you will really see the advantage. Our Event Listener (or controller) now looks very clean and simple.


public class MyController {

MyButton button = new MyButton();

public MyController()
{
button.registerActionListener(this);
}

@EventCallback(EventType.BUTTON_PUSHED)
public void myButtonWasPressed()
{
System.out.println("My Button Was Pushed");
}

@EventCallback(EventType.MOUSE_OVER)
public void aMouseIsOverMyButton()
{
System.out.println("A mouse if over my button");
}

}

Notice that I can now name my callback methods whatever I want. Also, if I'm not interested in a event type, I don't have to even annotation a method for it. This means I get the methods I want called for only the events I'm interested in.

I really think this is the way all Java APIs are going and for a good thing.

No comments: