Section 13.2. Introduction to RMI


13.2. Introduction to RMI

RMI is the distributed object system that is built into the core Java environment. You can think of RMI as a built-in facility for Java that allows you to interact with objects that are actually running in Java virtual machines on remote hosts on the network. With RMI (and other distributed object APIs we discuss in other chapters of this book), you can get a reference to an object that "lives" in a remote process and invoke methods on it as if it were a local object running within the same virtual machine as your code (hence the name "remote method invocation").

Another way to characterize RMI (and other remote object schemes) is in terms of the granularity of the distribution that it enables. The Java servlet and JSP APIs, described in Chapters 3 and 4, allow you to distribute applications at the user interface level. Putting a servlet or JSP frontend on your server-side object model serves to export a web-based (typically HTML) interface to your application, which any remote web browser can access. The Java networking APIs, embodied in the java.net\ and java.io packages, allow you to open up very narrow, low-level data connections to your Java process, for simple data exchanges or "heartbeat" purposes (make a successful connection and transmit a few bytes to confirm that a process is alive). RMI and other remote object systems fall somewhere in between the two. They allow you to export functionality at the object/component level, allowing remote clients to interact directly with individual objects, in the same way they do with local objects: using (remote) method calls.

RMI was added to the core Java API in Version 1.1 of the JDK, in recognition of the critical need for support for distributed objects in distributed application development. Prior to RMI and other remote object schemes, writing a distributed application involved basic socket programming, in which a "raw" communication channel was used to pass messages and data between two remote processes. Now, with RMI and distributed objects, you can "export" an object as a remote object, so that other remote processes/agents can access it directly as a Java object. So, instead of defining a low-level message protocol and data transmission format between processes in your distributed application, you can use Java interfaces as the "protocol" and the exported method arguments become the data transmission format. The distributed object system (RMI in this case) handles all the underlying networking needed to make your remote method calls work.

Java RMI is normally a Java-only distributed object scheme; if you are using the default RMI communication protocol (Java Remote Method Protocol, or JRMP ), objects in an RMI-based distributed application have to be implemented in Java. Some other distributed object schemes, most notably CORBA , are language-independent, which means that the objects can be implemented in any language that has a defined binding. With CORBA, for example, bindings exist for C, C++, Java, Smalltalk, and Ada, among other languages.

The advantages of RMI primarily revolve around the fact that it is "Java-native." Since RMI is part of the core Java API and is built to work directly with Java objects within the Java VM, the integration of its remote object facilities into a Java application is almost seamless. And since Java RMI is built on the assumption that both the client and server are Java objects, RMI can extend Java's internal garbage-collection mechanisms to provide distributed garbage collection of remotely exported objects.

If you have a distributed application with heterogeneous platform components, some of which are written in Java and some of which aren't, you have a few choices. You can use RMI, wrapping the non-Java code with RMI-enabled Java objects using the Java Native Interface (JNI). Near the end of this chapter, we discuss this first option in some detail to give you a feeling for when this technique could be useful and when it wouldn't be. Another option is to use another object distribution scheme, such as CORBA, that supports language-independent object interfaces. Chapter 14 covers the Java interface to CORBA that is included in the JDK. A third option involves using RMI/IIOP , which allows RMI objects to communicate directly with remote CORBA objects (implemented in any language supported by CORBA) over IIOP (Internet Inter-ORB Protocol). We discuss this option in some detail at the end of this chapter. Finally, a more recent and more popular option for connecting cross-platform distributed systems is to use SOAP-based web services. Standard support for web services in Java is provided by the JAX-RPC and SAAJ APIs, both of which are discussed in Chapter 12.

13.2.1. RMI in Action

Before we start examining the details of using RMI, let's look at a simple RMI remote object at work. We will create an Account object that represents some kind of bank account and then use RMI to export it as a remote object so that remote clients (e.g., ATMs, personal finance software running on a PC, etc.) can access it to carry out transactions.

The first step is to define the interface for our remote object. Example 13-1 shows the Account interface. You can tell that it's an RMI object because it extends the java.rmi.Remote interface. Another signal that this is meant for remote access is that each method can throw a java.rmi.RemoteException. The Account interface includes methods to get the account name and balance and to make deposits, withdrawals, and transfers.

Example 13-1. A remote account interface
 import java.rmi.Remote; import java.rmi.RemoteException; public interface Account extends Remote {     // Get the name on the account     public String getName( ) throws RemoteException;     // Get current balance     public float getBalance( ) throws RemoteException;     // Take some money away     public void withdraw(float amt)         throws RemoteException, InsufficientFundsException;     // Put some money in     public void deposit(float amt) throws RemoteException;     // Move some money from one account into this one     public void transfer(float amt, Account src)         throws RemoteException, InsufficientFundsException; } 

The next step is to create an implementation of this interface, which leads to the AccountImpl class shown in Example 13-2. This class implements all the methods listed in the Account interface and adds a constructor that takes the name of the new account to be created. Notice that the AccountImpl class implements the Account interface, but it also extends the java.rmi.UnicastRemoteObject class. The UnicastRemoteObject class provides the basic remote functionality for RMI server objects.

Example 13-2. Implementation of the remote account interface
 import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class AccountImpl extends UnicastRemoteObject implements Account {     // Our current balance     private float mBalance = 0;     // Name on account     private String mName = "";     // Create a new account with the given name     public AccountImpl(String name) throws RemoteException {         mName = name;     }     public String getName( ) throws RemoteException {         return mName;     }     public float getBalance( ) throws RemoteException {         return mBalance;     }     // Withdraw some funds     public void withdraw(float amt)         throws RemoteException, InsufficientFundsException {         if (mBalance >= amt) {             mBalance -= amt;             // Log transaction...             System.out.println("--> Withdrew " + amt +                                " from account " + getName( ));             System.out.println("    New balance: " + getBalance( ));         }         else {             throw new InsufficientFundsException("Withdrawal request of " +                                                  amt + " exceeds balance of "                                                  + mBalance);         }     }     // Deposit some funds     public void deposit(float amt) throws RemoteException {         mBalance += amt;         // Log transaction...         System.out.println("--> Deposited " + amt +                            " into account " + getName( ));         System.out.println("    New balance: " + getBalance( ));     }     // Move some funds from another (remote) account into this one     public void transfer(float amt, Account src)         throws RemoteException, InsufficientFundsException {         if (checkTransfer(src, amt)) {             src.withdraw(amt);             this.deposit(amt);             // Log transaction...             System.out.println("--> Transferred " + amt +                                " from account " + getName( ));             System.out.println("    New balance: " + getBalance( ));         }         else {             throw new InsufficientFundsException("Source account balance " +                                                  "is insufficient.");         }     }     // Check to see if the transfer is possible, given the source account    private boolean checkTransfer(Account src, float amt) {         boolean approved = false;         try {             if (src.getBalance( ) >= amt) {                 approved = true;             }         }         catch (RemoteException re) {             // If some remote exception occurred, then the transfer is still             // compromised, so return false             approved = false;         }         return approved;     } } 

Once the remote interface and an implementation of it are complete, you compile both Java files with your favorite Java compiler. If you are using JDK 1.5 or later for both the server and the client (and compiled the classes using a 1.5-compliant compiler), you can use them both at this pointthe stubs and skeletons needed to connect the client to the server will be dynamically generated at runtime. But if you're using an earlier JDK to run either the client or the server, you'll need to use the RMI stub/skeleton compiler to generate a client stub and a server skeleton for the AccountImpl object. The stub and skeleton handle the communication between the client application and the server object. The RMI compiler is called rmic, and you can invoke it for this example like so:

     % rmic -d /home/classes AccountImpl 

The stub and skeleton classes are generated and stored in the directory given by the -d option (/home/classes, in this case). This example assumes that the AccountImpl class and the /home/classes directory are already in your CLASSPATH before you run the RMI compiler.

There's just one more thing we need to do before we can actually use our remote object: register it with an RMI registry , so that remote clients can find it on the network. The utility class that follows, AccountServer, does this by creating an AccountImpl object and then binding it to a name in the local registry using the java.rmi.Naming interface:

     import java.net.MalformedURLException;     import java.rmi.Naming;     import java.rmi.RMISecurityManager;     import java.rmi.RemoteException;     public class AccountServer {         public static void main(String argv[]) {             try {                 System.setSecurityManager(new RMISecurityManager( ));                 // Make an Account with a given name                 AccountImpl acct = new AccountImpl("JimF");                 // Register it with the local naming registry                 Naming.rebind("JimF", acct);                 System.out.println("Registered account as JimF.");                 // Keep the server process alive indefinitely                 Object dummy = new Object( );                 synchronized (dummy) {                     try {                         dummy.wait( )  ;                     }                     catch (InterruptedException ie) {}                 }             }             catch (RemoteException re) {                 System.err.println("Remote exception while" +                                    "creating/registering  : "                                    + re.getMessage( ));             }             catch (MalformedURLException mue) {                 System.err.println("Bad name given when binding server object: "                                    + mue.getMessage( ));             }         }     } 

After the AccountServer.main( ) method is done registering its Account object, it keeps the server process alive by going into an infinite wait( ) loop, waiting on a local object that is the target of our own synchronized block. In a real-world situation, you would probably have more elegant ways to keep the server alive and kill it off. Or perhaps not, since this approach is simple, but effective.

After you compile the AccountServer class, you can run its main( ) method to register an Account with the local RMI registry. First, however, you need to start the registry. With Sun's JDK, the registry can be started using the rmiregistry utility. On a Unix machine, this can be done like so:

     objhost% rmiregistry & 

Once the registry is started, you can invoke the main( ) method on the AccountServer class simply by running it:

     objhost% java AccountServer     Registered account. 

Now we have a remote Account object that is ready and waiting for a client to access it and call its methods. The following client code does just this, by first looking up the remote Account object using the java.rmi.Naming interface (and assuming that the Account object was registered on a machine named objhost.org), and then calling the deposit method on the Account object:

     import java.rmi.Naming;     public class AccountClient {       public static void main(String argv[]) {         try {           // Look up account object           Account jimAcct = (Account)Naming.lookup("rmi://objhost.org/JimF");           // Make deposit           jimAcct.deposit(12000);           // Report results and balance.           System.out.println("Deposited 12,000 into account owned by " +                              jimAcct.getName( ));           System.out.println("Balance now totals: " + jimAcct.getBalance( ));         }         catch (Exception e) {           System.out.println("Error while looking up account:");           e.printStackTrace( );         }       }     } 

The first time you run this client, here's what you'd do:

     % java AccountClient     Deposited 12,000 into account owned by Jim.Farley     Balance now totals: 12000.0 

For the sake of this example, we have made two big assumptions about the environment used to run the code. First, we assume that the client process and the RMI registry have all the necessary classes available on their runtime CLASSPATH (i.e., the Account interface and, if needed, the stub classes generated from the AccountImpl implementation). The second assumption is that the JVMs being used for the RMI registry, the server process, and the client all have their security permissions set to allow the various network connections needed for the server to register the Account object and for the client to get a reference to the Account from the registry. Later in the chapter, we'll see how to deal with loading classes remotely and dynamically, when the client doesn't necessarily have them locally. We'll also discuss the security permissions needed to run RMI servers, clients, and registries. In the meantime, just to see the code running, make sure that all of the example classes are in the CLASSPATH of the client, the server process, and the RMI registry. Also, to avoid any potential security permission issues, you can make a special security policy file with the following contents:

     grant {         permission java.security.AllPermission;     }; 

Then run your client and server JVMs using this security policy:

     > java -Djava.security.policy=allpermissions.policy . . . 

This policy file will effectively disable all permission checks in the JVM, so it is definitely not recommended for use in production environments. But it's very handy for experimentation and debugging purposes.

13.2.2. RMI Architecture

Now that we've seen a complete example of an RMI object in action, let's look at what makes remote objects work, starting with an overview of the underlying RMI architecture . There are three layers that comprise the basic remote object communication facilities in RMI:


Stub/skeleton layer

Provides the interface that client and server application objects use to interact with each other.


Remote reference layer

The middleware between the stub/skeleton layer and the underlying transport protocol. This layer handles the creation and management of remote object references.


Transport protocol layer

The binary data protocol that sends remote object requests over the wire.

These layers interact with one another as shown in Figure 13-1. In this figure, the server is the application that provides remotely accessible objects, while the client is any remote application that communicates with these server objects.

In a distributed object system, the distinctions between clients and servers can get pretty blurry at times. Consider the case in which one process registers a remote-enabled object with the RMI naming service, and a number of remote processes are accessing it. We might be tempted to call the first process the server and the other processes the clients. But what if one of the clients calls a method on the remote object, passing a reference to an RMI object that's local to the client. Now the server has a reference to and is using an object exported from the client, which turns the tables somewhat. The "server" is really the server for one object and the client of another object, and the "client" is a client and a server, too. For the sake of discussion, we refer to a process in a distributed application as a server or client if its role in the overall system is generally limited to one or the other. In peer-to-peer systems, in which there is no clear client or server, we refer to elements of the system in terms of application-specific roles (e.g., chat participant, chat facilitator).

Figure 13-1. The RMI runtime architecture


As you can see in Figure 13-1, a client makes a request of a remote object using a client-side stub; the server object receives this request from a server-side object skeleton. A client initiates a remote method invocation by calling a method on a stub object. The stub maintains an internal reference to the remote object it represents and forwards the method invocation request through the remote reference layer by marshaling the method arguments into serialized form and asking the remote reference layer to forward the method request and arguments to the appropriate remote object. Marshaling involves converting local objects into portable form so that they can be transmitted to a remote process. Each object is checked as it is marshaled, to determine whether it implements the java.rmi.Remote interface. If it does, its remote reference is used as its marshaled data. If it isn't a Remote object, the argument is serialized into bytes that are sent to the remote host and reconstituted into a copy of the local object. If the argument is neither Remote nor Serializable, the stub throws a java.rmi.MarshalException back to the client.

If the marshaling of method arguments succeeds, the client-side remote reference layer receives the remote reference and marshaled arguments from the stub. This layer converts the client request into low-level RMI transport requests according to the type of remote object communication being used. In RMI, remote objects can (potentially) run under several different communication styles, such as point-to-point object references , replicated objects, or multicast objects. The remote reference layer is responsible for knowing which communication style is in effect for a given remote object and generating the corresponding transport-level requests. In the version of RMI bundled with Sun's JVM implementations, the only communication style provided out of the box is point-to-point object references, so this is the only style we'll discuss in this chapter. For point-to-point communication, the remote reference layer constructs a single network-level request and sends it over the wire to the sole remote object that corresponds to the remote reference passed along with the request.

On the server, the server-side remote reference layer receives the transport-level request and converts it into a request for the server skeleton that matches the referenced object. The skeleton converts the remote request into the appropriate method call on the actual server object, which involves unmarshaling the method arguments into the server environment and passing them to the server object. As you might expect, unmarshaling is the inverse procedure to the marshaling process on the client. Arguments sent as remote references are converted into local stubs on the server, and arguments sent as serialized objects are converted into local copies of the originals.

If the method call generates a return value or an exception, the skeleton marshals the object for transport back to the client and forwards it through the server reference layer. This result is sent back using the appropriate transport protocol, where it passes through the client reference layer and stub, is unmarshaled by the stub, and is finally handed back to the client thread that invoked the remote method.

13.2.3. RMI Object Services

On top of its remote object architecture, RMI provides some basic object services you can use in your distributed application. These include an object naming/registry service , a remote object activation service, and distributed garbage collection.

13.2.3.1. Naming/registry service

When a server process wants to export some RMI-based service to clients, it does so by registering one or more RMI-enabled objects with its local RMI registry (represented by the Registry interface). Each object is registered with a name clients can use to reference it. A client can obtain a stub reference to the remote object by asking for the object by name through the Naming interface. The Naming.lookup( ) method takes the fully qualified name of a remote object and locates the object on the network. The object's fully qualified name is in a URL-like syntax that includes the name of the object's host and the object's registered name.

It's important to note that, although the Naming interface is a default naming service provided with RMI, the RMI registry can be tied into other naming services by vendors. Sun has provided a binding to the RMI registry through JNDI, for example. See Chapter 9 for more details on how JNDI can be used to look up objects (remote or otherwise).

Once the lookup( ) method locates the object's host, it consults the RMI registry on that host and asks for the object by name. If the registry finds the object, it generates a remote reference to the object and delivers it to the client process, where it is converted into a stub reference that is returned to the caller. Once the client has a remote reference to the server object, communication between the client and the server commences as described earlier. We'll talk in more detail about the Naming and Registry interfaces later in this chapter.

13.2.3.2. Object activation service

The remote object activation service was introduced to RMI in JDK 1.2. It provides a way for server objects to be started on an as-needed basis. Without remote activation, a server object has to be registered with the RMI registry service from within a running Java virtual machine. A remote object registered this way is available only during the lifetime of the Java VM that registered it. If the server VM halts or crashes for some reason, the server object becomes unavailable and any existing client references to the object become invalid. Any further attempts by clients to call methods through these now-invalid references result in RMI exceptions being thrown back to the client.

The RMI activation service provides a way for a server object to be activated automatically when a client requests it. The activation service creates the server object dynamically within a new or existing virtual machine and obtains a reference to this newly created object for the client that caused the activation. A server object that wants to be activated automatically needs to register an activation method with the RMI activation daemon running on its host. We'll discuss the RMI activation service in more detail later in the chapter.

13.2.3.3. Distributed garbage collection

The last of the remote object services, distributed garbage collection , is a fairly automatic process that you as an application developer should never have to worry about. Every server that contains RMI-exported objects automatically maintains a list of remote references to the objects it serves. Each client that requests and receives a reference to a remote object, either explicitly through the registry/naming service or implicitly as the result of a remote method call, is issued this remote object reference through the remote reference layer of the object's host process. The reference layer automatically keeps a record of this reference in the form of an expirable "lease" on the object. When the client is done with the reference and allows the remote stub to go out of scope or when the lease on the object expires, the reference layer on the host automatically deletes the record of the remote reference and notifies the client's reference layer that this remote reference has expired. The concept of expirable leases, as opposed to strict on/off references, is used to deal with situations in which a client-side failure or a network failure keeps the client from notifying the server that it is done with its reference to an object.

When an RMI object has no further remote references recorded in the remote reference layer, then its eligibility for garbage collection is based solely on its local references, like any other Java object. If there are also no local references to the object (this reference list is kept by the Java VM itself as part of its normal garbage-collection algorithm), the object is marked as garbage and picked up by the next run of the system garbage collector.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net