The JavaTM Tutorial

| Start of Tutorial > Start of Trail > Start of Lesson | Search

Trail: RMI

## Implementing a Remote Interface

> Let's turn now to the task of implementing a class for the compute engine. In general the implementation class of a remote interface should at least
> - Declare the remote interfaces being implemented
> - Define the constructor for the remote object
> - Provide an implementation for each remote method in the remote interfaces

> The server needs to create and to install the remote objects. This setup procedure can be encapsulated in a `main` method in the remote object implementation class itself, or it can be included in another class entirely. The setup procedure should

> - Create and install a security manager
> - Create one or more instances of a remote object
> - Register at least one of the remote objects with the RMI remote object registry (or another naming service such as one that uses JNDI), for bootstrapping purposes

> The complete implementation of the compute engine follows. The ``engine.ComputeEngine`` class implements the remote interface `Compute` and also includes the `main` method for setting up the compute engine.

> >     package engine;

> >     import java.rmi.*;
> >     import java.rmi.server.*;
> >     import compute.*;

> >     public class ComputeEngine extends UnicastRemoteObject
> >                                implements Compute
> >     {
> >         public ComputeEngine() throws RemoteException {
> >             super();
> >         }

> >         public Object executeTask(Task t) {
> >             return t.execute();
> >         }

> >         public static void main(String[] args) {
> >             if (System.getSecurityManager() == null) {
> >                 System.setSecurityManager(new RMISecurityManager());
> >             }
> >             String name = "//host/Compute";
> >             try {
> >                 Compute engine = new ComputeEngine();
> >                 Naming.rebind(name, engine);
> >                 System.out.println("ComputeEngine bound");
> >             } catch (Exception e) {
> >                 System.err.println("ComputeEngine exception: " +
> >     			       e.getMessage());
> >                 e.printStackTrace();
> >             }
> >         }
> >     }

> Now let's take a closer look at each of the components of the compute engine implementation.

### Declare the Remote Interfaces Being Implemented

> The implementation class for the compute engine is declared as

> >     public class ComputeEngine extends UnicastRemoteObject
> >                                implements Compute

> This declaration states that the class implements the `Compute` remote interface (and therefore defines a remote object) and extends the class `java.rmi.server.UnicastRemoteObject`.

> `UnicastRemoteObject` is a convenience class, defined in the RMI public API, that can be used as a superclass for remote object implementations. The superclass `UnicastRemoteObject` supplies implementations for a number of `java.lang.Object` methods (`equals`, `hashCode`, `toString`) so that they are defined appropriately for remote objects`. UnicastRemoteObject`also includes constructors and static methods used to export a remote object, that is, make the remote object available to receive incoming calls from clients.

> A remote object implementation does not have to extend `UnicastRemoteObject`, but any implementation that does not must supply appropriate implementations of the `java.lang.Object` methods. Furthermore, a remote object implementation must make an explicit call to one of `UnicastRemoteObject`'s `exportObject` methods to make the RMI runtime aware of the remote object so that the object can accept incoming calls. By extending `UnicastRemoteObject`, the `ComputeEngine` class can be used to create a simple remote object that supports unicast (point-to-point) remote communication and that uses RMI's default sockets-based transport for communication.

> If you choose to extend a remote object from any class other than `Unicast-RemoteObject` or, alternatively, extend from the new JDK 1.2 class `java.rmi.activation.Activatable` (used to construct remote objects that can execute on demand), you need to export the remote object by calling either the `UnicastRemoteObject.exportObject` or `Activatable.exportObject` method explicitly from your class's constructor (or another initialization method, as appropriate).

> The compute engine example defines a remote object class that implements only a single remote interface and no other interfaces. The `ComputeEngine` class also contains some methods that can be called only locally. The first of these is a constructor for `ComputeEngine` objects; the second is a `main` method that is used to create a `ComputeEngine` and make it available to clients.

### Define the Constructor

> The `ComputeEngine` class has a single constructor that takes no arguments. The code for the constructor is

> >     public ComputeEngine() throws RemoteException {
> >         super();
> >     }

> This constructor simply calls the superclass constructor, which is the no-argument constructor of the `UnicastRemoteObject` class. Although the superclass constructor gets called even if omitted from the `ComputeEngine` constructor, we include it for clarity.

> During construction, a `UnicastRemoteObject` is exported, meaning that it is available to accept incoming requests by listening for incoming calls from clients on an anonymous port.

> > Note: In JDK 1.2 you may indicate the specific port that a remote object uses to accept requests.

> The no-argument constructor for the superclass, `UnicastRemoteObject`, declares the exception `RemoteException` in its `throws` clause, so the `Compute-Engine` constructor must also declare that it can throw `RemoteException`. A `RemoteException` can occur during construction if the attempt to export the object fails--due to, for example, communication resources being unavailable or the appropriate stub class not being found.

### Provide Implementations for Each Remote Method

> The class for a remote object provides implementations for each of the remote methods specified in the remote interfaces. The `Compute` interface contains a single remote method, `executeTask`, which is implemented as follows:

> >     public Object executeTask(Task t) {
> >         return t.execute();
> >     }

> This method implements the protocol between the `ComputeEngine` and its clients. Clients provide the `ComputeEngine` with a `Task` object, which has an implementation of the task's `execute` method. The `ComputeEngine` executes the `Task` and returns the result of the task's `execute` method directly to the caller.

> The `executeTask` method does not need to know anything more about the result of the `execute` method than that it is at least an `Object`. The caller presumably knows more about the precise type of the `Object` returned and can cast the result to the appropriate type.

> ### Passing Objects in RMI

> > Arguments to or return values from remote methods can be of almost any type, including local objects, remote objects, and primitive 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.`

> > A few 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 a file descriptor, encapsulate information that makes sense only within a single address space. Many of the core classes, including those 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.

> > - Remote objects are essentially passed by reference. A remote object reference is a stub, which is a client-side proxy that implements the complete set of remote interfaces that the remote object implements.
> > - Local objects are passed by copy, using object serialization. By default all fields are copied, except those that are marked `static` or `transient`. Default serialization behavior can be overridden on a class-by-class basis.

> > Passing an object by reference (as is done with remote objects) means that any changes made to the state of the object by remote method calls are reflected in the original remote object. When passing a remote object, only those interfaces that are remote interfaces are available to the receiver; any methods defined in the implementation class or defined in nonremote 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 either the `ComputeEngine` constructor or its `main` method or any of the methods in `java.lang.Object`.

> > In remote method calls objects--parameters, return values, and exceptions--that are not remote objects are passed by value. This means that a copy of the object is created in the receiving virtual machine. Any changes to this object's state at the receiver are reflected only in the receiver's copy, not in the original instance.

### Implement the Server's main Method

> The most involved 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 for accepting calls from clients. This method is not a remote method, which means that it cannot be called from a different virtual machine. Since the `main` method is declared `static`, the method is not associated with an object at all but rather with the class `ComputeEngine`.

### Create and Install a Security Manager

> The first thing that the `main` method does is to create and to install a security manager, which protects access to system resources from untrusted downloaded code running within the virtual machine. The security manager determines whether downloaded code has access to the local file system or can perform any other privileged operations.

> All programs using RMI must install a security manager, or RMI will not download classes (other than from the local class path) for objects received as parameters, return values, or exceptions in remote method calls. This restriction ensures that the operations performed by downloaded code go through a set of security checks.

> The `ComputeEngine` uses a security manager supplied as part of the RMI system, the `RMISecurityManager`. This security manager enforces a similar security policy as the typical security manager for applets; that is to say, it is very conservative as to what access it allows. An RMI application could define and use another `SecurityManager` class that gave more liberal access to system resources or, in JDK 1.2, use a policy file that grants more permissions.

> Here's the code that creates and installs the security manager:

> >     if (System.getSecurityManager() == null) {
> >         System.setSecurityManager(new RMISecurityManager());
> >     }

### Make the Remote Object Available to Clients

> Next, the `main` method creates an instance of the `ComputeEngine`. This is done with the statement

> >     Compute engine = new ComputeEngine();

> As mentioned, this constructor calls the `UnicastRemoteObject` superclass constructor, which in turn exports the newly created object to the RMI runtime. Once the export step is complete, the `ComputeEngine` remote object is ready to accept incoming calls from clients on an anonymous port, one chosen by RMI or the underlying operating system. Note that the type of the variable `engine` is `Compute`, not `ComputeEngine`. This declaration emphasizes that the interface available to clients is the `Compute` interface and its methods, not the `Compute-Engine` class and its methods.

> Before a caller can invoke a method on a remote object, that caller must first obtain a reference to the remote object. This can be done in the same way that any other object reference is obtained in a program, such as getting it 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 remote object, the RMI registry, for finding references to remote objects. The RMI registry is a simple remote object name service that allows remote clients to get a reference to a remote object by name. The registry is typically used only to locate the first remote object an RMI client needs to use. That first remote object then provides support for finding other objects.

> The `java.rmi.Naming` interface is used as a front-end API for binding, or registering, and looking up remote objects in the registry. Once a remote object is registered with the RMI registry on the local host, callers on any host can look up the remote object by name, obtain its reference, and then invoke remote methods on the object. The registry may be shared by all servers running on a host, or an individual server process may create and use its own registry, if desired.

> The `ComputeEngine` class creates a name for the object with the statement

> >     String name = "//host/Compute";

> This name includes the host name, `host`, on which the registry (and remote object) is being run and a name, `Compute`, that identifies the remote object in the registry. The code then needs to add the name to the RMI registry running on the server. This is done later (within the `try` block) with the statement

> >     Naming.rebind(name, engine);

> Calling the `rebind` method makes a remote call to the RMI registry on the local host. This call can result in a `RemoteException` being generated, so the exception needs to be handled. The `ComputeEngine` class handles the exception within the `try`/`catch` block. If the exception is not handled in this way, `RemoteException` would have to be added to the `throws` clause (currently nonexistent) of the `main` method.

> Note the following about the arguments to the call to `Naming.rebind`.

> - The first parameter is a URL-formatted `java.lang.String` representing the location and the name of the remote object. You will need to change the value of `host` to be the name, or IP address, of your server machine. If the host is omitted from the URL, the host defaults to the local host. Also, you don't need to specify a protocol in the URL. For example, supplying `Compute` as the name in the `Naming.rebind` call is allowed. Optionally a port number may be supplied in the URL; for example, the name `//host:1234/objectname` is legal. If the port is omitted, it defaults to 1099. You must specify the port number only if a server creates a registry on a port other than the default 1099. The default port is useful in that it provides a well-known place to look for the remote objects that offer services on a particular host.
> - The RMI runtime substitutes a reference to the stub for the remote object reference specified by the argument. Remote implementation objects, such as instances of `ComputeEngine,` never leave the VM where they are created, so when a client performs a lookup in a server's remote object registry, a reference to the stub is returned. As discussed earlier, remote objects in such cases are passed by reference rather than by value.
> - Note that for security reasons, an application can `bind`, `unbind`, or `rebind` remote object references only 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 out a message indicating that it's ready to start handling calls and then the `main` method exits. 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 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 takes care of keeping the `ComputeEngine`'s process up. 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 deals with handling any exception that might arise. The only exception that could be thrown in the code is a `RemoteException`, thrown either by the constructor of the `ComputeEngine` class or by the call to the RMI registry to bind the object to the name `Compute`. In either case the program can't do much more than exit after printing an error message. In some distributed applications it is possible to recover from the failure to make a remote call. For example, the application could choose another server and continue operation.

| Start of Tutorial > Start of Trail > Start of Lesson | Search