This article is lecture note from "Head First Design Patterns" by Elisabeth Freeman and Kathy Sierra and CS course from Kookmin by Inkyu Kim
Especially, this note takes a lot of contents from refectoring.guru and sourcemaking.com
Proxy is a structural design pattern that lets you provide a substitute or placeholder for another object. A proxy controls access to the original object, allowing you to perform something either before or after the request get through to the original object.
Why would we want to control access to an object?
Let's think about an example: you have a massive object that consumes a vast amout of system resources. You need it from time to time, but not always.
You could implement lazy initialisation: create this object only when it's actually needed. All of the object's clients would need to execute some deferred initialisation code. Unfortunately, this would probably cause a lot of code duplication.
The Proxy Pattern suggests that you create a new proxy class with the same interface as an original service object. Then you update your app so that it passes the proxy object to all of the original object's clients. Upon receiving a request from a client, the proxy creates a real service object and delegates all the work to it.
The benefit? If you need to execute something either before or after the primary logic of the class, the proxy lets you do this without changing that class. Since the proxy implements the same interface as the original class, it can be passed to any client that expects a real service object.
First of all, the Service Interface declares the interface of the Service. The proxy must follow this interface to be able to disguise itself as a service object. The Service is a class that provides some useful business logic.
The Proxy class has a reference field that points to a service object. After the proxy finishes its processing(e.g., lazy initialisation, logging, access control, caching, etc.), it passes the request to the service object. Usually, proxies manage the full lifecylce of their service objects. The Client should work with both services and proxies via the same interface. This way you can pass a proxy into any code that expects a service object.
The RMI is an API that provides a mechanism to create distributed application in java. The RMI allows an object to invoke methods on an object running in another JVM.
We are going to make proxy using RMI!
Before we talk about how to use RMI, we need to got the gist of remote method invocation in our head!
Let's say we want to design a system that allows us to call a local object that forwards each request to a remote object. How would we design it?
We need a couple of helper objects that actually do the communication for us. The client then calls a method on the client helper, as if the client helper were the actual service. The client helper takes care of forwarding that request for us.
But the client helper isn't really the remote service. Although the client helper acts like it(because it has the same method that the service is advertising), the client helper doesn't have any of the actual method logic the client is expecting. Instead the client helper contacts the server, transfers information about the method call.
On the server side, the service helper receives the request from the client helper, unpacks the information about the call, and then invokes the real method on the real service object. So, to the service object, the call is local.
Let's understand how to use RMI to enable remote method invocation.
What RMI does for us is build the client and service helper objects. It just right down to creating a client helper object with the same methods as the remote service. The nice hting about RMI is that you don't have to write any of the networking or I/O code ourselves.
RMI also provides all the runtime infrastructure to make it all work, including a lookup service that the client can use to find and access the remote objects.
public interface MyRemote extends Remote{}
import java.rmi.*;
public interface MyRemote extends Remote {
public String sayHello() throws RemoteException;
}
Every remote method call is considered 'risky'. Declaring RemoteException
on every method forces the client to pay attention and acknowledge that things might not work.
public String sayHello() throws RemoteException;
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
public String sayHello() {
return "Server says, 'Hey'";
}
//more codes
}
The compiler will make sure that you've implemented all the methods from the interface you implement.
public class MyRemoteImpl extends UnicastRemoteObject implements Myremote{
public MyRemoteImpl() throws RemoteException{}
You don't have to put anything in the constructor. You just need a way to declare that your superclass constructor throws an exception.try {
MyRemote servicce = new MyRmoteImple();
Naming.rebind("RemoteHello", service);
} catch(Exception ex) {...}
The client has to get the stub object (our proxy), since that's the thing the client will call methods on. And that's where the RMI registry comes in.
Let's get back to our GumballMachine remote proxy.
We'll start with the remote interface:
<GumballMachineRemote.java>
import java.rmi.*;
public interface GumballMachineRemote extends Remote {
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
Here are the methods were goint to support. Each one throws RemoteEcxeption. And all return types need to be primitive or Serialisable.
<State.java>
import java.io.*;
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
The State class is originally not a serialisable. So in State class, we extends Serializable interface(which has no methods in it). And now State in all the subclasses can be transferred over the network.
Actually, we're not done with Serialisable yet; we have one problem with State. As you may remember, each State object maintains a reference to a gumball machine so that it can call the gumball machine's methods and change its state.
public class NoQuarterState implements State {
private static final long serialVersionUID = 2L;
transient GumballMachine gumballMachine;
// all other methods here
}
In each implementation of State, we add the transient keyword to the GumballMachine instance variable. This tells that JVM not to serialise this field. Note that this can be slightly dangerous if you try to access this field once its been serialised and transferred.
<GumballMachine.java>
import java.rmi.*;
import java.rmi.server.*;
public class GumballMachine
extends UnicastRemoteObject implements GumballMachineRemote
{
// instance variables here
public GumballMachine(String location, int numberGumballs) throws RemoteException {
// code here
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
// other methods here
}
The only things that has changed are just import java.rmi.*
and server
then extends UnicastRemoteObject so that the GumballMachine
is able to act as a remote service. Of course we need to throw a remote exception in the constructor because the superclass does.
Now we just need to make sure that GumballMachine
is registered in RMI registry so that clients can locate it.
<GumballMachineTestDrive.java>
import java.rmi.*;
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachineRemote gumballMachine = null;
int count;
if (args.length < 2) {
System.out.println("GumballMachine <name> <inventory>");
System.exit(1);
}
try {
count = Integer.parseInt(args[1]);
gumballMachine =
new GumballMachine(args[0], count);
Naming.rebind("//" + args[0] + "/gumballmachine", gumballMachine);
} catch (Exception e) {
e.printStackTrace();
}
}
}
We added the call to Naming.rebind
which publishes the GumballMachien
stub under the name gumballmachine.
Now for the GumballMonitor client. We wanted to reuse it without having to rewrite it to work over a network.
<GumballMonitor.java>
import java.rmi.*;
public class GumballMonitor {
GumballMachineRemote machine;
public GumballMonitor(GumballMachineRemote machine) {
this.machine = machine;
}
public void report() {
try {
System.out.println("Gumball Machine: " + machine.getLocation());
System.out.println("Current inventory: " + machine.getCount() + " gumballs");
System.out.println("Current state: " + machine.getState());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
First we need to import the RMI package because we are using the RemoteException
class.
Then we're going to rely on the remote interface rather than the concrete GumballMachine class. We also need to catch any remote exceptions that might happen as we try to invoke methods that are ultimately happening over the network.
Real Final has come we've got to test our GumballMonitor. 🤓
<GumballMonitorTestDrive.java>
import java.rmi.*;
public class GumballMonitorTestDrive {
public static void main(String[] args) {
String[] location = {"rmi://santafe.mightygumball.com/gumballmachine",
"rmi://boulder.mightygumball.com/gumballmachine",
"rmi://austin.mightygumball.com/gumballmachine"};
if (args.length >= 0)
{
location = new String[1];
location[0] = "rmi://" + args[0] + "/gumballmachine";
}
GumballMonitor[] monitor = new GumballMonitor[location.length];
for (int i=0;i < location.length; i++) {
try {
GumballMachineRemote machine =
(GumballMachineRemote) Naming.lookup(location[i]);
monitor[i] = new GumballMonitor(machine);
System.out.println(monitor[i]);
} catch (Exception e) {
e.printStackTrace();
}
}
for (int i=0; i < monitor.length; i++) {
monitor[i].report();
}
}
}
We can see that the locations we're going to monitor. Ane we create an array of locations one for each machine. We also create an array of monitors.
Through Naming.lookup(location[i]);
we get a proxy to each remote machine.
It was a long journey from the beginning...
The Proxy passes the client request over the network, handling all of the nasty details of working the network. This is so called Remote Proxy. This is when the service object is located on a remote server.
In addition, Proxy Pattern has a dozens of ways to utilise it. So, let's go over the most popular uses.