The most fundamental means of interobject communication in Java is method invocation. Mechanisms like the Java event model are built on simple method invocations between objects that share a virtual machine. Therefore, when we want to communicate between virtual machines on different hosts, it's natural to want a mechanism with similar capabilities and semantics. Java's Remote Method Invocation mechanism does just that. It lets us get a reference to an object on a remote host and use it as if it were in our own virtual machine. RMI lets us invoke methods on remote objects, passing real objects as arguments and getting real objects as returned values.
Remote invocation is nothing new. For many years C programmers have used remote procedure calls (RPC) to execute a C function on a remote host and return the results. The primary difference between RPC and RMI is that RPC, being an offshoot of the C language, is primarily concerned with data structures. It's relatively easy to pack up data and ship it around, but for Java, that's not enough. In Java we don't work with simple data structures; we work with objects, which contain both data and methods for working on the data. Not only do we have to be able to ship the state of an object over the wire (the data), but the recipient has to be able to interact with the object after receiving it.
It should be no surprise that RMI uses object serialization, which allows us to send graphs of objects (objects and all of the connected objects that they reference). When necessary, RMI uses dynamic class loading and the security manager to transport Java classes safely. The real breakthrough of RMI is that it's possible to ship both code and data around the Net.
Before an object can be used with RMI, it must be serializable. But that's not sufficient. Remote objects in RMI are real distributed objects. As their name suggests, a remote object can refer to an object on a different machine; it can also refer to an object on the local host. The term remote means that the object is used through a special kind of object reference that can be passed over the network. Like normal Java objects, remote objects are passed by reference. Regardless of where the reference is used, the method invocation occurs at the original object, which still lives on its original host. If a server returns a reference to a remote object to you, you can call the object's methods; the actual method invocations will happen on the remote object's server. If a client creates a remote object and passes a reference to a server, the server can use the reference to invoke methods on the original object on the client side.
Non-remote objects are simpler. They are just normal serializable objects. The catch is that when you pass a non-remote object over the network it is simply copied. So references to the object on one host are not the same as those on the remote host. This is acceptable for many simple kinds of objects, especially objects that cannot be modified.
No, we're not talking about a gruesome horror movie. Stubs and skeletons are used in the implementation of remote objects. When you invoke a method on a remote object (which could be on a different host), you are actually calling some local code that serves as a proxy for that object. This is the stub. (It is called a stub because it is something like a truncated placeholder for the object.) The skeleton is another proxy that lives with the real object on its original host. It receives remote method invocations from the stub and passes them to the object.
You never have to work with stubs or skeletons directly; they are hidden from you (in the closet). Stubs and skeletons for your remote objects are created by running the rmic (RMI compiler) utility. After compiling your Java source files normally, you run rmic.
So far we've been referring to remote objects as objects (and they are,
of course). But to be more specific, remote objects are objects that
implement a special remote interface that specifies which of the
object's methods can be invoked remotely. The remote interface must extend
the java.rmi.Remote interface. Your
remote object must implement its remote
interface; so does the stub object that is automatically generated for
you. In the rest of your code, you should refer to the remote object
using its interface--not the object's actual class. Because both the
real object and stub implement the
remote interface, they are equivalent as far as we are concerned; we
never have to worry about whether we have a reference to a stub or an
actual implementation of the object locally. This "type equivalence"
means that we can use normal language features like casting with remote
objects.
All methods in the remote interface must declare that they
can throw the exception
java.rmi.RemoteException. This exception
(actually, one of many subclasses to
RemoteException) is thrown when any kind
of networking error happens: for example, the server could crash, the
network could fail, or you could be requesting an object that for some
reason isn't available.
Here's a simple example of the remote interface that defines the
behavior of MyRemoteObject; we'll give it
two methods that can be invoked remotely, both of which return some
kind of Widget object:
public interface MyRemoteObject
extends java.rmi.Remote {
public Widget doSomething() throws java.rmi.RemoteException;
public Widget doSomethingElse() throws java.rmi.RemoteException;
}
The actual implementation of a remote object (not the interface we
discussed previously) must extend
java.rmi.server.UnicastRemoteObject. This
is the RMI equivalent to the familiar
Object class. It provides implementations
of equals(),
hashcode(), and
toString() that make sense for remote
objects. It also "exports" the object by preparing the Java run-time system to accept
network connections for this object. It's possible to do this work yourself, but it
isn't necessary.
Here's a remote object class that matches the
MyRemoteObject interface; we haven't
supplied implementation for the two methods or the constructor:
public class RemoteObjectImpl
implements MyRemoteObject
extends java.rmi.UnicastRemoteObject {
public RemoteObjectImpl() throws java.rmi.RemoteException {...}
public Widget doSomething() throws java.rmi.RemoteException {...}
public Widget doSomethingElse() throws java.rmi.RemoteException {...}
// other non-public methods
...
}This class can have as many additional methods as it needs;
presumably, most of them will be private,
but that isn't strictly necessary. We have to supply a constructor
explicitly, even if the constructor does nothing, because the
constructor (like any method) can throw a
RemoteException; we therefore can't use
the default constructor.
The name UnicastRemoteObject begs the
question, "what other kinds of remote objects are there?" Right now,
none. It's possible that JavaSoft will develop remote objects using
other protocols or multicast techniques in the future.
The registry is the RMI phone book. You use the registry to look up a reference to a registered remote object on another host. We've already described how remote references can be passed back and forth by remote method calls. But the registry is needed to bootstrap the process; the client needs some way of looking up some initial object.
The registry is implemented by a class called
Naming and an application called
rmiregistry, which must be running before you start a Java
program that uses the registry.
To use the registry, create an instance of a remote object and
have it bind itself to a particular name in the registry. (Remote
objects that bind themselves to the registry usually provide a
main() method for this purpose.) The name
can be anything you choose; it takes the form of a slash (/) separated
path. When a client object wants to find your object, it constructs a
special URL with the rmi: protocol, the
hostname, and the object name. On the client, the RMI
Naming class then talks to the registry
and returns the remote object reference.
Which objects need to register themselves with the registry? Certainly, any object that the client has no other way of finding. A call to a remote method can return another remote object without using the registry. Likewise, a call to a remote method can have another remote object as its argument, without requiring the registry. You could design your system so that only one object registers itself, and then serves as a factory for any other remote objects you need. In other words, it wouldn't be hard to build a simple object request "bouncer" (I won't say "broker") that returns references to various objects. Why avoid using the registry? The current RMI registry is not very sophisticated, and lookups tend to be slow. It is not intended to be a general purpose directory service but simply to bootstrap RMI communications. It wouldn't be surprising if JavaSoft releases a much improved registry in the future, but that's not the one we have now.
The first thing we'll implement using RMI is a duplication of the
simple serialized object protocol from the previous section. We'll
make a remote RMI object called Server on
which we can invoke methods to
get a Date object or execute a
WorkRequest object.
First, we'll define our Remote interface:
import java.rmi.*;
import java.util.*;
public interface Server extends java.rmi.Remote {
Date getDate() throws java.rmi.RemoteException;
Object execute( WorkRequest work ) throws java.rmi.RemoteException;
}
The Server interface extends the
java.rmi.Remote interface, which
identifies objects that implement it as remote objects. We supply two
methods that take the place of our old protocol:
getDate() and
execute().
Next, we'll implement this interface in a class called
MyServer that holds
the bodies of these methods. (In this example, we're not using the
convention of adding Impl to the interface
name to create the actual object name. Using this convention, the
name of the server would be ServerImpl.)
public class MyServer
extends java.rmi.server.UnicastRemoteObject implements Server {
public MyServer() throws RemoteException { }
// Implement the Server interface
public Date getDate() throws RemoteException {
return new Date();
}
public Object execute( WorkRequest work ) throws RemoteException {
return work.execute();
}
public static void main(String args[]) {
System.setSecurityManager(new RMISecurityManager());
try {
Server server = new MyServer();
Naming.rebind("NiftyServer", server);
} catch (java.io.IOException e) {
// Problem registering server
}
}
}
MyServer extends
java.rmi.UnicastRemoteObject, so when we
create an instance
of MyServer it will automatically be
exported and start listening to the
network. We start by providing a
constructor that throws RemoteException.
This exception accommodates errors
that might occur in exporting an instance. We can't use the default
constructor provided by the compiler, because the automatically
generated constructor won't throw the exception. Next,
MyServer implements the
methods of the remote Server interface.
These methods are straightforward.
The last method in this class is main().
This method lets the object set itself up as a server.
main()
starts by installing a special security manager,
RMISecurityManager. This is a special
security manager that watches any stub classes loaded over the network
by RMI. It prevents someone from handing you a misbehaving stub, in
addition to performing the other functions of a security manager.
main()
creates an instance of the MyServer
object and then calls the static method
Naming.rebind() to register the object
with the registry. The arguments to
rebind() are the name of the remote object
in the registry (NiftyServer), which clients will use to look up the
object, and reference to the server object itself. We could have
called bind() instead, but
rebind() is less prone to problems: if
there's already a NiftyServer registered,
rebind() replaces it.
We wouldn't need
the main() method or this
Naming business if we weren't expecting
clients to use the registry to find the server. That is, we could
omit main() and still use this object as a
remote object. We would be limited to passing the object in method
invocations or returning it from method invocations--but in many
situations (not ours) those aren't big limitations.
Now we need our client:
public class MyClient {
public static void main(String [] args) throws RemoteException {
System.setSecurityManager(new RMISecurityManager());
new MyClient( args[0] );
}
public MyClient(String host) {
try {
Server server = (Server)
Naming.lookup("rmi://"+host+"/NiftyServer");
System.out.println( server.getDate() );
System.out.println( server.execute( new MyCalculation(2) ) );
} catch (java.io.IOException e) {
// I/O Error or bad URL
} catch (NotBoundException e) {
// NiftyServer isn't registered
}
}
}When we run MyClient, we pass it the
hostname of the server
on which the registry is running. The
main() method installs the
RMISecurityManager and then creates an
instance of the MyClient object, passing
the hostname from the command line as an argument to the constructor.
The constructor for MyClient
uses the hostname to construct a
URL for the object. The URL will look something like this:
rmi://hostname/NiftyServer, where
NiftyServer is the name under which
we registered our Server. We pass the URL
to the static Naming.lookup()
method. If all goes well, we get back a reference to a
Server! Of course, the registry has no
idea what kind of object it will return;
lookup() therefore returns an
Object, which we cast to
Server.
Compile all of the code. Then run RMI compiler to make the stub
and skeleton files for MyServer:
% rmic MyServerLet's run the code. For the first pass, we'll assume that you have all of the class files, including the stubs and skeletons generated by rmic, available in the class path on both the client and server machines. (You can run this example on a single host to test it if you want.) Make sure your class path is correct and then start the registry; then start the server:
% rmiregistry & % java MyServer
On a Windows system, run rmiregistry in another window by preceding it with the start command. Finally, on the client machine, run
MyClient, passing the hostname of
the server:
% java MyClient myhostThe client should print the date and the number four, which the server graciously calculated.
Before running the example, we told you to distribute all the class files to both the client and server machines. However, RMI was designed to ship classes, in addition to data, around the network; you shouldn't have to distribute all the classes in advance. Let's go a step further, and have RMI load classes for us, as needed.
First, we need to tell RMI where to find any other classes it needs. We can use the system property
java.rmi.server.codebase to specify a URL
on an HTTP server when we run our client or server. This URL specifies the base directory in which RMI
will begin its search for classes. When RMI sends a serialized object
(i.e., an object's data) to some client, it also sends this URL. If
the recipient needs the class file in addition to the data, it fetches
the file via HTTP. To be more precise: if the object needed is a
remote object, the recipient fetches the desired class's stub, which
was created by rmic. Remember that stubs are stand-ins for
the objects themselves; their job is to talk to the object, which
remains on the server. If the object needed doesn't implement the
Remote interface, the recipient fetches
the object's class file itself, and uses the object locally.
Therefore, we don't have to distribute class files; we can let clients
download them as necessary. In Figure 11.3, we see
MyClient going to the registry to get a
reference to the Server object. Then
MyClient dynamically downloads the stub
class for MyServer from the HTTP daemon
running on the server host.

We can now split our class files between the server and client
machines. For example, we could withhold the
MyCalculation class from the server, since
it really belongs to the client. Instead, we can make the
MyCalculation class available via an HTTP
daemon on some machine (probably our client's) and specify the URL
when we run MyClient:
% java -Djava.rmi.server.codebase='http://myserver/foo/' MyClientIn this case we would expect that
MyCalculation would be accessible at the
URL http://myserver/foo/MyCalculation.class.So far, we haven't done anything that we couldn't have done with the
simple object protocol. We only used one remote object,
MyServer, and we got its reference from
the RMI registry. Now we'll extend our example to pass some remote
references between the client and server.
We'll add two methods to our remote Server
interface:
public interface Server extends java.rmi.Remote {
...
StringEnumeration getList() throws java.rmi.RemoteException;
void asyncExecute( WorkRequest work, WorkListener listener )
throws java.rmi.RemoteException;
}
getList() retrieves a new kind of object
from the server: a
StringEnumeration. The
StringEnumeration is a simple list of
strings, with some methods for accessing the strings in order. We
will make it a remote object so that implementations of
StringEnumeration can stay on the server.Next we'll spice up our work request feature by adding an
asyncExecute()
method. asyncExecute() lets us hand off a
WorkRequest object as before,
but it does the calulation on its own time. The return type for
asyncExecute() is
void, because it doesn't actually return a
value; we get the result later.
With the request, our client passes a reference to a
WorkListener
object that is to be notified when the
WorkRequest is done. We'll have our
client implement WorkListener itself.
Because this is to be a remote object, our interface must extend
Remote, and its methods must throw
RemoteExceptions:
public interface StringEnumeration extends Remote {
public boolean hasMoreItems() throws RemoteException;
public String nextItem() throws RemoteException;
}
Next, we provide a simple implementation of
StringEnumeration, called
StringEnumerator:
public class StringEnumerator
extends java.rmi.server.UnicastRemoteObject implements StringEnumeration {
String [] list;
int index = 0;
public StringEnumerator( String [] list ) throws RemoteException {
this.list = list;
}
public boolean hasMoreItems() throws RemoteException {
return index < list.length;
}
public String nextItem() throws RemoteException {
return list[index++];
}
}
The StringEnumerator extends
UnicastRemoteObject.
Its methods are simple: it can give you the next string in the list,
and it can tell you whether there are any strings that you haven't
seen yet.
Next, we'll define the WorkListener remote
interface. This is the interface that defines how an object should
listen for a completed WorkRequest. It
has one method, workCompleted(), which the
server that is executing a WorkRequest
calls when the job is done:
public interface WorkListener extends Remote {
public void workCompleted( WorkRequest request, Object result )
throws RemoteException;
}
Next, let's add the new features to
MyServer. We need to add implementations
of the getList() and
asyncExecute() methods, which we just
added to the Server interface:
public class MyServer
extends java.rmi.server.UnicastRemoteObject implements Server {
...
public StringEnumeration getList() throws RemoteException {
return new StringEnumerator(
new String [] { "Foo", "Bar", "Gee" } );
}
public void asyncExecute( WorkRequest request , WorkListener listener )
throws java.rmi.RemoteException {
Object result = request.execute();
listener.workCompleted( request, result );
}
}
getList() just returns a
StringEnumerator with some
stuff in it. asyncExecute() calls a
WorkRequest's
execute() method and
notifies the listener when it's done. (Our implementation of
asyncExecute() is a little cheesy. If we
were forming a more complex calculation we would want to start a
thread to do the calculation, and return immediately from
asyncExecute(), so the client won't block.
The thread would
call workCompleted() at a later time, when
the computation was done. In this simple example, it would take
longer to start the thread than to perform the calculation.)
We have to modify MyClient to
implement the remote WorkListener
interface. This turns MyClient into a remote
object, so we must make it a
UnicastRemoteObject. We also add the
workCompleted() method that the
WorkListener interface requires:
public class MyClient extends java.rmi.server.UnicastRemoteObject
implements WorkListener {
...
public void workCompleted( WorkRequest request, Object result )
throws RemoteException {
System.out.println("Async work result = " + result);
}
}
Finally, we want MyClient to exercise the
new features. Add these lines after the calls to
getDate() and
execute():
// MyClient constructor
...
StringEnumeration se = server.getList();
while ( se.hasMoreItems() )
System.out.println( se.nextItem() );
server.asyncExecute( new MyCalculation(100), this );
We use getList() to get the enumeration
from the server, then loop,
printing the strings.
We also call asyncExecute() to perform
another calculation; this time,
we square the number 100.
The second argument to asyncExecute() is the WorkListener to notify
when the data is ready; we pass a reference to ourself
(this).
Now all we have to do is compile everything and run rmic to make the stubs for all our remote objects:
rmic MyClient MyServer StringEnumeratorRestart the RMI registry and
MyServer on
your server, and run the
client somewhere. You should get the following:
Fri Jul 11 23:57:19 PDT 1997 4 Foo Bar Gee Async work result = 10000
Java supports one important alternative to RMI, called CORBA (Common Object Request Broker Architecture). We won't say much about CORBA, but you should know it exists. CORBA is a standard developed by the Object Management Group (OMG), of which Sun Microsystems is one of the founding members. Its major advantage is that it works cross language: a Java program can use CORBA to talk to objects written in other languages, like C or C++. This is a considerable advantage if you want to build a Java front end for an older program that you can't afford to reimplement. CORBA also provides some other services that aren't yet available in Java. CORBA's major disadvantage is that it's complex. JavaSoft has announced that they will be making efforts to integrate RMI and CORBA, but it's too early to see where these efforts will lead.