Section 13.5. Accessing Remote Objects as a Client


13.5. Accessing Remote Objects as a Client

Now that we've defined a remote object interface and its server implementation and generated the stub and skeleton classes that RMI uses to establish the link between the server object and the remote client, it's time to look at how you make your remote objects available to remote clients.

13.5.1. The Registry and Naming Services

The first remote object reference in an RMI distributed application is typically obtained through the RMI registry facility and the Naming interface. Every host that wants to export remote references to local Java objects must be running an RMI registry daemon of some kind. A registry daemon listens (on a particular port) for requests from remote clients for references to objects served on that host. The standard JDK distribution provides an RMI registry daemon, rmiregistry. This utility simply creates a Registry object that listens to a specified port and then goes into a wait loop, waiting for local processes to register objects with it or for clients to connect and look up RMI objects in its registry. You start the registry daemon by running the rmiregistry command, with an optional argument that specifies a port to listen to:

         objhost% rmiregistry 5000 & 

Without the port argument, the RMI registry daemon listens on port 1099. Typically, you run the registry daemon in the background (i.e., put an & at the end of the command on a Unix system or run start rmiregistry [port] in a command prompt on a Windows system) or run it as a service at startup.

Once the RMI registry is running on a host, you can register remote objects with it using one of these classes: the java.rmi.registry.Registry interface, the java.rmi.registry.LocateRegistry class, or the java.rmi.Naming class.

A Registry object represents an interface to a local or remote RMI object registry. The bind( ) and rebind( ) methods can register an object with a name in the local registry, where the name for an object can be any unique string. If you try to bind( ) an object to a name that has already been used, the registry throws an AlreadyBoundException. If you think that an object may already be bound to the name you want to register, use the rebind( ) method instead. You can remove an object binding using the unbind( ) method. Note that these three methods (bind( ), rebind( ), and unbind( )) can be called only by clients running on the same host as the registry. If a remote client attempts to call these methods, the client receives a java.rmi.AccessException. You can locate a particular object in the registry using the lookup( ) method, while list( ) returns the names of all the objects registered with the local registry. Note that only Remote objects can be bound to names in the Registry. Remote objects are capable of supporting remote references. Standard Java classes aren't, so they can't be exported to remote clients through the Registry.

The LocateRegistry class provides a set of static methods a client can use to get references to local and remote registries, in the form of Registry objects. There are four versions of the static getregistry( ) method, so that you can get a reference to either a local registry or a remote registry running on a particular host, listening to either the default port (1099) or a specified port. There's also a static createRegistry( ) method that takes a port number as an argument. This method starts a registry running within the current Java VM on the given local port and returns the Registry object it creates.

Using the LocateRegistry and Registry interfaces, we can register one of our AccountImpl remote objects on the local host with the following code:

         AccountImpl server = new AccountImpl("Jim.Farley");         Registry localRegistry = LocateRegistry.getRegistry( );         try {           localRegistry.bind("JimF", server);         }         catch (RemoteException re) { // Handle failed remote operation }         catch (AlreadyBoundException abe) { // Already one there }         catch (AccessException ae) { // Shouldn't happen, but... } 

If this operation is successful (i.e., it doesn't raise any exceptions), the local registry has an AccountImpl remote object registered under the name JimF. Remote clients can now look up the object using a combination of the LocateRegistry and Registry interfaces or take the simpler approach and use the Naming class.

The Naming class lets a client look up local and remote objects using a URL-like naming syntax. The URL of a registered RMI remote object is typically in the format shown in Figure 13-3. Notice that the only required element of the URL is the actual object name. The protocol defaults to rmi:, the hostname defaults to the local host, and the port number defaults to 1099. Note that the default Naming class provided with Sun's JDK accepts only the rmi: protocol on object URLs. If you attempt to use any other protocol, a java.net.MalformedURLException is thrown by the lookup( ) method.

Figure 13-3. Anatomy of an RMI object URL


If we have a client running on a remote host that wants to look up the AccountImpl we registered, and the AccountImpl object is running on a host named rmiremote.farley.org, the client can get a remote reference to the object with one line of code:

     Account jimsAccount =         (Acount)Naming.lookup("rmi://rmiremote.farley.org/JimF"); 

If we have a client running on the same host as the AccountImpl object, the remote reference can be retrieved using the degenerate URL:

     Account jimsAccount = (Account)Naming.lookup("JimF"); 

Alternatively, you can use the LocateRegistry and Registry interfaces to look up the same object, using an extra line of code to find the remote Registry through the LocateRegistry interface:

     Registry rmtRegistry = LocateRegistry.getRegistry("rmiremote.farley.org");     Account jimsAccount =       (Account)rmtRegistry.lookup("JimF"); 

When you look up objects through an actual Registry object, you don't have the option of using the URL syntax for the name, because you don't need it. The hostname and port of the remote host are specified when you locate the Registry through the LocateRegistry interface, and the RMI protocol is implied, so all you need is the registered name of the object. With the Naming class, you can reduce a remote object lookup to a single method call, but the name must now include the host, port number, and registered object name, bundled into a URL. Internally, the Naming object parses the host and port number from the URL for you, finds the remote Registry using the LocateRegistry interface, and asks the Registry for the remote object using the object name in the URL.

The principal use for the Registry and Naming classes in an RMI application is as a means to bootstrap your distributed application. A server process typically exports just a few key objects through its local RMI registry daemon. Clients look up these objects through the Naming facility to get remote references to them. Any other remote objects that need to be shared between the two processes can be exported through remote method calls.

13.5.2. Remote Method Arguments and Return Values

As already mentioned, a critical element of executing a remote method call is the marshaling and unmarshaling of the method arguments and, once the method has executed, the reverse marshaling and unmarshaling of the method's return value. RMI handles this process for you automatically, but you need to understand how different types of objects are transmitted from the method caller to the server object and back again. More importantly, you need to know which types of objects can't be used in remote method calls at all.

When you call a method on a remote object, the arguments to the method have to be serializable. That is, they need to be primitive Java data types (like int, float, etc.) or Java objects that implement java.io.Serializable. The same restriction applies to the return value of the remote method. This restriction is enforced at runtime, when you actually make the remote method call, rather than at compile time.

The RMI stub and skeleton layer decides how to send method arguments and return values over the network, based on whether a particular object is Remote, Serializable, or neither:

  • If the object is a Remote object, a remote reference for the object is generated, and the reference is marshaled and sent to the remote process. The remote reference is received on the other end and converted into a stub for the original object. This process applies to both method arguments and return values.

  • If the object is Serializable but not Remote, the object is serialized and streamed to the remote process in byte form. The receiver converts the bytes into a copy of the original object.

  • If the method argument or return value is not serializable (i.e., it's not a primitive data type or an object that implements Serializable), the object can't be sent to the remote client, and a java.rmi.MarshalException is thrown.

The principal difference between remote and nonremote objects is that remote objects are sent by reference, while nonremote, serializable objects are sent by copy. In other words, a remote reference maintains a link to the original object it references, so changes can be made to the original object through the remote stub. If the server object calls update methods on an argument to a remote method and you want the updates to be made on the original object on the client side, the argument needs to be a Remote object that automatically exports a stub to the server object. Similarly, if the return value of a remote method call is intended to be a reference to an object living on the server, the server implementation needs to ensure that the object returned is a Remote object.

It's important to consider, whenever you pass either a remote reference or a serializable object from one VM to another in a remote method argument, whether the classes needed to unmarshal the method argument are available on the other side of the method call. If the receiver of the remote method argument has the necessary class definitions (the remote interface and stub class for remote references or the actual class definition for serializable objects) in its local CLASSPATH or some other local ClassLoader context, then you're fine. If it doesn't, you need to configure things so that the receiver of the method call can dynamically load the needed classes from the caller. We discuss how this happens in "Dynamic Classloading" later in this chapter.

13.5.3. Factory Classes

When a reference to a remote object is obtained through the RMI registry and then used to request additional remote references, the registered remote object is often referred to as a factory class.

Factory classes are useful in distributed applications that use remote objects, because in most cases you can't predict beforehand the kind and number of remote objects that will need to be shared between two processes. To make a remote object visible to clients through the RMI registry service, you need to explicitly create the object inside a Java VM on the server and then register that object using the bind( ) or rebind( ) method on the Registry. Using remote references obtained through method calls on factory objects, however, the client application can dynamically request the creation of new remote objects, without the objects being registered individually with the server registry.

As an example, suppose we're building a remote banking system, using the Account object we've been working with throughout this chapter. We want to set up a centralized server that provides account services to remote clients running on PCs, embedded in ATMs, and the like. On the server, we could run an RMI registry, create an Account object for every account we have on record, and register each one with the RMI registry service using the account name. In this scheme, registering accounts with the RMI registry goes something like this:

     Registry local = LocateRegistry.getRegistry( );     local.bind("Abrams, John", new AccountImpl("John Abrams"));     local.bind("Barts, Homer", new AccountImpl("Homer Barts"));      .      .      . 

As you can imagine, this is quite unwieldy in practice. Starting the server can take a long time, as thousands of accounts need to be registered, many of them unnecessarily, since many accounts may not see any activity before the next downtime. More importantly, accounts that are created or closed during the server's lifetime somehow need to be added to or removed from the RMI registry, as well as to and from the bank's database of accounts. A much more sensible approach is to define a factory class for Account objects, along the lines of the following interface:

     import java.rmi.Remote;     import java.rmi.RemoteException;     public interface AccountManager extends Remote {       public Account getAccount(String name) throws RemoteException;       public boolean newAccount(Account s) throws RemoteException;     } 

AccountManager lets a client ask for an account by name, using the getAccount( ) remote method. The method returns a reference to an Account object that corresponds to the account. Once the client has the Account reference, transactions against the account can be done through method calls on the Account object. The AccountManager also has a newAccount( ) method that allows clients to add new accounts to the manager's underlying database.

The server implementation of the getAccount( ) method simply needs to look up the named account in the account database, create an AccountImpl object to represent the account, and return the object to the remote client as a remote reference. Since Account objects are Remote objects, the RMI remote reference layer automatically creates a remote reference for the Account object, and the client that called the getAccount( ) method receives a stub for the Account object on the server.

Using the factory object to find accounts is more manageable than using the RMI registry. The bank maintains a database of accounts and their status, so the server implementation of the AccountManager can access that database directly to find accounts and create corresponding Account remote objects. Trying to keep the RMI registry in sync with the bank database makes the registry an unnecessary shadow of the main database of accounts, giving the bank two databases to maintain.



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