thread1 sum thread2
5. Open a third window and run the client
5.4 Using RMI Meaningfully
In a realistic RMI application, multiple methods and probably multiple objects will be employed. With such real-world applications, there are two possible strategies that may be adopted, as described below.
• Use a single instance of the implementation class to hold instance(s) of a class whose methods are to be called remotely. Pass instance(s) of the latter class as argument(s) of the constructor for the implementation class.
• Use the implementation class directly for storing required data and methods, creating instances of this class, rather than using separate class(es).
Some authors use the first strategy, while others use the second. Each approach has its merits and both will be illustrated below by implementing the same application, so that the reader may compare the two techniques and choose his/her own preference.
Example
This application will make bank account objects available to connecting clients, which may then manipulate these remote objects by invoking their methods. For simplicity's sake, just four account objects will be created and the practical considerations relating to security of such accounts will be ignored completely!
Each of the above two methods will be implemented in turn...
Method 1
Instance variables and associated methods for an individual account will be encapsulated within an application class called Account. If this class does not already exist, then it must be created, adding a further step to the four steps specified in Section 5.2. This step will be inserted as step 3 in the description below.
1. Create the interface.
Our interface will be called Bank1and will provide access to details of all accounts via method getBankAccounts. This method returns a Vector of Account objects that will be declared within the implementation class. The code is shown below:
import java.rmi.*;
import java.util.Vector;
public interface Bank1 extends Remote {
public Vector<Account> getBankAccounts()
throws RemoteException;
}
2. Define the implementation.
The code for the implementation class provides both a definition for the above method and the definition for a constructor to set up the Vector of Account objects:
import java.rmi.*;
import java.rmi.server.*;
import java.util.Vector;
public class Bank1Impl extends UnicastRemoteObject
implements Bank1 {
//Declare the Vector that will hold Account //objects...
private Vector<Account> acctInfo;
//The constructor must be supplied with a Vector of //Account objects...
public Bank1Impl(Vector<Account> acctVals)
throws RemoteException
{
acctInfo = acctVals;
}
//Definition for the single interface method...
public Vector<Account> getBankAccounts()
throws RemoteException
{
return acctInfo;
} }
3. Create any required application classes.
In this example, there is only class Account to be defined. Since it is to be used in the return value for our interface method, it must be declared to implement the Serializable interface (contained in package java.io).
public class Account implements java.io.Serializable {
//Instance variables...
private int acctNum;
private String surname;
private String firstNames;
private double balance;
//Constructor...
public Account(int acctNo, String sname,
String fnames, double bal)
public double getBalance() {
return balance;
}
public double withdraw(double amount) {
if ((amount>0) && (amount<=balance)) return amount;
else
return 0;
}
public void deposit(double amount) {
if (amount > 0)
balance += amount;
} }
4. Create the server process.
The code for the server class sets up a Vector holding four initialised Account objects and then creates an implementation object, using the Vector as the argument of the constructor. The reference to this object is bound to the programmer-chosen name Accounts (which must be specified as part of a URL identifying the host machine) and placed in the registry. The server code is shown below.
import java.rmi.*;
import java.util.Vector;
public class Bank1Server {
private static final String HOST = "localhost";
public static void main(String[] args)
throws Exception {
//Create an initialised array of four Account //objects...
Account[] account =
{new Account(111111,"Smith","Fred James",112.58), new Account(222222,"Jones","Sally",507.85),
new Account(234567,"White","Mary Jane",2345.00), new Account(666666,"Satan","Beelzebub",666.00)};
Vector<Account> acctDetails =
new Vector<Account>();
//Insert the Account objects into the Vector...
for (int i=0; i<account.length; i++) acctDetails.add(account[i]);
//Create an implementation object, passing the //above Vector to the constructor...
Bank1Impl temp = new Bank1Impl(acctDetails);
//Save the object's name in a String...
String rmiObjectName =
"rmi://" + HOST + "/Accounts";
//(Could omit host name, since 'localhost' would be //assumed by default.)
//Bind the object's name to its reference...
Naming.rebind(rmiObjectName,temp);
System.out.println("Binding complete...\n");
} }
5. Create the client process.
The client uses method lookup of class Naming to obtain a reference to the remote object, typecasting it into type Bank1. Once the reference has been retrieved, it can be used to execute remote method getBankAccounts. This returns a reference to the Vector of Account objects which, in turn, provides access to the individual Account objects. The methods of these Account objects can then be invoked as though those objects were local.
import java.rmi.*;
import java.util.Vector;
public class Bank1Client {
private static final String HOST = "localhost";
public static void main(String[] args) {
try {
//Obtain a reference to the object from the //registry and typecast it into the appropriate //type...
Bank1 temp = (Bank1)Naming.lookup(
"rmi://" + HOST + "/Accounts");
Vector<Account> acctDetails =
The steps for compilation and execution are the same as those outlined in the previous section for the Hello example, with the minor addition of compiling the source code for class Account. The steps are shown below.
1. Compile all files with javac.
This time, there are five files...
javac Bank1.java javac Bank1Impl.java javac Account.java javac Bank1Server.java javac Bank1Client.java
2. Compile the implementation class with the rmic compiler.
rmic -v1.2 Bank1Impl
This will cause a file with the name Bank1Impl_stub.class to be created.
3. Start the RMI registry.
Enter the following command:
rmiregistry
The contents of the registry window will be identical to the screenshot shown in Figure 5.2.
4. Open a new window and run the server.
From the new window, invoke the Java interpreter:
java Bank1Server Server output is as shown in Figure 5.3.
5. Open a third window and run the client.
Again, invoke the Java interpreter:
java Bank1Client Output is shown in Figure 5.5 below.
Figure 5.5 Output from the Bank1Client RMI program.
Once again, the server process and the RMI registry will need to be closed down by entering Ctrl-C in each of their windows.
Method 2
For this method, no separate Account class is used. Instead, the data and methods associated with an individual account will be defined directly in the implementation
class. The interface will make the methods available to client processes. The same four steps as were identified in Section 5.2 must be carried out, as described below.
1. Create the interface.
The same five methods that appeared in class Account in Method 1 are declared, but with each now declaring that it throws a RemoteException.
import java.rmi.*;
public interface Bank2 extends Remote {
public int getAcctNum()throws RemoteException;
public String getName()throws RemoteException;
public double getBalance()throws RemoteException;
public double withdraw(double amount)
throws RemoteException;
public void deposit(double amount)
throws RemoteException;
}
2. Define the implementation.
As well as holding the data and method implementations associated with an individual account, this class defines a constructor for implementation objects. The method definitions will be identical to those that were previously held within the Account class, of course.
import java.rmi.*;
import java.rmi.server.*;
public class Bank2Impl extends UnicastRemoteObject
implements Bank2 {
private int acctNum;
private String surname;
private String firstNames;
private double balance;
//Constructor for implementation objects...
public Bank2Impl(int acctNo, String sname,
String fnames, double bal) throws RemoteException {
acctNum = acctNo;
surname = sname;
firstNames = fnames;
balance = bal;
}
public int getAcctNum() throws RemoteException {
return acctNum;
}
public String getName() throws RemoteException {
return (firstNames + " " + surname);
}
public double getBalance() throws RemoteException {
return balance;
}
public double withdraw(double amount)
throws RemoteException {
if ((amount>0) && (amount<=balance)) return amount;
else
return 0;
}
public void deposit(double amount)
throws RemoteException {
if (amount > 0)
balance += amount;
} }
3. Create the server process.
The server class creates an array of implementation objects and binds each one individually to the registry. The name used for each object will be formed from concatenating the associated account number onto the word 'Account' (forming 'Account111111', etc.).
import java.rmi.*;
public class Bank2Server {
private static final String HOST = "localhost";
public static void main(String[] args)
throws Exception {
//Create array of initialised implementation //objects...
Bank2Impl[] account =
{new Bank2Impl(111111,"Smith",
"Fred James",112.58), new Bank2Impl(222222,"Jones","Sally",507.85), new Bank2Impl(234567,"White",
System.out.println("Binding complete...\n");
} }
4. Create the client process.
The client again uses method lookup, this time to obtain references to individual accounts (held in separate implementation objects):
import java.rmi.*;
public class Bank2Client {
private static final String HOST = "localhost";
private static final int[] acctNum =
{111111,222222,234567,666666};
public static void main(String[] args) {
try {
//Simply display all account details...
Output for this client will be exactly as shown in Figure 5.5 for Method 1.