Understanding Proxy Patterns: Static and Dynamic Proxies in Java

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:

  1. A base interface defining common methods
  2. The real subject implementing the base interface with actual business logic
  3. 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 MethodInterceptor instead of InvocationHandler
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.

Tags: java Design Patterns Proxy Pattern Static Proxy Dynamic Proxy

Posted on Sun, 14 Jun 2026 17:59:53 +0000 by atsphpflash