Now that you have a basic idea of how Java RMI works, we can explore the details of creating and using distributed objects with RMI. As I mentioned earlier, defining a remote RMI object involves specifying a remote interface for the object, then providing a class that implements this interface. The remote interface and implementation class are then used by RMI to generate a client stub and server skeleton for your remote object. The communication between local objects and remote objects is handled using these client stubs and server skeletons. The relationships among stubs, skeletons, and the objects that use them are shown in Figure 3-2.
When a client gets a reference to a remote object (details on how this reference is obtained come later) and then calls methods on this object reference, there needs to be a way for the method request to get transmitted back to the actual object on the remote server and for the results of the method call to get transmitted back to the client. This is what the generated stub and skeleton classes are for. They act as the communication link between the client and your exported remote object, making it seem to the client that the object actually exists within its Java VM.
The RMI compiler (rmic) automatically generates these stub and skeleton classes for you. Based on the remote interface and implementation class you provide, rmic generates stub and skeleton classes that implement the remote interface and act as go-betweens for the client application and the actual server object. For the client stub class, the compiler generates an implementation of each remote method that simply packages up (marshals) the method arguments and transmits them to the server. For the server skeleton class, the RMI compiler generates another set of implementations of the remote methods, but these are designed to receive the method arguments from the remote method call, unpackage them, and make the corresponding method call on the object implementation. Whatever the method call generates (return data or an exception), the results are packaged and transmitted back to the remote client. The client stub method (which is still executing at this point) unpackages the results and delivers them to the client as the result of its remote method call.
So, the first step in creating your remote objects is to define the remote interfaces for the types of objects you need to use in a distributed object context. This isn't much different from defining the public interfaces in a nondistributed application, with the following exceptions:
Every object you want to distribute using RMI has to directly or indirectly extend an interface that extends the java.rmi.Remote interface.
Every method in the remote interface has to declare that it throws a java.rmi.RemoteException or one of the parent classes of RemoteException.[1]
[1]Note that prior to Java 1.2, the RMI specification required that every method on a remote interface had to throw RemoteException specifically. In Java 1.2, this has been loosened to allow any superclass of RemoteException. The reason for this change is to make it easier to define generic interfaces that support both local and remote objects.
RMI imposes the first requirement to allow it to differentiate quickly between objects that are enabled for remote distribution and those that are not. As we've already seen, during a remote method invocation, the RMI runtime system needs to be able to determine whether each argument to the remote method is a Remote object or not. The Remote interface, which is simply a tag interface that marks remote objects, makes it easy to perform this check.
The second requirement is needed to deal with errors that can happen during a remote session. When a client makes a method call on a remote object, any number of errors can occur, preventing the remote method call from completing. These include client-side errors (e.g., an argument can't be marshaled), errors during the transport of data between client and server (e.g., the network connection is dropped), and errors on the server side (e.g., the method throws a local exception that needs to be sent back to the remote caller). The RemoteException class is used by RMI as a base exception class for any of the different types of problems that might occur during a remote method call. Any method you declare in a Remote interface is assumed to be remotely callable, so every method has to declare that it might throw a RemoteException, or one of its parent interfaces.
Example 3-3 shows a simple remote interface that declares two methods: doThis() and doThat(). These methods could do anything that we want; in our Account example, we had remote methods to deposit, withdraw, and transfer funds. Each method takes a single String argument and returns a String result. Since we want to use this interface in an RMI setting, we've declared that the interface extends the Remote interface. In addition, each method is declared as throwing a RemoteException.
import java.rmi.Remote; import java.rmi.RemoteException; public interface ThisOrThatServer extends Remote { public String doThis(String todo) throws RemoteException; public String doThat(String todo) throws RemoteException; }
With the remote interface defined, the next thing we need to do is write a class that implements the interface. Example 3-4 shows the ThisOrThatServerImpl class, which implements the ThisOrThatServer interface.
import java.rmi.server.UnicastRemoteObject; import java.rmi.RemoteException; public class ThisOrThatServerImpl extends UnicastRemoteObject implements ThisOrThatServer { public ThisOrThatServerImpl() throws RemoteException {} // Remotely accessible methods public String doThis(String todo) throws RemoteException { return doSomething("this", todo); } public String doThat(String todo) throws RemoteException { return doSomething("that", todo); } // Non-remote methods private String doSomething(String what, String todo) { String result = "Did " + what + " to " + todo + "."; return result; } }
This class has implementations of the doThis() and doThat() methods declared in the ThisOrThatServer interface; it also has a nonremote method, doSomething(), that is used to implement the two remote methods. Notice that the doSomething() method doesn't have to be declared as throwing a RemoteException, since it isn't a remotely callable method. Only the methods declared in the remote interface can be invoked remotely. Any other methods you include in your implementation class are considered nonremote (i.e., they are only callable from within the local Java virtual machine where the object exists).
You probably noticed that our ThisOrThatServerImpl class also extends the UnicastRemoteObject class. This is a class in the java.rmi.server package that extends java.rmi.server.RemoteServer, which itself extends java.rmi.ser-ver.RemoteObject, the base class for all RMI remote objects. There are four key classes related to writing server object implementations:
RemoteObject implements both the Remote and java.rmi.server package, it is used by both the Serializable interfaces. Although the RemoteObject class is in the client and server portions of a remote object reference. Both client stubs and server implementations are subclassed (directly or indirectly) from RemoteObject. A RemoteObject contains the remote reference for a particular remote object.
RemoteObject is an abstract class that reimplements the equals(), hashCode(), and toString() methods inherited from Object in a way that makes sense and is practical for remote objects. The equals() method, for example, is implemented to return true if the internal remote references of the two RemoteObject objects are equal, (i.e., if they both point to the same server object).
RemoteServer is an abstract class that extends RemoteObject. It defines a set of static methods that are useful for implementing server objects in RMI, and it acts as a base class for classes that define various semantics for remote objects. In principle, a remote object can behave according to a simple point-to-point reference scheme; it can have replicated copies of itself scattered across the network that need to be kept synchronized; or any number of other scenarios. JDK 1.1 supported only point-to-point, nonpersistent remote references with the UnicastRemoteObject class. The Java 2 SDK 1.2 has introduced the RMI activation system, so it provides another subclass of RemoteServer, Activatable.
This is a concrete subclass of RemoteServer that implements point-to-point remote references over TCP/IP networks. These references are nonpersistent: remote references to a server object are only valid during the lifetime of the server object. Before the server object is created (inside a virtual machine running on the host) or after the object has been destroyed, a client can't obtain remote references to the object. In addition, if the virtual machine containing the object exits (intentionally or otherwise), any existing remote references on clients become invalid and generate RemoteException objects if used.
This concrete subclass of RemoteServer is part of the new RMI object activation facility in Java 1.2 and can be found in the java.rmi.activation package. It implements a server object that supports persistent remote references. If a remote method request is received on the server host for an Activatable object, and the target object is not executing at the time, the object can be started automatically by the RMI activation daemon.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |