/mar 15, 2015

Dynamic message passing with Java

By Jason Nichols

Imagine this scenario, you've stepped outside of Spring MVC and are reading messages from a different source (either a websocket, MQ, or vanilla TCP socket). No longer can you rely on Spring to automatically route incoming messages to the correct handler method. This is what less battle tested developers will toss down into their IDE:

private static final ObjectMapper MAPPER = new ObjectMapper();

public void handleMessage(byte[] message) {
  // How do I tell what type of message this is?
}

And at that point you've got to wonder if there's an easier way. Not only do you want to figure out what type of message you've got without having to read bytes manually, but you also need to figure out how to send each message to the code to deal with specific message types. Let's start with the first issue.

Deserializing to the correct object

If you happen to be using Jackson for message serialization you're in luck, although other libraries will offer solutions as well. Suppose you have a well-defined class hierarchy for your messages as such:

public abstract class AbstractMessage {

  private String messageId;

  // Imagine getter and setter methods for messageId
}

Then assume each message we send over the wire is a descendent of that base class

public class HeartbeatMessage extends AbstractMessage {
  // Yadda yadda yadda
}
public class ShutdownMessage extends AbstractMessage {
  // More yadda yadda yadda
}

With that sane message class structure, let's enable Jackson to automatically determine what object type to deserialize a field to based on extra metadata inserted into the serialized JSON text. It's actually insanely easy and can be done in one line:

@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")

Add that to the top of each of your message classes, and each time Jackson serializes an object it will automatically embed a property named "@class" into the serialized text. During deserialization Jackson will read that property and automatically deserialize into the correct object type.

Now we can deserialize!

private static final ObjectMapper MAPPER = new ObjectMapper();

public void handleMessage(byte[] message) throws IOException, JsonParseException, JsonMappingException
{
  AbstractMessage msg = MAPPER.readValue(message, AbstractMessage.class);
}

Dynamic Handling

Notice that even with the new annotation and metadata, the most specific we can deserialize to is an AbstractMessage. This is Java's static typing at play. If stopped here in a debugger you'd notice that the specific object type is in fact resolved at runtime. A naive routing handler would look like this:


if (msg instanceof HeartbeatMessage) {
  handleHeartbeat((HeartbeatMessage)msg);
} else if (msg instanceof ShutdownMessage) {
  handleShutdown((ShutdownMessage)msg);
}

This works ok with two message types, but what about 10, 20, or 50? Let's find some way to route messages without resorting to this. As it happens, Google's Guava library has an EventBus that will do the heavy lifting for us! I won't go into details about the EventBus here, but check out the Guava tutorials.

All that's needed is to add an EventBus to our handling class and annotate a few methods:

public class MessageHandler {
  private static final ObjectMapper MAPPER = new ObjectMapper();

  private final EventBus eventBus = new EventBus();

  public init() {
    eventBus.register(this);
  }

  public void handleMessage(byte[] message) throws IOException, JsonParseException, JsonMappingException
  {
    AbstractMessage msg = MAPPER.readValue(message, AbstractMessage.class);
    eventBus.post(msg);
  }

  @Subscribe
  public void handleHeartBeat(HeartbeatMessage msg) {
    // Handle the heartbeat specific message here
  }

  @Subscribe
  public void handleShutdown(ShutdownMessage msg) {
    // Handle the shutdown specific message here.
  }
}

On line 13, we post the deserialized message to the EventBus. Even though we don't know the specific type the bus will resolve it and look for any handlers that specify the correct type. The annotations on line 16 and 21 tell EventBus which methods are available for injecting messages into.

Lastly, I threw in an init method, but you can register the handling object with the EventBus from anywhere.

With the above code, we've removed the need to manually resolve message types and made the routing happen behind the scenes. Enjoy not debugging and maintaining the code you didn't have to write ;)

Happy coding!

Jason

Related Posts

By Jason Nichols