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:
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.
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. :-)
Post a Comment