Remote Proxies
When an object whose method you want to call is running on another computer, you cannot call the method directly, so you must find another way to communicate with it. You could open a socket on the remote machine and devise some protocol to pass messages to the remote object. Ideally, such a scheme would let you pass messages in almost the same way as if the object were local. You should be able to call methods on a proxy object that forwards the calls to the real object on the remote machine. In fact, such schemes have been realized, notably in CORBA (Common Object Request Broker Architecture), in ASP.NET (Active Server Pages for .NET), and in Java’s Remote Method Invocation (RMI).
RMI makes it fairly easy for a client to obtain a proxy object that forwards calls to a desired object that is active on another computer. It is well worth learning about RMI, as it is part of the underpinning of the Enterprise JavaBeans (EJB) specification, an important industry standard. Regardless of how industry standards evolve, the role of Proxy in distributed computing will continue into the foreseeable future, and RMI provides a good example of this pattern in action.
To experiment with RMI, you will need a good reference on this topic, such as Javaェ Enterprise in a Nutshell [Flanagan et al. 2002]. The following example is not a tutorial on RMI but merely points out the presence and value of Proxy within RMI applications. RMI and EJB bring in a number of new design concerns; you can’t simply make every object remote and get a reasonable system. We won’t go into those challenges; we’ll simply explore how RMI is a great example of Proxy.
Suppose that you decide to explore the workings of RMI, making an object’s methods available to a Java program running on another computer. The initial development step is to create an interface for the class to which you want to provide remote access. As an experimental project, suppose that you create a Rocket interface that is independent of existing code at Oozinoz:
package com.oozinoz.remote; import java.rmi.*; public interface Rocket extends Remote { void boost(double factor) throws RemoteException; double getApogee() throws RemoteException; double getPrice() throws RemoteException; }
The Rocket interface extends Remote, and the methods in the interface all declare that they throw RemoteException. The reasons for these aspects of the interface lie outside the scope of this book, but any book that teaches RMI should cover them. Your RMI source should also explain that, to act as a server, the implementation of your remote interface can subclass _UnicastRemoteObject, as Figure 11.5 shows.
Figure 11.5 To use RMI, you can first define the interface you want for messages that pass between computers and then create a subclass of UnicastRemoteObject that implements it.
Your plan is for RocketImpl objects to be active on a server and to be available through a proxy that is active on a client. The code for RocketImpl is simple.
package com.oozinoz.remote; import java.rmi.*; import java.rmi.server.UnicastRemoteObject;
public class RocketImpl extends UnicastRemoteObject implements Rocket { protected double price; protected double apogee; public RocketImpl(double price, double apogee) throws RemoteException { this.price = price; this.apogee = apogee; } public void boost(double factor) { apogee *= factor; } public double getApogee() { return apogee; } public double getPrice() { return price; } }
An instance of RocketImpl can be active on one machine and can be accessed by a Java program running on another machine. For this to work, a client needs a proxy for the RocketImpl object. This proxy needs to implement the Rocket interface and must have the additional features required to communicate with a remote object. A great benefit of RMI is that it automates the construction of this proxy. To generate the proxy, place the RocketImpl.java file and the Rocket.java interface file below the directory where you will run the RMI registry:
c:\rmi>dir /b com\oozinoz\remote RegisterRocket.class RegisterRocket.java Rocket.class Rocket.java RocketImpl.class RocketImpl.java ShowRocketClient.class ShowRocketClient.java
To create the RocketImpl stub that facilitates remote communication, run the RMI compiler that comes with the JDK:
c:\rmi> rmic com.oozinoz.remote.RocketImpl
Note that the rmic executable takes a class name, not the filename, as an argument. Earlier versions of the JDK constructed separate files for use on the client and server machines. As of version 1.2, the RMI compiler creates a single stub file that both the client and server machines need. The rmic command creates a RocketImpl_Stub class:
c:\rmi>dir /b com\oozinoz\remote RegisterRocket.class RegisterRocket.java Rocket.class Rocket.java RocketImpl.class RocketImpl.java RocketImpl_Stub.class ShowRocketClient.class ShowRocketClient.java
To make an object active, you must register it with an RMI registry running on the server. The rmiregistry executable comes as part of the JDK. When you run the registry, specify the port that the registry will listen to:
c:\rmi> rmiregistry 5000
With the registry running on the server machine, you can create and register a _RocketImpl object:
package com.oozinoz.remote; import java.rmi.*; public class RegisterRocket { public static void main(String[] args) { try { // Challenge! Naming.rebind( "rmi://localhost:5000/Biggie", biggie); System.out.println("Registered biggie"); } catch (Exception e) { e.printStackTrace(); } } }
If you compile and run this code, the program displays a confirmation that the rocket is registered:
Registered biggie
You need to replace the _//Challenge! line in the RegisterRocket class with code that creates a biggie object that models a rocket. The remaining code in the main() method registers this object. A description of the mechanics of the _Naming class is outside the scope of this discussion. However, you should have enough information to create the biggie object that this code registers.
Challenge 11.4
Replace the //Challenge! line with a declaration and instantiation of the biggie object. Define biggie to model a rocket with a price of $29.95 and an apogee of 820 meters.
A solution appears on page 377.
Running the RegisterRocket program makes a RocketImpl object—specifically, biggie—available on a server. A client that runs on another machine can access biggie if the client has access to the Rocket interface and the RocketImpl_Stub class. If you are working on a single machine, you can still test out RMI, accessing the server on localhost rather than on another host.
package com.oozinoz.remote; import java.rmi.*; public class ShowRocketClient { public static void main(String[] args) { try { Object obj = Naming.lookup( "rmi://localhost:5000/Biggie");
Rocket biggie = (Rocket) obj; System.out.println( "Apogee is " + biggie.getApogee()); } catch (Exception e) { System.out.println( "Exception while looking up a rocket:"); e.printStackTrace(); } } }
When this program runs, it looks up an object with the registered name of “Biggie.” The class that is serving this name is RocketImpl, and the object obj that lookup() returns will be an instance of RocketImpl_Stub class. The RocketImpl_Stub class implements the Rocket interface, so it is legal to cast the object obj as an instance of the Rocket interface. The RocketImpl_Stub class actually subclasses a RemoteStub class that lets the object communicate with a server.
When you run the ShowRocketClient program, it prints out the apogee of a “Biggie” rocket.
Apogee is 820.0
Through a proxy, the getApogee() call is forwarded to an implementation of the Rocket interface that is active on a server.
Challenge 11.5
Figure 11.6 shows the getApogee() call being forwarded. The rightmost object appears in a bold outline, indicating that it is active outside the _ShowRocketClient program. Fill in the class names of the unlabeled objects in this figure.
A solution appears on page 377.
Figure 11.6 This diagram, when completed, will show the flow of messages in an RMI-based distributed application.
The benefit of RMI is that it lets client programs interact with a local object that is a proxy for a remote object. You define the interface for the object that you want clients and servers to share. RMI supplies the communication mechanics and isolates both server and client from the knowledge that two implementations of Rocket are collaborating to provide nearly seamless interprocess communication.