The Proxy Pattern is a structural design pattern that allows you to 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 gets through to the original object.
There are several types of proxies, including:
In Java, there are two main ways to implement the Proxy Pattern: static proxies and dynamic proxies.
A static proxy is a proxy class that is created at compile time. Here is a simple example:
// Subject interface
interface Image {
void display();
}
// RealSubject class
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
// Proxy class
class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// Main class to test the proxy
public class StaticProxyExample {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
// Image will be loaded from disk
image.display();
System.out.println("");
// Image will not be loaded from disk again
image.display();
}
}
In this example, the ProxyImage
class acts as a proxy for the RealImage
class. It defers the creation of the RealImage
object until the display
method is called.
A dynamic proxy is a proxy class that is created at runtime. Java provides the java.lang.reflect.Proxy
class and the java.lang.reflect.InvocationHandler
interface to create dynamic proxies. Here is an example:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// Subject interface
interface Calculator {
int add(int a, int b);
}
// RealSubject class
class RealCalculator implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
// InvocationHandler implementation
class CalculatorProxyHandler implements InvocationHandler {
private Object target;
public CalculatorProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = method.invoke(target, args);
System.out.println("After method call");
return result;
}
}
// Main class to test the dynamic proxy
public class DynamicProxyExample {
public static void main(String[] args) {
Calculator realCalculator = new RealCalculator();
CalculatorProxyHandler handler = new CalculatorProxyHandler(realCalculator);
Calculator proxyCalculator = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class<?>[]{Calculator.class},
handler
);
int result = proxyCalculator.add(2, 3);
System.out.println("Result: " + result);
}
}
In this example, the CalculatorProxyHandler
class implements the InvocationHandler
interface. The invoke
method is called every time a method on the proxy object is invoked.
invoke
method of a dynamic proxy, make sure to handle exceptions properly. Otherwise, the exceptions can propagate up and cause unexpected behavior.The Proxy Pattern is a powerful design pattern that can be used to control access to an object, add additional functionality, or optimize performance. In Java, you can implement the Proxy Pattern using static proxies or dynamic proxies. By understanding the fundamental concepts, usage methods, common practices, and best practices of the Proxy Pattern, you can write more flexible and maintainable code.