TRMI
Tutorial Details:
Empower RMI with TRMI
Empower RMI with TRMI
By: By Guy Gur-Ari
Transparent RMI simplifies distributed application development
he Java RMI (Remote Method Invocation) API provides us with a clean way to build distributed Java applications. The components that make up these applications communicate with each other by invoking methods on their remote counterparts. Transparent RMI (TRMI) extends the RMI API to simplify distributed application development by eliminating most of the standard API's overhead.
This article details the benefits of using TRMI in place of standard RMI. It shows how distributed software developers can provide a cleaner design for their applications and focus on the problem at hand rather than on the details of remote invocation. The article also discusses how TRMI allows developers to easily retrofit an existing application with remote objects. I assume that you are familiar with the basics of RMI and Java's proxy mechanism.
Before describing RMI's drawbacks, let's review the steps required for creating a distributed application using RMI:
Create an interface that will be called remotely. This interface must extend java.rmi.Remote . Furthermore, its methods must all declare that they throw java.rmi.RemoteException in case of an invocation problem.
Create an implementation class that implements this interface. The implementation class should extend java.rmi.server.UnicastRemoteObject , which implements all the setup required of an RMI server object.
Create a server program that initializes the implementation object and binds it to a unique URL using java.rmi.Naming .
Clients wanting to use the remote instance look it up using that URL and can then call its methods. A method call is marshaled and passed over the network to the remote object, which unmarshals it, executes it locally, and returns the result (or propagates the exception, as the case may be) to the caller.
A developer wishing to create a nontrivial application using RMI faces several difficulties:
Because of the requirements imposed on remote interfaces (they must implement Remote , and their methods must throw RemoteException ), interfaces not designed with RMI in mind cannot be used remotely.
Calls to remote interfaces prove cumbersome, as they must be enclosed in a try / catch block for catching the possible RemoteException . Therefore, you must scatter exception-handling code throughout the application. To avoid doing so, developers usually limit remote invocation to a small portion of their programs.
The nuisance of RemoteException s also makes it difficult to use interfaces designed for remote execution locally.
No convenient approach can generically handle disconnections from a server in a single location. (An example of such handling might include looking up the remote object again and trying to reinvoke the method.)
Implementations of Remote interfaces cannot easily extend arbitrary classes, since they normally extend UnicastRemoteObject . (You could avoid this limitation with some effort, by reimplementing UnicastRemoteObject .)
Enter transparent RMI
Transparent RMI overcomes these difficulties by using Java's reflection and proxy mechanisms. Before delving into TRMI's details, let's see what a program that uses TRMI looks like. (Note that the code presented here is a slightly modified version of the code you can download from Resources .) Suppose we want to call the Hello interface from a remote JVM:
public interface Hello {
/**
* Returns a hello string
*/
public String hello();
}
Hello 's implementation is trivial:
public class HelloImpl implements Hello {
public String hello() {
System.out.println("hello() called");
return "Hi there!";
}
}
To access the object remotely, we set up a HelloServer :
import trmi.*;
public class HelloServer {
public static void main(String[] args) {
String name;
// ...
try {
// Create the object --
Hello hello = new HelloImpl();
// -- and bind it
trmi.Naming.rebind(
name,
hello,
new Class[] {Hello.class});
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
// ...
}
}
Note how the program above binds the remote object: We use the trmi.Naming class, which has semantics similar to java.rmi.Naming 's, albeit with a few important differences. Most notably, trmi.Naming deals in everyday objects and interfaces, while java.rmi.Naming handles Remote instances.
The HelloImpl instance is bound more or less as a typical remote object (using rebind() ), with an important difference: HelloImpl implements the simple Hello interface, which isn't a Remote interface. We also tell trmi.Naming which interfaces we want the object to expose remotely. We must do this because, unlike java.rmi.Naming , trmi.Naming cannot easily differentiate between remote and nonremote interfaces.
We now have a server that has a remotely exposed Hello implementation. We use it like this:
public class HelloClient {
public static void main(String[] args) {
String name;
Hello hello;
// ...
try {
// Look up the object
hello = (Hello) trmi.Naming.lookup(name);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
System.out.println("Saying hello...");
// Make the call (look Ma, no try block!)
String response = hello.hello();
System.out.println(
"Hello server replied: "
+ response + "\n");
}
}
We first look up the object using trmi.Naming.lookup , this time exactly as we look up a standard RMI object. We then invoke the hello() method on the object we looked up, treating the object as if it were local; no try / catch block surrounds the call, and we don't perform any error handling. Those tasks happen behind the scenes, as you shall see.
The gory details: Client side
So, how does TRMI work? As stated above, TRMI uses Java's proxy mechanism to perform its magic. The mechanism encapsulates actual RMI invocation in a pair of classes? StubInvocationHandler and RemoteObjectWrapperImpl ?that act as a bridge between any interface and its (remote) implementation.
When trmi.Naming.bind binds an object on the server, a RemoteObjectWrapperImpl instance is created to wrap it. This is the instance that java.rmi.Naming actually binds to the RMI registry. The instance exposes two remote methods declared in RemoteObjectWrapper , a standard RMI Remote interface. The more important method of the two is invokeRemote() , which the client calls to invoke a method on the wrapped object. More on that later.
Here is how the object is bound:
package trmi;
public class Naming {
// ...
public static void bind(String name, Object obj, Class[] ifaces)
throws AlreadyBoundException, ... {
// Create the wrapper
RemoteObjectWrapper wrapper =
new RemoteObjectWrapperImpl(obj, ifaces);
// Bind the wrapper
java.rmi.Naming.bind(name, wrapper);
}
}
Note that the RemoteObjectWrapperImpl constructor accepts both the wrapped object and the interfaces to be exposed remotely. As mentioned earlier, the user must let TRMI know which interfaces to expose and which to keep local because those details aren't clearly indicated as they are with RMI (that is, using extends Remote ). You should also note that standard RMI is used to bind the remote wrapper to the RMI registry. Similarly, all the naming-related methods ( unbind() and lookup() , for example) in trmi.Naming use standard RMI; doing so ensures that TRMI will be compatible with future versions of RMI and JNDI (Java Naming and Directory Interface).
On the client side, trmi.Naming.lookup() looks up a bound remote object:
public class Naming {
public static Object lookup(String name) throws NotBoundException, ... {
Remote remoteObj = java.rmi.Naming.lookup(name);
// If this is a transparent-RMI object, handle it accordingly
if (remoteObj instanceof RemoteObjectWrapper) {
RemoteObjectWrapper wrapper = (RemoteObjectWrapper) remoteObj;
return createStub(name, wrapper);
}
// Otherwise, return the standard RMI stub
else {
return remoteObj;
}
}
private static Object createStub(
String name,
RemoteObjectWrapper wrapper) throws RemoteException {
Class[] exposedInterfaces = wrapper.exposedInterfaces();
// Create the invocation handler
RemoteExceptionRecoveryStrategy recoveryStrategy;
if (name != null) {
recoveryStrategy = recoveryStrategyFactory.getRecoveryStrategy(
name, exposedInterfaces);
} else {
recoveryStrategy = recoveryStrategyFactory.getRecoveryStrategy(
exposedInterfaces);
}
StubInvocationHandler invocationHandler = new StubInvocationHandler(
wrapper, recoveryStrategy);
// Create a proxy that supports the object's exposed interfaces
Object stub = Proxy.newProxyInstance(
trmi.Naming.class.getClassLoader(),
exposedInterfaces,
invocationHandler);
return stub;
}
}
After trmi.Naming retrieves the object with java.rmi.Naming , it checks to see if the object is indeed a TRMI wrapper. If it isn't, then the object must be a standard RMI remote object, and returns as-is. If the object is a TRMI wrapper, a TRMI stub is created for it in createStub() . The stub is a proxy implementation of all the object's exposed interfaces, with a StubInvocationHandler at its core. Note the reference to a recoveryStrategyFactory : the centralized error handling takes place there, as you'll see later. For now, let's see what happens when the client calls hello() on its proxy stub. The figure below shows the TRMI setup required to execute the call.
Typical TRMI setup while invoking hello()
The call first reaches the stub's StubInvocationHandler :
public class StubInvocationHandler
implements InvocationHandler, Serializable {
// ...
public Object invoke(
Object proxy,
Method method,
Object[] params)
throws Throwable, RuntimeException {
// Loops while we fail to make the call
while (true) {
try {
// Handle the primitives issue
Class[] convertedTypes =
method.getParameterTypes();
boolean[] primitiveTypes =
new boolean[convertedTypes.length];
convertPrimitiveParamTypes(
convertedTypes,
pri
Read
Tutorial at: Click here to view the tutorial
Rate Tutorial: TRMI
View Tutorial: TRMI
Related
Tutorials:
|