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