Remote Proxy Pattern

The proxy acts as a local representative for an object that lives somewhere else on the network on a different JVM.

Bates and SierraHead First Design Patterns

Scenario. The client uses a service invoking some methods. Actually the service is located on a remote machine but the location the service should be transparent respect to the client.

Remote proxy. It behaves as a local representative for an remote object living on a different JVM.

Method call. A method call against the proxy results in the transfer of the call over the wire to the remote JVM. Once there the method call is invoked against the real object. The result of the call is returned back to the proxy, then from the proxy to the client.

##RMI Java Remote Method Invocation (RMI) is an example of remote proxy. RMI build two helper objects, stub and skeleton, which hide the communication and technical details about the transfer of the method call and the result return. The client will just interact with the proxy, one of the two helper objects.

RMI vs. Remote proxy Remote proxy does not involve any helper object as RMI does, but just the proxy concept.

##Example A very simple example consists of a client that uses a service to get something done. The client is unaware of the service location and type. The client does not know if the service is remote or local, if it will do database or disk access to provide data.

There are five steps to implement a remote service based on RMI.

###Remote Service Interface Define the interface the client will use to interact with the service.

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;

public interface ContactService extends Remote {
	List<String> listAll() throws RemoteException;

	int getContactSize() throws RemoteException;
}

Remote interface. java.rmi.Remote is just a marker that is an interface without methods. The marker tells that the interface will be used to support remote calls.

Client. It uses a service of type ContactService, the remote service interface, to invoke methods without knowing any implementation detail and thinking it is the real object.

Proxy. It implements the remote interface as the real object does, so the proxy can be used as a surrogate of the real object or, better, the proxy substitute the real object for all the request.

Stub. In RMI the proxy is called stub. It will manage all the networking and I/O operations. Something could go wrong (ex. network failure) so every remote method call is risky and has to declare to throw a java.rmi.RemoteException to handle possible communication failures.

Arguments and return values. They must be primitive or Serializable. Serialization is used to package values and transfer them across the network. In the example above, all the types implement natively Serializable interface as many other types from Java API.

###Remote Service Implementation The implementation of the service is very simple, this is the real object where the calls will be invoked on. The service will reside on the server machine.

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;

public class ContactServiceImpl extends UnicastRemoteObject implements ContactService {

	protected ContactServiceImpl() throws RemoteException {
	}

	@Override
	public List<String> listAll() {
		List<String> contacts = new ArrayList<String>();
		contacts.add("Fermi");
		contacts.add("Majorana");
		return contacts;
	}

	@Override
	public int getContactSize() {
		return 18;
	}
}

Being remote. Service implementation must extends java.rmi.server.UnicastRemoteObject to make it remote and so working as a remote service object. This class has some functionality (ex. read and write values on the socket) that allow it to be remote.

Superclass constructor. Superclass UnicastRemoteObject constructor throws a RemoteException. Superclass constructor is always called so no choice but declare that a constructor throws an exception.

Remote service registration. The remote service implementation needs be registered in the registry to make it available to remote clients. The registration is done by the Server class.

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;

public class Server {
	public static void main(final String[] args) {
		try {
			ContactService contactService = new ContactServiceImpl();
			Naming.rebind("/contact_service", contactService);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
}

RMI registry. Statement Naming.rebind("/service", service) puts the remote service implementation into the RMI registry with a service name. The client will use this name in order to look up for the stub.

Stub. The server registers or puts the stub in the registry.

###Generate Stub and Skeleton They are the client and server helpers created automatically by rmic. These classes implements all the code necessary to manage the connection socket and transfer the method call to the real object residing on the remote JVM.

MacBook:remote blackcat$ rmic ContactServiceImpl

Stub and skeleton. Invoking rmic on service implementation generates ContactServiceImpl_Stub.class and ContactServiceImpl_Skeleton.class, the two helper objects. They will manage the transfer over the wire of the request about the method call which, finally, will be executed against the real object, ContactServiceImpl.

###Start the Registry The rmiregistry is a sort of white pages where services can be registered and looked up. The client will look for the proxy or client helper or stub into the register. Classes must be available to the rmi registry.

MacBook:remote blackcat$ rmiregistry &

###Start the Server Once the registry has been started, it is possible to run the server which will register the service implementation.

MacBook:remote blackcat$ java Server

The Client

The client looks up the service, gets the reference to the stub and invokes the method against it.

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class Client {
	public static void main(final String[] args) {
		new Client().start();
	}

	public void start() {
		try {
			ContactService contactService = (ContactService) Naming.lookup("rmi://127.0.0.1/contact_service");
			System.out.println("number of contacts in the address book is: " + contactService.getContactSize());
			for (String contact : contactService.listAll()) {
				System.out.println(contact);
			}
		} catch (NotBoundException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
}

Naming lookup. Static method Naming.lookup("rmi://127.0.0.1/contact_service") allows the client to get the reference to an instance which represents the helper or stub.

Remote interface. The client uses the remote interface as service type without knowing the real class name of the remote service.

Cast. The lookup method returns an instance of type Object which has to be casted to the remote service type.