Background
In object-oriented systems, certain objects may be expensive to create, or some operations require security checks. Directly accessing such objects can introduce complications. One solution is to introduce an intermediate layer—the proxy layer. This is the essence of the Proxy Pattern.
The Proxy Pattern, one of the 23 classic design patterns, provides a surrogate for another object to control access to it. Through a proxy, additional functionality can be attached to the original object.
Proxy patterns come in two varieties:
- Static Proxy: Requires manually creating a proxy class
- Dynamic Proxy: Generates proxy objects automatically without explicit proxy class declarations
The key distinction is that proxies add an intermediate layer to control access, while the actual operations are performed by the proxy target. This differs from the Decorator Pattern, which adds new capabilities directly to the original object.
Static Proxy
Consider a scenario: when you need a document signed by the CEO, you first approach an assistant. The assistant reviews the document before presenting it to the CEO. Here, the assistant acts as the proxy layer, controlling access to the signing operation.
The structure involves:
- A base interface defining common methods
- The real subject implementing the base interface with actual business logic
- The proxy implementing the same interface but adding additional behavior before delegating to the real subject
Base Interface
Define an interface with the operation both proxy and real subject must implement:
public interface DocumentSigner {
void sign(String document);
}
Real Subject
Implement the interface with the actual business logic:
public class ExecutiveDirector implements DocumentSigner {
@Override
public void sign(String document) {
System.out.println("Executive Director: Signing document - " + document);
}
}
Proxy
The proxy implements the same interface but adds validation before delegating:
public class ExecutiveAssistant implements DocumentSigner {
private DocumentSigner target;
public ExecutiveAssistant(DocumentSigner signer) {
this.target = signer;
}
@Override
public void sign(String document) {
System.out.println("Executive Assistant: Please wait while I review the document");
if (validateDocument(document)) {
target.sign(document);
} else {
System.out.println("Executive Assistant: Document does not meet requirements");
}
}
private boolean validateDocument(String document) {
System.out.println("Executive Assistant: Validating document");
return "contract".equalsIgnoreCase(document);
}
}
Usage
public static void main(String[] args) {
ExecutiveAssistant assistant = new ExecutiveAssistant(new ExecutiveDirector());
assistant.sign("contract");
assistant.sign("agreement");
}
Output demonstrates that calls to the proxy are controlled but ultimately executed by the real subject. The proxy intercepts and can add behavior without modifying the underlying object.
Dynamic Proxy
The static approach has limitations. If the assistant needs to handle signing for multiple roles—perhaps also handling department approvals—the fixed proxy class becomes restrictive. Dynamic proxies solve this by using reflection to generate proxy instances at runtime.
JDK Dynamic Proxy
JDK dynamic proxy leverages Java's native reflection mechanism.
Extend the example with an additional interface and implementation:
public interface ApprovalAuthority {
void sign(String document);
}
public class LegalDepartment implements ApprovalAuthority {
@Override
public void sign(String document) {
System.out.println("Legal Department: Signing document - " + document);
}
}
The proxy layer implements InvocationHandler:
public class UniversalAssistant implements InvocationHandler {
private Object subject;
public Object createProxy(Object target) {
this.subject = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("Universal Assistant: Processing request...");
if (validateInput((String) args[0])) {
result = method.invoke(subject, args);
} else {
System.out.println("Universal Assistant: Request rejected");
}
return result;
}
private boolean validateInput(String input) {
System.out.println("Universal Assistant: Validating input");
return "contract".equalsIgnoreCase(input);
}
}
Usage
public static void main(String[] args) {
UniversalAssistant assistant = new UniversalAssistant();
DocumentSigner director = (DocumentSigner) assistant.createProxy(new ExecutiveDirector());
director.sign("contract");
ApprovalAuthority legal = (ApprovalAuthority) assistant.createProxy(new LegalDepartment());
legal.sign("agreement");
}
The proxy now handles multiple interface types without code changes, demonstrating the flexibility of dynamic proxies.
CGLib Dynamic Proxy
CGLib provides an alternative using bytecode generation. Unlike JDK proxies that require interfaces, CGLib creates proxies by subclassing target classes.
Key differences:
- No interface required; operates on concrete classes
- Can intercept protected methods
- Uses
MethodInterceptorinstead ofInvocationHandler
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class BytecodeAssistant implements MethodInterceptor {
private Object target;
public Object createProxy(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setInterfaces(target.getClass().getInterfaces());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object result = null;
System.out.println("BytecodeAssistant: Processing method call...");
if (validateInput((String) args[0])) {
result = proxy.invokeSuper(obj, args);
} else {
System.out.println("BytecodeAssistant: Request rejected");
}
return result;
}
private boolean validateInput(String input) {
System.out.println("BytecodeAssistant: Validating input");
return "contract".equalsIgnoreCase(input);
}
}
Usage
public static void main(String[] args) {
BytecodeAssistant assistant = new BytecodeAssistant();
ExecutiveDirector director = (ExecutiveDirector) assistant.createProxy(new ExecutiveDirector());
director.sign("contract");
LegalDepartment legal = (LegalDepartment) assistant.createProxy(new LegalDepartment());
legal.sign("contract");
}
CGLib requires cglib.jar and asm.jar dependencies. It returns subclass instances and can intercept protected methods, offering more flexibility than JDK proxies.
Comparison Summary
| Aspect | Static Proxy | Dynamic Proxy |
|---|---|---|
| Implementation | Manual proxy class required | Auto-generated at runtime |
| Flexibility | Fixed at compile time | Adapts to multiple targets |
| Interface | Requires shared interface | JDK requires interface; CGLib does not |
| Aspect | JDK Proxy | CGLib Proxy |
|---|---|---|
| Mechanism | Java reflection | Bytecode generation |
| Interface Requirement | Must ipmlement interface | Works with concrete classes |
| Method Access | Public methods only | Public and protected methods |
| Dependencies | None (built-in) | Requires cglib and asm jars |
Key Takeaways
The Proxy Pattern creates an intermediary to control access to real objects. Static proxies offer clarity but limited flexibility, while dynamic proxies (JDK and CGLib) provide runtime adaptability through reflection or bytecode manipulation. Choose based on whether interface constraints exist and the level of flexibility required.