Asynchronous Java Calls, Part 1

After doing some work with JavaScript and DWR for Ajax and reverse-Ajax communication, I began to enjoy the asynchronous call paradigm involved. This is similar to how JavaScript frameworks such as Node.js work, where everything is event-driven and asynchronous. This leads to code that scales well without the complication of writing multi-threaded code. Admittedly, it was an adjustment at first, but once used to it, I liked the result: fast, responsive UIs with a very clean and consistent programming model. I wondered how easily and transparently this could be achieved with Java.

The goal I set was to write plain-old Java code that appeared to make method calls on Java objects, but have them be intercepted and translated to an asynchronous message-based implementation. I wanted this to be mostly transparent to both sides: the code calling the object, as well as the object implementation itself. Thanks to the richness of Java and its libraries, the solution almost fell into place: Use Reflection to create proxy objects to the objects to be called, where the method and parameters are placed on Java Message Service (JMS) queues. The objects to be used asynchronously would simply extend a base class, which would use reflection to listen on the appropriate queue ( based on the class’s interface package names).

In the end, I achieved this goal, and the implementation remains mostly transparent. Here’s a summary:

  • For the caller, you use a helper class to get a proxy to the object whose interface name you provide. The helper class creates the queue, and the proxy object packages up the method calls and parameters on the queue, and routes the return value back (if any) to the caller over the queue as well.

  • For the object to be called, just extend a base class. The constructor for the base class uses reflection to get the object’s implemented interfaces, creates a queue for each one with a name that matches the interface package, and listens on the queues. When messages arrive, the appropriate method is called with the parameters received.

One caveat: This implementation does require a JMS provider that lets you create queues on the fly, without having them be predefined. In the next blog, we’ll examine one that supports this, and even works across the network without any configuration. For now, let’s take a closer look at the implementation.

The Caller’s Side: AsyncHelper.java

If you want to invoke methods on objects asynchronously, your application code must use the helper class, AsyncHelper.java, which has two static methods: getProxy(), which uses Reflection to create a proxy object for the real object you want to use; and call(), which packages the method name and parameters into an array and sends the request over JMS.

First, your application calls AsyncHelper.getProxy() with the name of the interface you want to call, and a callback object to receive the return value:


class MyApp implements AsyncCallback {
  …
  public void someMethod() {
    MyObject myObj = (MyObject)AsyncHelper.getProxy("mypackage.MyObject", this);
    …
  }

  public void onReturn(…) { … }
}

The application can then invoke methods on the proxy object as though it were the real object, but since the calls are made asynchronously, you need to provide a callback to receive the return value. The AsyncCallback interface defines this callback method. The getProxy() method is very straightforward:

    public static Object getProxy(String interfacePackage, 
                                                        AsyncCallback callback) {
        try {
            Class c = Class.forName(interfacePackage);
            InvocationHandler handler = 
                    new AsyncInvocationHandler(interfacePackage, callback);
            Object prxy = Proxy.newProxyInstance( 
                    c.getClassLoader(), 
                    new Class[] { c }, 
                    handler );

            return prxy;
        }
        catch ( Exception e ) { }
        return null;
    }

First, the Class object for the interface name provided is loaded. Next, an InvocationHandler object instance is created to handle the actual method calls on the proxy object (more on this in a bit). Finally, the Object object itself, which acts like the real object, is created using the Class and InvocationHandler instances. This object is returned to the caller, and the Java VM uses it to delegate method calls to your InvocationHandler implementation, by calling its invoke() method. Java Reflection requires you to implement the invoke method, which in our case looks like this:

    public Object invoke(Object proxy, Method mthd, Object[] args) throws Throwable {
        try {
            Class proxyClass = proxy.getClass();
            Class[] interfaces = proxyClass.getInterfaces();
            String interfaceName = interfaces[0].getName();
            AsyncHelper.call(interfaceName, mthd.getName(), args, callback);
        }
        catch ( Exception e ) {  }
        return null;
    }