The Java 2 Enterprise Edition (J2EE) remote method invocation (RMI) framework enables you to create virtually transparent, distributed services and applications. RMI-based applications consist of Java objects making method calls to one another without regard for their location. This allows one Java object to invoke methods on another Java object residing in another virtual machine in the same manner in which methods are invoked on a Java object residing in the same virtual machine.
Objects residing in different virtual machines can obtain references to each other using RMI’s lookup service or by receiving the object reference as a parameter or return value of a method call. Parameters and return values are marshaled by RMI via Java’s object serialization.
Remote objects and interfaces
Java provides an interface with the fully qualified name of java.rmi.Remote, which must be directly or indirectly implemented by any object that participates in a remote conversation with another Java object. Specifically, any object identified by the java.rmi.Remote interface implies that its methods can be invoked from another virtual machine. An object implementing the java.rmi.Remote interface is commonly referred to as a “remote object” and must declare its methods in the following manner:
- Each remote invocation-eligible method must declare java.rmi.RemoteException in its throws clause.
- Each nonprimitive argument or return value of a remote invocation-eligible method must be directly or indirectly declared as implementing the java.io.Serializable interface.
In addition to implementing the java.rmi.Remote interface and properly declaring any remote methods, remote objects must provide a no-arg constructor that throws a java.rmi.RemoteException. This ensures that the object can be constructed remotely from a serialized state.
Remote objects must be exported to accept incoming remote method invocations. This is usually facilitated by extending java.rmi.server.UnicastRemoteObject or java.rmi.activation.Activatable. By extending one of these classes, a remote object is automatically exported when it is created.
The following interface definition illustrates how the java.rmi.Remote interface is used in a typical situation:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface TimeKeeper extends Remote
{
public String currentDate() throws RemoteException;
public String currentTime() throws RemoteException;
}
Since the String class is declared as implementing the java.io.Serializable interface, String is a valid return type for a remote method.
The following implementation class illustrates how the TimeKeeper interface could be implemented to define a valid remote object:
import java.rmi.RemoteException;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class TimeKeeperImpl implements TimeKeeper
{
public TimeKeeperImpl()
throws RemoteException
{
}
public String currentDate() throws RemoteException
{
Calendar cal = new GregorianCalendar();
String retVal = (cal.get(Calendar.MONTH) + “/” +
cal.get(Calendar.DAY_OF_MONTH) + “/” +
cal.get(Calendar.YEAR));
return retVal;
}
public String currentTime() throws RemoteException
{
Calendar cal = new GregorianCalendar();
String retVal = (cal.get(Calendar.HOUR_OF_DAY) + “:” +
cal.get(Calendar.MINUTE) + “:” +
cal.get(Calendar.SECOND));
return retVal;
}
}
RMI registries
For obtaining references to remote objects, RMI provides a remote object called a registry, which associates names with remote objects. An RMI server registers each remote object with the registry so that the objects can be located and retrieved. When an RMI client wants to invoke a method on a remote object, it must first locate the remote object within a registry using the remote object’s name. If the remote object exists, the registry will return a reference to the remote object. This reference can then be used to make method invocations on the remote object.
RMI servers
RMI communications work in a client/server fashion. This dictates that an object on one side of an RMI conversation must act as the server and objects on the other side must act as clients. An RMI server creates an instance of each remote object and binds each instance to a name in the rmiregistry. An RMI server can be self-sufficient, in that it can implement a main method, thus freeing itself from a dependency on another class in order to execute.
Since an RMI server can download and execute code from virtually any host, the main method of each RMI server needs to install a security manager to prevent the classes that are loaded by it from misbehaving. The following example illustrates how to instantiate a security manager and bind one object instance in the RMI registry:
import java.rmi.RMISecurityManager;
import java.rmi.Naming;
public class SimpleRMIServer
{
public static void main(String[] args)
{
if (System.getSecurityManager() == null)
{
System.setSecurityManager(new RMISecurityManager());
}
try
{
TimeKeeperImpl remoteObj = new TimeKeeperImpl();
// Bind the remote object to the name “TimeKeeper”
Naming.bind(“//HostName/TimeKeeper”, remoteObj);
System.out.println(“TimeKeeper successfully bound in registry”);
}
catch (Exception e)
{
System.err.println(“Error binding TimeKeeper: ” + e.getMessage());
}
}
}
Summary
In this article, I explored how RMI is used to hide remote interaction issues, allowing programmers to concentrate on other issues besides the communication infrastructure. The next article will further explore RMI, as I show how RMI clients can locate remote objects and invoke methods on them.