Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding the Proxy Design Pattern in Java

Tech 2

The Proxy design pattern is a structural pattern that provides a surrogate or placeholder to control access to another object. It involves creating a representative class that manages interaction with a real subject, enabling additional functionality like access control, lazy initialization, or logging.

Core Concept

A proxy acts as an intermediary between a client and a real object. Its used when direct access to the real object is undesirable or impossible due to reasons like security, cost of creation, or location (e.g., remote objects). In Java, proxies are categorized by when the proxy class is created: static proxies are generated at compile time, while dynamic proxies are created at runtime. Dynamic proxies in Java are further divided into JDK proxies (interface-based) and CGLIB proxies (subclass-based).

Key Components

  1. Subject Interface/Abstract Class: Defines the common interface for both the RealSubject and the Proxy, declaring the business methods.
  2. RealSubject: The actual object that performs the core business logic. This is the object the proxy represents.
  3. Proxy: Implements the same interface as the RealSubject. It holds a reference to the RealSubject and controls access to it, often adding pre- and post-processing logic.

Common use cases for the proxy pattern include:

  • Remote Proxy: Represents an object located in a different address space (e.g., on a remote server).
  • Virtual Proxy: Creates expensive objects on demand (e.g., lazy loading of large images).
  • Protection Proxy: Controls access to the real object based on permissions or security roles.
  • Caching Proxy: Stores results of expensive operations to improve performance for subsequent requests.

Static Proxy Implementation

In a static proxy, the relationship between the proxy and the real subject is fixed at compile time. The proxy class implements the same interface as the real subject and contains a reference to it.

// Subject Interface
interface DataService {
    void fetchData();
}

// Real Subject
class DatabaseService implements DataService {
    @Override
    public void fetchData() {
        System.out.println("DatabaseService: Retrieving data from database.");
    }
}

// Static Proxy
class DataServiceProxy implements DataService {
    private DatabaseService dbService;

    public DataServiceProxy(DatabaseService service) {
        this.dbService = service;
    }

    @Override
    public void fetchData() {
        System.out.println("Proxy: Logging request start time.");
        dbService.fetchData(); // Delegate to real subject
        System.out.println("Proxy: Logging request completion.");
    }
}

// Client Code
public class ClientApp {
    public static void main(String[] args) {
        DatabaseService realService = new DatabaseService();
        DataServiceProxy serviceProxy = new DataServiceProxy(realService);
        serviceProxy.fetchData(); // Client interacts with the proxy
    }
}

Advantages: Simple to understand and implement; provides clear control over the real subject. Disadvantages: Requires a new proxy class for each real subject or new functionality, leading to class proliferation; tight coupling as the proxy references a concrete real subject class.

Dynamic Proxy Implementation

JDK Dynamic Proxy

JDK dynamic proxies are built into the Java standard library (java.lang.reflect.Proxy). They require the real subject to implement at least one interface. The proxy logic is defined in an InvocationHandler.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// Subject Interface
interface PaymentProcessor {
    void processPayment(double amount);
}

// Real Subject
class BankPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("BankPaymentProcessor: Processing payment of $" + amount);
    }
}

// Invocation Handler
class PaymentInvocationHandler implements InvocationHandler {
    private Object targetObject;

    public PaymentInvocationHandler(Object target) {
        this.targetObject = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("InvocationHandler: Validating transaction.");
        Object result = method.invoke(targetObject, args);
        System.out.println("InvocationHandler: Transaction logged.");
        return result;
    }
}

// Client Code
public class PaymentClient {
    public static void main(String[] args) {
        BankPaymentProcessor realProcessor = new BankPaymentProcessor();
        PaymentProcessor dynamicProxy = (PaymentProcessor) Proxy.newProxyInstance(
                realProcessor.getClass().getClassLoader(),
                realProcessor.getClass().getInterfaces(),
                new PaymentInvocationHandler(realProcessor)
        );
        dynamicProxy.processPayment(250.75);
    }
}

The Proxy.newProxyInstance method dynamically creates a proxy class that implements the specified interfaces. All method calls on this proxy are routed to the invoke method of the provided InvocationHandler.

CGLIB Dynamic Proxy

CGLIB is a third-party library that generates proxies by creating a subclass of the target class. It does not require the target to implement an interface.

First, add the CGLIB dependency (e.g., in Maven):

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// Target Class (No interface required)
class ImageRenderer {
    public void renderImage(String filename) {
        System.out.println("ImageRenderer: Rendering image from " + filename);
    }
}

// Method Interceptor
class RendererInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Interceptor: Checking cache for image.");
        Object result = proxy.invokeSuper(obj, args); // Call original method
        System.out.println("Interceptor: Updating cache.");
        return result;
    }
}

// Client Code
public class ImageClient {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ImageRenderer.class); // Set target class as superclass
        enhancer.setCallback(new RendererInterceptor()); // Set interceptor logic
        ImageRenderer proxy = (ImageRenderer) enhancer.create(); // Create proxy instance
        proxy.renderImage("photo.jpg");
    }
}

Limitation: CGLIB cannot proxy final classes or final methods because it works by subclassing.

Comparison of Proxy Types

  • JDK vs. CGLIB Proxy: JDK proxies require an interface. CGLIB proxies work by subclassing, so they can proxy classes without interfaces but fail with final classes. Performance varies by JDK version; modern JDKs (1.8+) often make JDK proxies more efficient for interface-based scenarios.
  • Dynamic vs. Static Proxy: Dynamic proxies centralize proxy logic in a handler/interceptor, making it easier to maintain when the subject interface has many methods. Adding a method to the interface requires no changes to the dynamic proxy handler, whereas a static proxy would need to be updated. Dynamic proxies offer greater flexibility and reduce boilerplate code.

Advantages and Disadvantages

Advantages:

  • Acts as a protective intermediary for the real object.
  • Can transparently add functionality (logging, security, caching) without modifying the real subject.
  • Reduces coupling by separating the client from the real object.

Disadvantages:

  • Increases system complexity by introducing additional classes/layers.
  • May introduce a slight performance overhead due to the indirection.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.