Understanding Dynamic Proxy Implementations in Java
The Essence of Proxy Patterns
The proxy pattern provides a mechanism to control access to objects, allowing developers to inject cross-cutting concerns like logging, transaction management, or authentication without modifying the original source code. While static proxies are defined during compilation, dynamic proxies are constructed at runtime, forming the backbone of frameworks like Spring AOP.
JDK Dynamic Proxies
JDK dynamic proxies are native to the Java standard library, leveraging the java.lang.reflect package. This approach mandates that the target object must implement at least one interface.
Implementation
To create a JDK proxy, you define a target interface and implement the java.lang.reflect.InvocationHandler interface to encapsulate the interception logic.
public interface DataProcessor {
void process(String input);
}
public class ProcessorImpl implements DataProcessor {
public void process(String input) {
System.out.println("Processing: " + input);
}
}
public class DynamicInvocationHandler implements java.lang.reflect.InvocationHandler {
private final Object target;
public DynamicInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
System.out.println("Log: Executing " + method.getName());
Object output = method.invoke(target, args);
System.out.println("Log: Completed " + method.getName());
return output;
}
}
Proxy Generation
You use the java.lang.reflect.Proxy factory to instantiate the proxy object:
DataProcessor original = new ProcessorImpl();
DataProcessor proxy = (DataProcessor) java.lang.reflect.Proxy.newProxyInstance(
original.getClass().getClassLoader(),
original.getClass().getInterfaces(),
new DynamicInvocationHandler(original)
);
proxy.process("Data_Payload");
CGLIB Dynamic Proxies
CGLIB (Code Generation Library) is a high-performance alternative that does not require the target class to implement interfaces. Instead, it generates a subclass of the target class at runtime and overrides non-final methods to inject custom behavoir.
Implementation
CGLIB utilizes the MethodInterceptor callback.
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB intercepting: " + method.getName());
return proxy.invokeSuper(obj, args);
}
}
Proxy Generation
Using the Enhancer class, you define the target class as the superclass for the generated proxy:
net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();
enhancer.setSuperclass(ProcessorImpl.class);
enhancer.setCallback(new CglibInterceptor());
ProcessorImpl proxy = (ProcessorImpl) enhancer.create();
proxy.process("CGLIB_Data");
Comparative Overview
| Feature | JDK Dynamic Proxy | CGLIB |
|---|---|---|
| Mechanism | Interface-based | Inheritance-based |
| Dependency | Native Java API | Requires CGLIB library |
| Target Restricsion | Requires interface | Cannot proxy final classes/methods |
| Performance | Reflection-based | Subclassing bytecode |
Framework Integration
Modern frameworks like Spring leverage these mechanisms transparently. Spring AOP prefers JDK proxies for classes implementing interfaces, but defaults to CGLIB for classes without them. This behavior can be strictly overridden using the proxy-target-class configuration flag, forcing the use of CGLIB even when interfaces are present.