This pattern was previously described in GoF95.
Let us consider the following code sample:
//Client
class Customer{
public void someMethod(){
//Create the Service Provider Instance
FileUtil futilObj=new FileUtil();
//Access the Service
futilObj.writeToFile(“Some Data”);
}
}
As part of its implementation, the Customer class creates an instance of the FileUtil class and directly accesses its services. In other words, for a client object, the way of accessing a FileUtil object is fairly straightforward. From the implementation it seems to be the most commonly used way for a client object to access a service provider object. In contrast, sometimes a client object may not be able to access a service provider object (also referred to as a target object) by normal means. This could happen for a variety of reasons depending on:
In such cases, instead of having client objects to deal with the special requirements for accessing the target object, the Proxy pattern suggests using a separate object referred to as a proxy to provide a means for different client objects to access the target object in a normal, straightforward manner.
The Proxy object offers the same interface as the target object. The Proxy object interacts with the target object on behalf of a client object and takes care of the specific details of communicating with the target object. As a result, client objects are no longer needed to deal with the special requirements for accessing the services of the target object. A client can call the Proxy object through its interface and the Proxy object in turn forwards those calls to the target object. Client objects need not even know that they are dealing with Proxy for the original object. The Proxy object hides the fact that a client object is dealing with an object that is either remote, unknown whether instantiated or not, or needs special authentication. In other words, a Proxy object serves as a transparent bridge between the client and an inaccessible remote object or an object whose instantiation may have been deferred.
Proxy objects are used in different scenarios leading to different types of proxies. Let us take a quick look at some of the proxies and their purpose.
Note: Table 23.1 lists different types of Proxy objects. In this chapter, only the remote proxy is discussed in detail. Some of the other proxy types are discussed as separate patterns later in this book.
From the discussion of different Proxy objects, it can be observed that there are two main characteristics of a Proxy object:
In this context, it looks very similar to some of the other patterns discussed earlier in this book. Let us see in detail the similarities and differences between the Proxy pattern and some of the other similar patterns.
Table 23.1 List of Different Proxy Types
In Java, the concept of Remote Method Invocation (RMI) makes extensive use of the Remote Proxy pattern. Let us take a quick look at the concept of RMI and different components that facilitate the RMI communication process.
RMI enables a client object to access remote objects and invoke methods on them as if they are local objects (Figure 23.1).
The following different components working together provide the stated RMI functionality:
Figure 23.1 Client’s View of Its Communication with a Remote Object Using RMI
methods in the remote object that can be accessed by its clients. In other words, the remote interface can be seen as the client’s view of the remote object.
Requirements:
▪ Remote Object — A remote object is responsible for implementing the methods declared in the associated remote interface.
Requirements:
▪ RMI Registry — RMI registry provides the storage area for holding different remote objects.
▪ Client — Client is an application object attempting to use the remote object.
▪ RMIC: Java RMI Stub Compiler — Once a remote object is compiled successfully, RMIC, the Java RMI stub compiler can be used to generate stub and skeleton class files for the remote object. Stub and skeleton classes are generated from the compiled remote object class. These stub and skeleton classes make it possible for a client object to access the remote object in a seamless manner.
The following section describes how the actual communication takes place between a client and a remote object.
In general, a client object cannot directly access a remote object by normal means. In order to make it possible for a client object to access the services of a remote object as if it is a local object, the RMIC-generated stub of the remote object class and the remote interface need to be copied to the client computer.
The stub acts as a (Remote) proxy for the remote object and is responsible for forwarding method invocations on the remote object to the server where the actual remote object implementation resides. Whenever a client references the remote object, the reference is, in fact, made to a local stub. That means, when a client makes a method call on the remote object, it is first received by the local stub instance. The stub forwards this call to the remote server. On the server the RMIC generated skeleton of the remote object receives this call.
Figure 23.2 The Actual RMI Communication Process
The skeleton is a server side object and it does not need to be copied to the client computer. The skeleton is responsible for dispatching calls to the actual remote object implementation. Once the remote object executes the method, results are sent back to the client in the reverse direction.
Figure 23.2 shows the actual RMI communication process.
For more information on the Java RMI technology, I recommend reading the RMI tutorial at java.sun.com.
It can be seen from the RMI communication discussion that the stub class, acting as a remote proxy for the remote object, makes it possible for a client to treat a remote object as if it is available locally. Thus, any application that uses RMI contains an implicit implementation of the Proxy pattern.
During the discussion of the Façade pattern, we built a simple customer data management application to validate and save the input customer data. Our design consisted of a set of three subsystem classes — Account, Address and CreditCard — representing different parts of the customer data.
Before applying the Façade pattern, the client AccountManager was designed to directly interact with the three subsystem classes to validate and save the customer data. Applying the Façade pattern, we defined a CustomerFacade Façade object to deal with the three subsystem classes on behalf of the client AccountManager (Figure 23.3).
In this application, both the subsystem components and the Façade object are local to the AccountManager client object.
Figure 23.3 Customer Data Management Application for the Local Mode of Operation: Class Association
Let us build a different version of the same application that runs in the remote mode. In the remote mode, the application makes use of remote objects using the Java RMI technology.
In designing the application for the remote mode of operation, we would move all of the subsystem components (Account, Address and CreditCard) and the Façade (CustomerFacade) to a remote server (Figure 23.4) with the following advantages:
Let us start designing our customer data management application for the remote mode of operation using the RMI technology.
As the first step, let us define a remote interface CustomerIntr that:
public interface CustomerIntr extends java.rmi.Remote {
void setAddress(String inAddress) throws RemoteException;
void setCity(String inCity) throws RemoteException;
Figure 23.4 Customer Data Management Application for the Remote Mode of Operation: Class Association
void setState(String inState) throws RemoteException;
void setFName(String inFName) throws RemoteException;
void setLName(String inLName) throws RemoteException;
void setCardType(String inCardType) throws RemoteException;
void setCardNumber(String inCardNumber)
throws RemoteException;
void setCardExpDate(String inCardExpDate)
throws RemoteException;
boolean saveCustomerData() throws RemoteException;
}
Let us redesign the CustomerFacade Façade class (Listing 23.1) so that it now implements the CustomerIntr remote interface. Different client objects can interface with the subsystem objects by invoking the CustomerIntr methods on the concrete CustomerFacade. Figure 23.5 shows the structure and the associ-ation between the CustomerFacade and the remote interface CustomerIntr it implements.
Because the subsystem components are local to the CustomerFacade class, it continues to refer to them as local objects without any changes in the way it instantiates and invokes methods on them. When executed, the CustomerFa-cade creates an instance of itself and keeps it in the RMI registry with a reference name. Client objects will be able to obtain this copy of the remote object using the reference name.
Listing 23.1 CustomerFacade Class: Revised
public class CustomerFacade extends UnicastRemoteObject
implements CustomerIntr {
private String address;
private String city;
private String state;
private String cardType;
private String cardNumber;
private String cardExpDate;
private String fname;
private String lname;
public CustomerFacade() throws RemoteException {
super();
System.out.println("Server object created");
}
public static void main(String[] args) throws Exception {
String port = "1099";
String host = "localhost";
//Check for hostname argument
if (args.length == 1) {
host = args[0];
}
if (args.length == 2) {
port = args[1];
}
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
//Create an instance of the server
CustomerFacade facade = new CustomerFacade();
//Bind it with the RMI Registry
Naming.bind("//" + host + ":" + port + "/CustomerFacade”,
facade);
System.out.println("Service Bound…");
}
public void setAddress(String inAddress)
throws RemoteException {
address = inAddress;
}
public void setCity(String inCity)
throws RemoteException{ city = inCity;
}
public void setState(String inState)
throws RemoteException{ state = inState;
}
public void setFName(String inFName)
throws RemoteException{ fname = inFName;
}
public void setLName(String inLName)
throws RemoteException{ lname = inLName;
}
public void setCardType(String inCardType)
throws RemoteException {
cardType = inCardType;
}
public void setCardNumber(String inCardNumber)
throws RemoteException {
cardNumber = inCardNumber;
}
public void setCardExpDate(String inCardExpDate)
throws RemoteException {
cardExpDate = inCardExpDate;
}
public boolean saveCustomerData() throws RemoteException{
Address objAddress;
Account objAccount;
CreditCard objCreditCard;
/*
client is transparent from the following
set of subsystem related operations.
*/
boolean validData = true;
String errorMessage = "";
objAccount = new Account(fname, lname);
if (objAccount.isValid() == false) {
validData = false;
errorMessage = "Invalid FirstName/LastName";
}
objAddress = new Address(address, city, state);
if (objAddress.isValid() == false) {
validData = false;
errorMessage = "Invalid Address/City/State";
}
objCreditCard = new CreditCard(cardType, cardNumber,
cardExpDate);
if (objCreditCard.isValid() == false) {
validData = false;
errorMessage = "Invalid CreditCard Info";
}
if (!validData) {
System.out.println(errorMessage);
return false;
}
if (objAddress.save() && objAccount.save() &&
objCreditCard.save()) {
return true;
} else {
return false;
}
}
}
Because a client does not need to access any of the subsystem components directly, none of the subsystem components undergoes any changes in the new design for the remote mode of operation of the application.
Let us redesign the client AccountManager class (Listing 23.2).
Similar to the local mode of operation, AccountManager displays the nec-essary user interface to accept the input customer data (Figure 23.6). When the user enters the data and clicks on the Validate & Save button, it retrieves the remote object reference from the RMI registry using the reference name.
Once the remote object reference is retrieved from the registry, the client can invoke operations on the remote object reference as if it is a local object. Figure 23.7 depicts this behavior.
Note that the stub class corresponding to the compiled CustomerFacade class must be copied onto the client AccountManager location before executing the application. After the CustomerFacade is compiled, the stub and skeleton classes can be generated using the RMIC compiler on the compiled Customer- Facade class. Detailed instructions on compiling and deploying different application components are provided under the following “Additional Notes” section.
Figure 23.5 Façade Design: Remote Mode of Operation
In reality, when the client invokes a method such as saveCustomerData on the CustomerFacade remote object, the CustomerFacade_stub object, which is local to the client, first receives it. The CustomerFacade_stub then transmits the method call to the server for processing.
On the server side the CustomerFacade_skel is responsible for receiving the method call through the lower levels of the communication network. It then dispatches it to the actual CustomerFacade object on the server. In case of the saveCustomerData method, the CustomerFacade object creates the neces-sary subsystem objects and invokes the required methods on these objects to validate and save the customer data. The result of the processing is carried back to the client in the reverse manner. Figure 23.8 depicts this actual communication mechanism.
As can be seen from above, the CustomerFacade_stub class enables the client object to invoke methods on the remote CustomerFacade object as if it is present locally, which, otherwise, is not accessible by normal means. Thus the stub functions as a remote proxy.
Listing 23.2 AccountManager Class: Revised
…
…
public void actionPerformed(ActionEvent e) {
…
…
if (e.getActionCommand().equals(
AccountManager.VALIDATE_SAVE)) {
//get input values
String firstName = objAccountManager.getFirstName();
String lastName = objAccountManager.getLastName();
String address = objAccountManager.getAddress();
…
…
try {
//Call registry for AddOperation
facade = (CustomerIntr) Naming.lookup ("rmi://" +
objAccountManager.getRMIHost() + ":" +
objAccountManager.getRMIPort() +
"/CustomerFacade");
facade.setFName(firstName);
facade.setLName(lastName);
facade.setAddress(address);
…
…
//Client is not required to access subsystem components.
boolean result = facade.saveCustomerData();
if (result) {
validateCheckResult =
" Valid Customer Data: Data Saved Successfully ";
} else {
validateCheckResult =
" Invalid Customer Data: Data Could Not Be Saved ";
}
} catch (Exception ex) {
System.out.println(
"Error: Please check to ensure the " +
"remote server is running" +
ex.getMessage());
}
objAccountManager.setResultDisplay(
validateCheckResult);
}
}
…
…
Download the source code from the following Web site: http://www.crcpress.com/e_products/downloads/download.asp.
1. Compile all Java files in the Proxy/Server folder.
2. Execute the following command from the Proxy/Server folder:
Rmic CustomerFacade
This command invokes the RMI stub compiler and creates the stub and skeleton classes CustomerFacade_Skel.class and CustomerFacade_ Stub.class, respectively.
3. Copy the following files from the Proxy/Server folder to the Proxy/Client folder:
CustomerIntr.class
CustomerFacade_Stub.class
4. Compile all Java files in the Proxy/Client folder.
5. Start the rmiregistry:
start rmiregistry <objectRegistryPort> (Windows)
rmiregistry & (Solaris)
start rmiregistry
Figure 23.6 The User Interface: Remote Mode of Operation
Figure 23.7 AccountManager View of Its Communication with the Remote Custom-erFacade
Figure 23.8 The Actual Flow of Communication
6. Run the following command:
java -Djava.security.policy=<PolicyFile> CustomerFacade
<RemoteRegistryHost> <RemoteRegistryPort>
java -Djava.security.policy=java.policy CustomerFacade
localhost 1099
7. The following output will be displayed:
Server object created
Service bound…
8. Go to the folder Proxy/client and execute the following command to run the client:
java -Djava.security.policy=<PolicyFile> AccountManager
<RemoteRegistryHost> <RemoteRegistryPort>
▪ Example:
java -Djava.security.policy=java.policy AccountManager
localhost 1099
This executes the client AccountManager and the user interface will be displayed.
1. In our example design, a client can access only the CustomerFacade remote object. The CustomerFacade internally interacts with the remote subsystem components directly. But a client cannot access any of the subsystem components (Account, Address or the CreditCard). Make necessary changes to the Account, Address and the CreditCard classes and to the deployment process, to enable a client to access these subsystem components directly without having to go through the Cus-tomerFacade.
2. Design and implement the purchase request Façade as a remote object. (Refer to Practice Questions 1 and 2 of Chapter 22 — Façade.)
3.138.67.203