views:

96

answers:

1

On the server-side I have a ListenerManager which fires callbacks to its Listeners. The manager is exported using a Spring RmiServiceExporter

On the client-side I have a proxy to the manager created by an RmiProxyFactoryBean, and a Listener implementation registered through this proxy with the manager on the server side.

So far so good: the ListenerManager is given a Listener and it invokes its callbacks, however since the listener is just a deserialized copy of the client-side object, the callback runs on the server side, not the client side.

How can I get Spring to generate a proxy on the server-side to the client-side listener so that the callback invoked by the server is executed remotely on the client-side? Surely I don't need another (exporter, proxy factory) pair in the opposite direction?

A: 

A pure RMI solution: the client-side listener object needs to implement java.rmi.server.UnicastRemoteObject. If it does, and each of its methods throw RemoteException then when it is passed to the server through the manager proxy everything is wired up automatically, and method invocations on the server-side proxy to this listener are remote invocations of methods on the real client-side object.

This will do, but it's even better to be able to wrap the object for export without requiring a particular superclass. We can use a CGLIB Enhancer to "proxy" the listener as a subclass of UnicastRemoteObject that also implements the service interfaces. This still requires that the target object implement java.rmi.Remote and declare throws RemoteException.

Next step is a solution that can export arbitrary objects for remote invocation of their methods, without requiring that they implement Remote or declare throws RemoteException. We must integrate this proxying with the existing Spring infrastructure, which we can do with a new implementation of RmiBasedExporter modelled on the non-registry bits of RmiServiceExporter#prepare() to export the RMI stub of our proxy and on the invocation part of RmiClientInterceptor.doInvoke(MethodInvocation, RmiInvocationHandler). We need to be able to get hold of an exported proxy instance of our service interfaces. We can model this on the means used by Spring to apparently "export" non-RMI interfaces. Spring proxies the interface to generate a RmiInvocationWrapper for invocation of a non-RMI method, serialises the method details and arguments, then invokes this on the far side of the RMI connection.

  • Use a ProxyFactory and an RmiInvocationHandler implementation to proxy the target object.
  • Use a new implementation of RmiBasedExporter to getObjectToExport(), and export it using UnicastRemoteObject#export(obj, 0).
  • For the invocation handler, rmiInvocationHandler.invoke(invocationFactory.createRemoteInvocation(invocation)), with a DefaultRemoteInvocationFactory.
  • Handle exceptions and wrap appropriately to avoid seeing UndeclaredThrowableExceptions.

So, we can use RMI to export arbitrary objects. This means we can use one of these objects on the client-side as a parameter to an RMI method call on an RMI server-side object, and when the deserialised stub on the server-side has methods invoked, those methods will execute on the client-side. Magic.

Joe Kearney