Proxy Design Pattern in Java

In this article, we will do an in-depth exploration of Proxy Design Pattern in Java. So, let’s get started.

What is Proxy Design Pattern in Java?

Proxy provides placeholder for another object to enforce access control, reduce expensive creation of original object,to enforce features like logging, instrumentation etc.

Proxy basically controls the access to the real subject as shown below.

Proxy Design Pattern

Use Cases: Proxy Design Pattern in Java

The Proxy pattern applies whenever you need to control access to an object. The most common situations include:

  • Remote proxies:control access to remote objects.Think of an ATM implementation, it will hold proxy objects for bank information that exists in the remote server. RMI is an example of proxy implmenetation for this type in java.
  • Virtual proxies:control access to resources that are expensive to create, such as large images.When an underlying image is huge in size, just represent it using a virtual proxy object and on demand load the real object. You feel that the real object is expensive in terms of instantiation and so without the real need we are not going to use the real object. Until the need arises we will use the virtual proxy.
  • Protection proxies:control what functionality specific users can access.

The JDK’s built-in support for the Proxy design pattern

J2SE 1.3 (Java 2 Platform, Standard Edition) and beyond directly supports proxy.
Three classes from the java.lang.reflect package: Proxy, Method, and InvocationHandler are used for implementing Proxy Design Pattern.

Example : Proxy Design Pattern in Java

In our example, we use make use of the built-in java classes(Proxy, Method, and InvocationHandler) to implement protection proxy. Let us look into the flow diagram and java code.

Flow Diagram

proxy-flowchart

Java Code: Proxy Design Pattern in Java

// IAccessInformation Interface

package com.design.proxy;

public interface IAccessInformation {

    public void accessData(User user);
}

// AccessInformation class

package com.design.proxy;

import java.lang.reflect.Proxy;

public class AccessInformation implements IAccessInformation {

    private AccessInformation() {

    }

    @Override
    public void accessData(User user) {
        System.out.println("Data access allowed for the user:" + user.getName());
    }

    public static IAccessInformation getNewInstance() {
        AccessInformation realSubject = new AccessInformation();
        AccessInvocationHandler accessInvocationHandler = new AccessInvocationHandler(realSubject);
        return (IAccessInformation) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(),
            accessInvocationHandler);
    }

}

// AccessInvocationHandler Class

package com.design.proxy;

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

public class AccessInvocationHandler implements InvocationHandler {

    private IAccessInformation realSubject = null;

    public AccessInvocationHandler(IAccessInformation realSubject) {
        this.realSubject = realSubject;
    }

    public Object invoke(Object proxy, Method m, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (m.getName().equals("accessData")) {
            if (args[0] == null) {
                throw new IllegalArgumentException("Please provider the User");
            }
            User user = (User) args[0];
            if (!user.getRole().equals("Admin")) {
                throw new IllegalAccessException(user.getName() + " dont have access");
            }
        }
        Object result = m.invoke(realSubject, args);
        return result;
    }

}

// User Class

package com.design.proxy;

public class User {

    private String name;
    private String role;
    public String getName() {
        return name;
    }
    public User(String name, String role) {
        this.name = name;
        this.role = role;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getRole() {
        return role;
    }
    public void setRole(String role) {
        this.role = role;
    }

}

// ProxyDemo Class

package com.design.proxy;

public class ProxyDemo {

    public static void main(String[] args) {
        User user = new User("Gyan", "Developer");
        User user1 = new User("Gyan1", "Admin");
        IAccessInformation accessInformation = AccessInformation
            .getNewInstance();
        try {
            accessInformation.accessData(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            accessInformation.accessData(user1);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}


java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy0.accessData(Unknown Source)
at com.design.proxy.ProxyDemo.main(ProxyDemo.java: 11)
Caused by: java.lang.IllegalAccessException: Gyan dont have access
at com.design.proxy.AccessInvocationHandler.invoke(AccessInvocationHandler.java: 22)
    ...2 more

Data access allowed
for the user: Gyan1

Pros: Proxy Design Pattern in Java

  • Controlled Access: Proxy provides controlled access to the real object, allowing added security and validation checks.
  • Lazy Loading: Proxy can delay the creation of the real object until it’s actually needed, improving performance.
  • Caching: Proxy can implement caching to store frequently used data and reduce network or resource access.
  • Remote Access: Proxy allows transparent interaction with remote objects, providing easy communication across networks.
  • Logging and Auditing: Proxy can add logging and auditing functionality for tracking object usage and interactions.
  • Reduced Memory Usage: Proxy can manage memory usage by creating and releasing resources as needed.

Cons: Proxy Design Pattern in Java

  • Complexity: Introducing proxies can add complexity to the codebase, requiring careful design and implementation.
  • Performance Overhead: In some cases, the proxy might introduce a performance overhead due to additional layer of abstraction.
  • Maintainability: Managing both proxies and real objects might increase maintenance effort, particularly in dynamic systems.

Best Practices: Proxy Design Pattern in Java

  • Identify Use-Cases: Determine scenarios where proxy pattern can add value like access control, lazy loading, or remote access.
  • Clear Interface: Define a common interface for both the real object and proxy to ensure transparency.
  • Lazy Initialization: Use proxies for lazy initialization of expensive resources only when required.
  • Smart Proxies: Create smart proxies that add value through additional functionality or optimizations.
  • Proxy Composition: Consider combining multiple proxy types, like virtual, protection, and caching proxies, for comprehensive use.
  • Remote Proxy Handling: Handle remote communication details in the remote proxy to encapsulate network complexities.
  • Avoid Overuse: Don’t overuse proxy pattern; apply it where actual benefits are apparent.

Conclusion: Proxy Design Pattern in Java

In conclusion, this article delved into the world of the Proxy Design Pattern in Java, providing a comprehensive exploration of its concepts and implementation. The Proxy pattern acts as a placeholder for another object, serving various purposes such as access control, resource optimization, and logging. Use-cases for the Proxy pattern encompass scenarios like remote access, virtual proxies for resource-intensive objects, and protection proxies for controlled functionality access.

By utilizing built-in Java classes like Proxy, Method, and InvocationHandler from the java.lang.reflect package, we illustrated the implementation of a protection proxy. The article highlighted the key components of the Proxy pattern, its practical application, and the flow of execution through detailed diagrams and Java code snippets.

The article also covered the advantages and drawbacks of using the Proxy Design Pattern in Java. The pros include controlled access, performance enhancement, and memory optimization, while the cons entail added complexity and potential performance overhead.

Furthermore, best practices were outlined to ensure effective usage of the Proxy pattern, emphasizing the importance of identifying use-cases, maintaining a clear interface, and practicing proxy composition wisely.

In summary, the Proxy Design Pattern in Java provides developers with a versatile tool for managing access, resources, and interactions between objects, enhancing the flexibility and maintainability of software systems.

Must Read Design Pattern Articles: 

Leave a Reply

Your email address will not be published. Required fields are marked *