Wednesday, December 15, 2004

Messaging with annotations

I'm working on a talk for SD West 2005 with a former coworker, Gregor Hohpe of ThoughtWorks. We coded a simple Java messaging system for single VM apps. Most messaging systems are based on messages segregated by subject, usually a String field. Our system though is type-based. Messages implement (or extend) Message, and receivers provide methods receive(Message) to receive published messages.

The dispatch loop makes extensive use of reflection to find a "best matching" method. If you have:

public class FooReceiver extends Receiver {
    public void receiver(final Message message) { }

    public void receiver(final FooMessage message) { }
}

Then if a FooMessage (or a subtype) comes along, the better matching method receives the message, otherwise the more general method does.

But I have found a better way: annotations :-) Consider:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MessageHandler {
    Class[] value();
}

This bit of annotation magic lets me say this in my receiver class:

public class FooReceiver {
    @MessageHandler({Message.class})
    void receiveAllTypes(final Message message) { }

    @MessageHandler({FooMessage.class})
    void receiveFooTypes(final FooMessage message) { }
}

Now if a FooMessage comes along, both methods see the message: logically, there is no method overloading to consider since they are differently-named methods. But this has several other advantages:

  • No need to extend or implement anything; just annotate the methods
  • Methods can have better names than just receive; it's the annotation, not the name, which counts
  • Interestingly, you can write methods to handle disjoint types:
public class DisjointReceiver {
    @MessageHandler({FooMessage.class, BarMessage.class})
    void handleEitherFooOrBarType(final Message message) { }
}

Nifty — there is no need for FooMessage or BarMessage to be related types. In fact, to carry the disjunction one step further, I could dispense entirely with the Message class! Hmmm...

UPDATE: I fixed the bad links. That's what I get for blogging while woozy with flu.

2 comments:

Anonymous said...

How then, are you doing registration? I would expect a registerInterest(MessageSelector, MessageHandler) around somewhere, but if there's no MessageHandler interface, is that second param simply Object? If so, what if the instance passed has no @MessageHandler() declarations?

This will freak out the RTT haters. I like it though.

Brian Oxley said...

My post doesn't show the whole picture. If you follow the link in the post's title (http://binkley.fastmail.fm/), you can download full sources in a Maven project where I have both reflection-based and annotation-based solutions.

There's a separate Channel class which subscribe, unsubscribe, and publish. My channel implementation does breadth-first delivery so that receivers and themselves publish more messages without causing the messages to be out of order: if you're published first, you're delivered first, and I queue up subsequent publishings.

It's part of what my talk partner, Gregor Hohpe, calls "programming without a call stack". Since Java lacks actual continuations, one must make do with reflection and other tools. :-)