Asynchronous Java Calls, Part 2
In my previous blog, I presented a framework that almost transparently turns Java method calls into asynchronous calls, much like JavaScript code works in the Ajax paradigm, or that of Node.js. It accomplishes all of this with Java Reflection, and JMS as the asynchronous transport. In summary, Reflection is used to transform method calls into messages that are sent asynchronously. In this blog, we'll explore the JMS portion of the framework, and the helper class that makes it easy, JMSHelper.java.
Asynchronous Objects
To support asynchronous method calls for any object, simply extend the abstract base class, AsyncObject.java. In that class's constructor, a call is made to the method JMSHelper.listen(), with the object's this pointer passed as a parameter. Before this method is invoked, however, the JMS machinery is put in place in the JMSHelper constructor:
public JMSHelper() {
try {
// Setup JMS stuff here
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.allure.JetStream.jndi.JetStreamInitialContextFactory");
ctx = new InitialContext(env);
ConnectionFactory factory = (ConnectionFactory)ctx.lookup("ConnectionFactory");
connection = factory.createConnection();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
connection.start();
}
catch ( Exception e ) { }
}
JNDI is used to create a JMS ConnectionFactory object, from which a Connection is established with the JMS provider. A call to start() is all it takes to get the connection ready to send and receive messages.
The JMSHelper.listen() method uses reflection to discover all of the interfaces the object implements, and then creates and listens on a queue per interface. The full package name of each interface is used to avoid name collisions. Here is the code:
public void listen(Object listener) {
try {
// Use the class's interfaces, and create a queue per interface
Class cls = listener.getClass();
Class[] interfaces = cls.getInterfaces();
for ( Class inface: interfaces ) {
String queueName = inface.getName();
Queue queue = null;
try {
queue = (Queue)ctx.lookup(queueName);
}
catch (Exception e) { }
if ( queue == null )
queue = session.createQueue( queueName );
MessageConsumer receiver = session.createConsumer(queue);
receiver.setMessageListener((MessageListener)listener);
}
}
catch ( Exception e ) { }
}
Since we covered the Reflection code in the previous blog, let's focus on the JMS parts now. After retrieving the array of interfaces from the Java runtime, a JMS Queue is looked for (to see if it already exists) or created for each interface, along with a JMS MessageConsumer to receive messages for the specific queue. A reference to the AsyncObject base class, which implements the JMS MessageListener interface, is passed as the message listener. When messages arrive for each queue, the appropriate Java object's onMessage() is called with the message itself. Since this is implemented in the abstract based class, AsyncObject, there's no need to write this code in your own objects.
The onMessage() method converts the JMS message to a method call, which was examined in detail on the previous blog.
Sending Return Values
When a message is received from the caller, JMSHelper wraps the call into a JMS Message object, using a JMS TemporaryQueue as the "Reply To" destination. This queue has no real name, and is only associated with the delivered message to serve as a direct path back to the sender, implementing the request-reply pattern. This temporary queue is created, a listener is provided, and it then is set as the replyTo field for the message as shown here:
Queue replyTo = session.createTemporaryQueue();
MessageConsumer subscriber = session.createConsumer(replyTo);
ReplyListner replyListener = new ReplyListner(managerName, callback);
subscriber.setMessageListener(replyListener);
Message msg = session.createTextMessage("ManagerMessage");
msg.setObjectProperty("params", params);
msg.setJMSReplyTo(replyTo);
Recall that all JMS messages in the AsyncJava package contain the method name and parameters in an array, which is placed into the message as the object property, "params". The replyTo TemporaryQueue is created, with the nested listener class ReplyListener, which routes the return message (with the embedded return value) back to the original caller via the method onReturn() on the caller's AsyncCallback interface implementation.
Conclusion
The combination of two powerful features of the Java platform, Reflection and JMS, makes some very useful design patterns and paradigms possible. Here, I've shown how Reflection can be used to abstract Java method calls and turn them into asynchronous calls to more easily perform parallel processing without the need for complicated multi-threaded code. JMS abstracts the delivery and transport used to perform the actual asynchronous message delivery. And thanks to the built-in features and support in both, this implementation works within one JVM, or across multiple JVMs on a LAN or WAN.
Happy Coding!
-EJB

