Some reader favorites:
EJB fundamentals and session beans
Create a scrollable virtual desktop in Swing
Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API
If you are unfamiliar with RMI, I recommend jGuru's "Fundamentals of RMI" course. If you are unfamiliar with design patterns or the specifics of the Adapter pattern, I recommend Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, et al. Online tutorials are also available on the Patterns homepage. (See Resources for links to these materials.)
Initially, you must use RMI's protocol for making remote method calls. It would be beneficial, however, if most of your source code could keep using its existing protocol. The Adapter design pattern solves that problem by providing access to an object through a protocol that that object does not directly support. In this case, you should create adapter classes that wrap up the RMI protocol; this lets your existing code use a local protocol when making a method call on a remote object. Since an adapter class wraps up another object, it is frequently referred to as a wrapper class.
The example application for this article has a class called StatisticsCalculator that performs statistical calculations. Given a Vector of numbers, StatisticsCalculator provides methods that calculate statistical values like standard deviation, median, mode, and mean. A snippet of the code
is shown in Listing 1. (See Resources for the complete source code.)
public class StatisticsCalculator
{
private Vector values_;
public void setValues(Vector values)
{
values_ = values;
}
public double getStandardDeviation()
{
if ((values_ == null) || (values_.size() <= 1))
return 0;
.
.
.
}
}
Listing 1. The StatisticsCalculator class
A typical usage of the StatisticsCalculator class is shown in Listing 2.
StatisticsCalculator sc = new StatisticsCalculator();
Vector v = new Vector();
v.addElement(new Double(100));
v.addElement(new Double(200));
v.addElement(new Double(300));
sc.setValues(v);
System.out.println("Std Dev: " + sc.getStandardDeviation());
Listing 2. Client usage of a local StatisticsCalculator
If you run the StatisticsCalculator object on another system, the easiest way to access it on that remote system is with RMI. By following the standard steps
for RMI support, you can create an interface that extends java.rmi.Remote. Let's call it RemoteStatisticsCalculatorService; it is shown in Listing 3.
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Vector;
public interface RemoteStatisticsCalculatorService extends Remote
{
public static final String SERVICENAME =
"RemoteStatisticsCalculatorService";
public void setValues(Vector values) throws RemoteException;
public double getStandardDeviation() throws RemoteException;
}
Listing 3. The RemoteStatisticsCalculatorService interface
At this point, some people would modify the StatisticsCalculator class to directly implement the RemoteStatisticsCalculatorService. Having read Becker's article, though, I will implement the RemoteStatisticsCalculatorService interface with a wrapper class that provides remote access to a StatisticsCalculator object. The result, RemoteStatisticsCalculatorServer, is shown in Listing 4. It includes a main() method that creates the object being wrapped and passes it to the constructor.
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Enumeration;
import java.util.Vector;
public class RemoteStatisticsCalculatorServer
extends UnicastRemoteObject
implements RemoteStatisticsCalculatorService
{
// object being wrapped
private StatisticsCalculator calc_;
public RemoteStatisticsCalculatorServer(StatisticsCalculator calc)
throws RemoteException
{
calc_ = calc;
}
public void setValues(Vector values) throws RemoteException
{
calc_.setValues(values);
}
public double getStandardDeviation() throws RemoteException
{
return calc_.getStandardDeviation();
}
public static void main(String[] args)
{
try
{
// start the registry
Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
// create a StatisticsCalculator, and then bind our
// server object to the service
// name RemoteStatisticsCalculatorServer
registry.rebind(RemoteStatisticsCalculatorService.SERVICENAME,
new RemoteStatisticsCalculatorServer(new StatisticsCalculator()));
}
catch (RemoteException e)
{
System.out.println(e);
}
}
}
Listing 4. The StatisticsCalculator server program
With the RMI server program complete, you can finally change the code that uses a local StatisticsCalculator object. This program will now be an RMI client that attempts to establish a remote connection with an object that implements
the RemoteStatisticsCalculatorService. This client's code is shown in Listing 5.
try
{
// find the registry
Registry remoteRegistry = LocateRegistry.getRegistry(rmiHostName_);
// get a reference to the remote calculator
RemoteStatisticsCalculatorService sc =
(RemoteStatisticsCalculatorService)
remoteRegistry.lookup(RemoteStatisticsCalculatorService.SERVICENAME);
Vector v = new Vector();
v.addElement(new Double(100));
v.addElement(new Double(200));
v.addElement(new Double(300));
sc.setValues(v);
System.out.println("Std Dev: " + sc.getStandardDeviation());
}
catch (NotBoundException e)
{
System.out.println(e);
}
catch (RemoteException e)
{
System.out.println(e);
}
Listing 5. Client usage of a remote StatisticsCalculator
Much has changed between Listing 2 and Listing 5. In addition to adding a call to remoteRegistry.lookup(), the RMI protocol requires adding try/catch blocks for the RMI exceptions that can be thrown. So everywhere you use StatisticsCalculator in the existing code, you must add this remote exception handling. This can be a considerable burden, which you can avoid
by using the Adapter pattern for the RMI client code. The first step is to define a second interface that does not extend
java.rmi.Remote. I'll call it StatisticsCalculatorService; it is shown in Listing 6.
import java.util.Vector;
public interface StatisticsCalculatorService
{
public void setValues(Vector values);
public double getStandardDeviation();
}
Listing 6. The StatisticsCalculatorService interface
RMI is not mentioned in the StatisticsCalculatorService interface -- it just provides an abstraction of the functionality in the original StatisticsCalculator class. Now you can define an Adapter class for the client that wraps the RMI functionality for you. I'll call it StatisticsCalculatorAccess, since it provides access to a StatisticsCalculatorService.
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Vector;
public class StatisticsCalculatorAccess implements StatisticsCalculatorService
{
private String rmiHostName_ = "localhost";
// the remote calculator
private RemoteStatisticsCalculatorService remoteCalculator_;
public StatisticsCalculatorAccess()
{
try
{
// find the registry
Registry remoteRegistry = LocateRegistry.getRegistry(rmiHostName_);
// get a reference to the remote calculator
remoteCalculator_ =
(RemoteStatisticsCalculatorService)
remoteRegistry.lookup(RemoteStatisticsCalculatorService.SERVICENAME);
}
catch (NotBoundException e)
{
System.out.println(e);
}
catch (RemoteException e)
{
System.out.println(e);
}
}
public void setValues(Vector values)
{
try
{
remoteCalculator_.setValues(values);
}
catch (RemoteException e)
{
System.out.println(e);
}
}
public double getStandardDeviation()
{
double retVal = 0;
try
{
retVal = remoteCalculator_.getStandardDeviation();
}
catch (RemoteException e)
{
System.out.println(e);
}
return retVal;
}
}
Listing 7. An adapter for local access to a remote StatisticsCalculator
To modify your existing code to use StatisticsCalculatorAccess, you must change only one line, as shown in Listing 8.
StatisticsCalculatorService sc = new StatisticsCalculatorAccess();
Vector v = new Vector();
v.addElement(new Double(100));
v.addElement(new Double(200));
v.addElement(new Double(300));
sc.setValues(v);
System.out.println("Std Dev: " + sc.getStandardDeviation());
Listing 8. Client usage of the StatisticsCalculatorAccess adapter
If you compare Listing 8 with Listing 2, you will see that only the first line is different, because the StatisticsCalculatorAccess class is handling the RMI details. This lets you partition the application with RMI while minimizing the impact on your existing
code. Also, placing the RMI code in a wrapper class will result in a smaller impact if you eventually stop using RMI.
You can even take the idea a couple of steps further. Suppose that under some circumstances, you don't need a remote StatisticsCalculator: for example, the list of numbers is so small that sending them across the network for a remote-system computation isn't
worth the overhead. You can modify the StatisticsCalculatorAccess class so its constructor knows whether to create a local StatisticsCalculatorService or get access to a remote one. This is shown in Listing 9.
public class StatisticsCalculatorAccess implements StatisticsCalculatorService
{
private String rmiHostName_ = "localhost";
// the remote calculator
private RemoteStatisticsCalculatorService remoteCalculator_;
/ the local calculator
private StatisticsCalculatorService localCalculator_;
// state flag to indicate if we're wrapping a
// local or remote calculator
private boolean remote_;
public StatisticsCalculatorAccess()
{
this(true);
}
public StatisticsCalculatorAccess(boolean remote)
{
remote_ = remote;
if (remote)
{
// get access to a remote calculator
try
{
// find the registry
Registry remoteRegistry = LocateRegistry.getRegistry(rmiHostName_);
// get a reference to the remote calculator
remoteCalculator_ = (RemoteStatisticsCalculatorService)
remoteRegistry.lookup(RemoteStatisticsCalculatorService.SERVICENAME);
}
catch (NotBoundException e)
{
System.out.println(e);
}
catch (RemoteException e)
{
System.out.println(e);
}
}
else
{
// just create a local calculator
localCalculator_ = new StatisticsCalculator();
}
}
public void setValues(Vector values)
{
if (remote_)
{
try
{
remoteCalculator_.setValues(values);
}
catch (RemoteException e)
{
System.out.println(e);
}
}
else
{
localCalculator_.setValues(values);
}
}
public double getStandardDeviation()
{
double retVal = 0;
if (remote_)
{
try
{
retVal = remoteCalculator_.getStandardDeviation();
}
catch (RemoteException e)
{
System.out.println(e);
}
}
else
{
retVal = localCalculator_.getStandardDeviation();
}
return retVal;
}
}
Listing 9. A more flexible StatisticsCalculatorAccess adapter
The no-argument constructor will create a StatisticsCalculatorAccess object that is using a remote object. That means the code in Listing 8 remains unchanged if that is the desired behavior.
To use a local object, you can change the first line in Listing 8 to:
StatisticsCalculatorService sc = new StatisticsCalculatorAccess(false);
Countless other possibilities exist: the constructor could decide whether to use a local or remote object based on a remote
object's availability, or even on system load. By handling the details in the StatisticsCalculatorAccess class, you make your program flexible with minimal impact on existing source code.
I should tie up some loose ends: To make the code in Listing 9 work, I modified the StatisticsCalculator class (shown in Listing 1) to implement the new StatisticsCalculatorService interface. Also, since StatisticsCalculatorAccess now provides a constructor that will create a local StatisticsCalculator object, you might as well use it in the server program too. The modified RemoteStatisticsCalculatorServer is shown in Listing 10; the boldfaced text highlights the changes made since Listing 4.
public class RemoteStatisticsCalculatorServer extends UnicastRemoteObject implements RemoteStatisticsCalculatorService
{
private StatisticsCalculatorService calc_;
public RemoteStatisticsCalculatorServer(StatisticsCalculatorService calc) throws RemoteException
{
calc_ = calc;
}
public void setValues(Vector values) throws RemoteException
{
calc_.setValues(values);
}
public double getStandardDeviation() throws RemoteException
{
return calc_.getStandardDeviation();
}
public static void main(String[] args)
{
try
{
// start the registry
Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
// bind our server object to the service name RemoteStatisticsCalculatorServer
registry.rebind(RemoteStatisticsCalculatorService.SERVICENAME,
new RemoteStatisticsCalculatorServer(
new StatisticsCalculatorAccess(false) ));
}
catch (RemoteException e)
{
System.out.println(e);
}
}
}
Listing 10. Server program modified to use the more flexible adapter class
The finished application has two classes that contain main() methods: RemoteStatisticsCalculatorServer and StatisticsUser. To compile them, you can use the following commands:
javac RemoteStatisticsCalculatorServer javac StatisticsUser
The next step is to run rmic, the Java RMI compiler. It generates the required stub classes for RMI to use:
rmic RemoteStatisticsCalculatorServer
Then you can start the server program. You will need support for a TCP/IP "localhost" on your system:
java RemoteStatisticsCalculatorServer
Finally, on a separate command line, you can start the client program:
java StatisticsUser
The StatisticsUser program produces one line of output:
Std Dev: 100.0
Free Download - 5 Minute Product Review. When slow equals Off: Manage the complexity of Web applications - Symphoniq
![]()
Free Download - 5 Minute Product Review. Realize the benefits of real user monitoring in less than an hour. - Symphoniq