• Tidak ada hasil yang ditemukan

Using RMI Meaningfully

Remote Method Invocation (RMI)

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 fi rst 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 consid- erations 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 encap- sulated 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 specifi ed in Sect. 5.2 . This step will be inserted as step 3 in the description below.

1. Create the interface.

Our interface will be called Bank1 and will provide access to details of all accounts via method getBankAccounts . This method returns an ArrayList of Account objects that will be declared within the implementation class. The code is shown below:

import java.rmi.*;

import java.util.ArrayList;

public interface Bank1 extends Remote {

public ArrayList<Account> getBankAccounts()

throws RemoteException;

}

2. Defi ne the implementation.

The code for the implementation class provides both a defi nition for the above method and the defi nition for a constructor to set up the ArrayList s of Account objects:

import java.rmi.*;

import java.rmi.server.*;

import java.util.ArrayList;

public class Bank1Impl extends UnicastRemoteObject implements Bank1 {

//Declare the ArrayList that will hold Account //objects…

private ArrayList<Account> acctInfo;

//The constructor must be supplied with an ArrayList //of Account objects…

public Bank1Impl(ArrayList<Account> acctVals) throws RemoteException {

acctInfo = acctVals;

}

//Defi nition for the single interface method…

public ArrayList<Account> getBankAccounts()

throws RemoteException {

return acctInfo;

} }

3. Create any required application classes.

In this example, there is only class Account to be defi ned. 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 fi rstNames;

private double balance;

//Constructor…

public Account(int acctNo, String sname,

String fnames, double bal) {

acctNum = acctNo;

surname = sname;

fi rstNames = fnames;

balance = bal;

}

//Methods…

public int getAcctNum() {

return acctNum;

}

public String getName() {

return (fi rstNames + " " + surname);

}

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 an ArrayList holding four initialised Account objects and then creates an implementation object, using the ArrayList as the argu- ment of the constructor. The reference to this object is bound to the programmer- chosen name Accounts (which must be specifi ed 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.ArrayList;

public class Bank1Server {

private static fi nal 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)};

ArrayList<Account> acctDetails =

new ArrayList<Account>();

//Insert the Account objects into the ArrayList … for (int i=0; i<account.length; i++)

acctDetails.add(account[i]);

//Create an implementation object, passing the //above ArrayList 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 ArrayList 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.ArrayList;

public class Bank1Client {

private static fi nal 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");

ArrayList<Account> acctDetails =

temp.getBankAccounts();

//Simply display all acct details…

for (int i=0; i<acctDetails.size(); i++) {

//Retrieve an Account object from the // ArrayList …

Account acct = acctDetails.get(i);

//Now invoke methods of Account object //to display its details…

System.out.println("\nAccount number: "

+ acct.getAcctNum());

System.out.println("Name: "

+ acct.getName());

System.out.println("Balance: "

+ acct.getBalance());

} }

catch(ConnectException conEx) {

System.out.println(

"Unable to connect to server!");

System.exit(1);

}

catch(Exception ex) {

ex.printStackTrace();

System.exit(1);

} } }

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 fi les with javac.

This time, there are fi ve fi les…

javac Bank1.java javac Bank1Impl.java javac Account.java javac Bank1Server.java javac Bank1Client.java 2. Start the RMI registry.

Enter the following command:

rmiregistry

The contents of the registry window will be identical to the screenshot shown in Fig. 5.2 .

3. Open a new window and run the server.

From the new window, invoke the Java interpreter:

java Bank1Server Server output is as shown in Fig. 5.3 . 4. Open a third window and run the client.

Again, invoke the Java interpreter:

java Bank1Client Output is shown in Fig. 5.5 below.

Fig. 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 defi ned directly in the implementation class. The interface will make the methods available to client processes. The same four steps as were identifi ed in Sect. 5.2 must be carried out, as described below.

1. Create the interface.

The same fi ve 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. Defi ne the implementation.

As well as holding the data and method implementations associated with an indi- vidual account, this class defi nes a constructor for implementation objects. The method defi nitions 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 fi rstNames;

private double balance;

//Constructor for implementation objects…

public Bank2Impl(int acctNo, String sname,

String fnames, double bal) throws RemoteException {

acctNum = acctNo;

surname = sname;

fi rstNames = fnames;

balance = bal;

}

public int getAcctNum() throws RemoteException {

return acctNum;

}

public String getName() throws RemoteException {

return (fi rstNames + " " + 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 fi nal 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",

"Mary Jane",2345.00), new Bank2Impl(666666,"Satan",

"Beelzebub",666.00)};

for (int i=0; i<account.length; i++) {

int acctNum = account[i].getAcctNum();

/*

Generate each account name (as a concatenation of 'Account' and the account number) and bind it to the appropriate object reference in the array…

*/

Naming.rebind("rmi://" + HOST + "/Account"

+ acctNum, account[i]);

}

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 fi nal String HOST = "localhost";

private static fi nal int[] acctNum =

{111111,222222,234567,666666};

public static void main(String[] args) {

try {

//Simply display all account details…

for (int i=0; i<acctNum.length; i++) {

/*

Obtain a reference to the object from the registry and typecast it into the

appropriate type…

*/

Bank2 temp =

(Bank2)Naming.lookup("rmi://" + HOST + "/Account" + acctNum[i]);

//Now invoke the methods of the interface to //display details of associated account…

System.out.println("\nAccount number: "

+ temp.getAcctNum());

System.out.println("Name: "

+ temp.getName());

System.out.println("Balance: "

+ temp.getBalance());

} }

catch(ConnectException conEx) {

System.out.println(

"Unable to connect to server!");

System.exit(1);

}

catch(Exception ex) {

ex.printStackTrace();

System.exit(1);

} } }

Output for this client will be exactly as shown in Fig. 5.5 for Method 1 .