This section discusses the task of implementing a class for the compute engine. In general, a class that implements a remote interface should at least do the following:
An RMI server program needs to create the initial remote objects and export them to the RMI runtime, which makes them available to receive incoming remote invocations. This setup procedure can be either encapsulated in a method of the remote object implementation class itself or included in another class entirely. The setup procedure should do the following:
The complete implementation of the compute engine follows. The
class implements the remote interface engine.ComputeEngine
Compute
and also includes the main
method for setting up the compute engine. Here is the source code for the ComputeEngine
class:
package engine; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import compute.Compute; import compute.Task; public class ComputeEngine implements Compute { public ComputeEngine() { super(); } public <T> T executeTask(Task<T> t) { return t.execute(); } public static void main(String[] args) { if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } try { String name = "Compute"; Compute engine = new ComputeEngine(); Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0); Registry registry = LocateRegistry.getRegistry(); registry.rebind(name, stub); System.out.println("ComputeEngine bound"); } catch (Exception e) { System.err.println("ComputeEngine exception:"); e.printStackTrace(); } } }
The following sections discuss each component of the compute engine implementation.
The implementation class for the compute engine is declared as follows:
public class ComputeEngine implements Compute
This declaration states that the class implements the Compute
remote interface and therefore can be used for a remote object.
The ComputeEngine
class defines a remote object implementation class that implements a single remote interface and no other interfaces. The ComputeEngine
class also contains two executable program elements that can only be invoked locally. The first of these elements is a constructor for ComputeEngine
instances. The second of these elements is a main
method that is used to create a ComputeEngine
instance and make it available to clients.
The ComputeEngine
class has a single constructor that takes no arguments. The code for the constructor is as follows:
public ComputeEngine() { super(); }
This constructor just invokes the superclass constructor, which is the no-argument constructor of the Object
class. Although the superclass constructor gets invoked even if omitted from the ComputeEngine
constructor, it is included for clarity.
The class for a remote object provides implementations for each remote method specified in the remote interfaces. The Compute
interface contains a single remote method, executeTask
, which is implemented as follows:
public <T> T executeTask(Task<T> t) { return t.execute(); }
This method implements the protocol between the ComputeEngine
remote object and its clients. Each client provides the ComputeEngine
with a Task
object that has a particular implementation of the Task
interface's execute
method. The ComputeEngine
executes each client's task and returns the result of the task's execute
method directly to the client.
Arguments to or return values from remote methods can be of almost any type, including local objects, remote objects, and primitive data types. More precisely, any entity of any type can be passed to or from a remote method as long as the entity is an instance of a type that is a primitive data type, a remote object, or a serializable object, which means that it implements the interface java.io.Serializable.
Some object types do not meet any of these criteria and thus cannot be passed to or returned from a remote method. Most of these objects, such as threads or file descriptors, encapsulate information that makes sense only within a single address space. Many of the core classes, including the classes in the packages java.lang
and java.util
, implement the Serializable
interface.
The rules governing how arguments and return values are passed are as follows:
static
or transient
. Default serialization behavior can be overridden on a class-by-class basis.Passing a remote object by reference means that any changes made to the state of the object by remote method invocations are reflected in the original remote object. When a remote object is passed, only those interfaces that are remote interfaces are available to the receiver. Any methods defined in the implementation class or defined in non-remote interfaces implemented by the class are not available to that receiver.
For example, if you were to pass a reference to an instance of the ComputeEngine
class, the receiver would have access only to the compute engine's executeTask
method. That receiver would not see the ComputeEngine
constructor, its main
method, or its implementation of any methods of java.lang.Object
.
In the parameters and return values of remote method invocations, objects that are not remote objects are passed by value. Thus, a copy of the object is created in the receiving Java virtual machine. Any changes to the object's state by the receiver are reflected only in the receiver's copy, not in the sender's original instance. Any changes to the object's state by the sender are reflected only in the sender's original instance, not in the receiver's copy.
main
MethodThe most complex method of the ComputeEngine
implementation is the main
method. The main
method is used to start the ComputeEngine
and therefore needs to do the necessary initialization and housekeeping to prepare the server to accept calls from clients. This method is not a remote method, which means that it cannot be invoked from a different Java virtual machine. Because the main
method is declared static
, the method is not associated with an object at all but rather with the class ComputeEngine
.
The main
method's first task is to create and install a security manager, which protects access to system resources from untrusted downloaded code running within the Java virtual machine. A security manager determines whether downloaded code has access to the local file system or can perform any other privileged operations.
If an RMI program does not install a security manager, RMI will not download classes (other than from the local class path) for objects received as arguments or return values of remote method invocations. This restriction ensures that the operations performed by downloaded code are subject to a security policy.
Here's the code that creates and installs a security manager:
if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); }
Next, the main
method creates an instance of ComputeEngine
and exports it to the RMI runtime with the following statements:
Compute engine = new ComputeEngine(); Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);
The static UnicastRemoteObject.exportObject
method exports the supplied remote object so that it can receive invocations of its remote methods from remote clients. The second argument, an int
, specifies which TCP port to use to listen for incoming remote invocation requests for the object. It is common to use the value zero, which specifies the use of an anonymous port. The actual port will then be chosen at runtime by RMI or the underlying operating system. However, a non-zero value can also be used to specify a specific port to use for listening. Once the exportObject
invocation has returned successfully, the ComputeEngine
remote object is ready to process incoming remote invocations.
The exportObject
method returns a stub for the exported remote object. Note that the type of the variable stub
must be Compute
, not ComputeEngine
, because the stub for a remote object only implements the remote interfaces that the exported remote object implements.
The exportObject
method declares that it can throw a RemoteException
, which is a checked exception type. The main
method handles this exception with its try
/catch
block. If the exception were not handled in this way, RemoteException
would have to be declared in the throws
clause of the main
method. An attempt to export a remote object can throw a RemoteException
if the necessary communication resources are not available, such as if the requested port is bound for some other purpose.
Before a client can invoke a method on a remote object, it must first obtain a reference to the remote object. Obtaining a reference can be done in the same way that any other object reference is obtained in a program, such as by getting the reference as part of the return value of a method or as part of a data structure that contains such a reference.
The system provides a particular type of remote object, the RMI registry, for finding references to other remote objects. The RMI registry is a simple remote object naming service that enables clients to obtain a reference to a remote object by name. The registry is typically only used to locate the first remote object that an RMI client needs to use. That first remote object might then provide support for finding other objects.
The java.rmi.registry.Registry
remote interface is the API for binding (or registering) and looking up remote objects in the registry. The java.rmi.registry.LocateRegistry
class provides static methods for synthesizing a remote reference to a registry at a particular network address (host and port). These methods create the remote reference object containing the specified network address without performing any remote communication. LocateRegistry
also provides static methods for creating a new registry in the current Java virtual machine, although this example does not use those methods. Once a remote object is registered with an RMI registry on the local host, clients on any host can look up the remote object by name, obtain its reference, and then invoke remote methods on the object. The registry can be shared by all servers running on a host, or an individual server process can create and use its own registry.
The ComputeEngine
class creates a name for the object with the following statement:
String name = "Compute";
The code then adds the name to the RMI registry running on the server. This step is done later with the following statements:
Registry registry = LocateRegistry.getRegistry(); registry.rebind(name, stub);
This rebind
invocation makes a remote call to the RMI registry on the local host. Like any remote call, this call can result in a RemoteException
being thrown, which is handled by the catch
block at the end of the main
method.
Note the following about the Registry.rebind
invocation:
LocateRegistry.getRegistry
synthesizes a reference to a registry on the local host and on the default registry port, 1099. You must use an overload that has an int
parameter if the registry is created on a port other than 1099.ComputeEngine
, never leave the Java virtual machine in which they were created. Thus, when a client performs a lookup in a server's remote object registry, a copy of the stub is returned. Remote objects in such cases are thus effectively passed by (remote) reference rather than by value.bind
, unbind
, or rebind
remote object references with a registry running on the same host. This restriction prevents a remote client from removing or overwriting any of the entries in a server's registry. A lookup
, however, can be requested from any host, local or remote.Once the server has registered with the local RMI registry, it prints a message indicating that it is ready to start handling calls. Then, the main
method completes. It is not necessary to have a thread wait to keep the server alive. As long as there is a reference to the ComputeEngine
object in another Java virtual machine, local or remote, the ComputeEngine
object will not be shut down or garbage collected. Because the program binds a reference to the ComputeEngine
in the registry, it is reachable from a remote client, the registry itself. The RMI system keeps the ComputeEngine
's process running. The ComputeEngine
is available to accept calls and won't be reclaimed until its binding is removed from the registry and no remote clients hold a remote reference to the ComputeEngine
object.
The final piece of code in the ComputeEngine.main
method handles any exception that might arise. The only checked exception type that could be thrown in the code is RemoteException
, either by the UnicastRemoteObject.exportObject
invocation or by the registry rebind
invocation. In either case, the program cannot do much more than exit after printing an error message. In some distributed applications, recovering from the failure to make a remote invocation is possible. For example, the application could attempt to retry the operation or choose another server to continue the operation.