Proxy Design Pattern Overview
The Proxy pattern provides controlled access to objects by creating intermediaries. This allows pre/post-procesing operations on target objects.
<code class="java">
public interface FinancialService {
void executeTransaction(String id, double amount);
}
public class BankService implements FinancialService {
public void executeTransaction(String id, double amount) {
System.out.println("Processed transaction " + id + ": $" + amount);
}
}
public class Proxy implements FinancialService {
private final BankService target = new BankService();
public void executeTransaction(String id, double amount) {
System.out.println("Pre-transaction checks");
target.executeTransaction(id, amount);
System.out.println("Post-transaction audit");
}
}
</code>
Static proxies require manual implementation for each interface. Dynamic proxies solve this by generating proxy classes at runtime.
Dynamic Proxy Mechanisms
JDK Dynamic Proxy
JDK proxies require interfaces and use reflection:
<code class="java">
public class ProxyHandler implements InvocationHandler {
private final Object target;
public ProxyHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After " + method.getName());
return result;
}
}
// Usage
FinancialService proxy = (FinancialService) Proxy.newProxyInstance(
loader,
new Class[]{FinancialService.class},
new ProxyHandler(new BankService())
);
proxy.executeTransaction("T100", 500);
</code>
Implementation Analysis
The core generation process:
<code class="java">
// Simplified Proxy generation
byte[] bytecode = ProxyGenerator.generateProxyClass("$Proxy0", interfaces);
Class<?> proxyClass = defineClass(loader, "$Proxy0", bytecode, 0, bytecode.length);
</code>
Generated proxy class structure:
<code class="java">
public final class $Proxy0 extends Proxy implements FinancialService {
private static Method m1;
public $Proxy0(InvocationHandler h) {
super(h);
}
public final void executeTransaction(String id, double amount) {
h.invoke(this, m1, new Object[]{id, amount});
}
static {
m1 = Class.forName("FinancialService").getMethod("executeTransaction", String.class, double.class);
}
}
</code>
CGLIB Dynamic Proxy
CGLIB proxies classes directly using bytecode manipulation:
<code class="java">
public class PaymentService {
public void processPayment(String id, double amount) {
System.out.println("Payment processed: " + id + " $" + amount);
}
}
public class CglibProxyFactory {
public static Object createProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After " + method.getName());
return result;
}
});
return enhancer.create();
}
}
// Usage
PaymentService proxy = (PaymentService) CglibProxyFactory.createProxy(PaymentService.class);
</code>
Implementation Analysis
CGLIB generates subclass proxies:
<code class="java">
public class PaymentService$$EnhancerByCGLIB extends PaymentService {
private MethodInterceptor interceptor;
public void processPayment(String id, double amount) {
MethodProxy proxy = ...;
interceptor.intercept(this,
PaymentService.class.getMethod("processPayment"),
new Object[]{id, amount},
proxy
);
}
}
</code>
Method execution flow:
<code class="java">
// In MethodInterceptor
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
// Pre-processing
Object result = proxy.invokeSuper(obj, args); // Calls superclass method
// Post-processing
return result;
}
</code>
Final methods cannot be proxied due to inheritance constraints.