Contents
Proxy Design Pattern
This pattern is listed as a Structural pattern in the tree of Design Patterns by GoF. It is more focused on intercepting and delegating client request.
Proxy Objects
Proxy objects are not a separate concept of the Proxy Design pattern, but the outcome of it. I am separately handling this because it eases to understand the case. In the context Proxy Objects are known as the replica of the class they wrap, they are either created statically or dynamically.
Proxy objects do not modify the original class that they wrap, but apply some logic and call the original method in the proxy’s handler method. Clients first access to the Proxy Objects, it will intercept the requests and delegate the request only when required to the actual object.
The usage of proxy design pattern offers numerous advantages;
- Access Control and limitation to the real objects. In a situation where the direct access to an enormous file or a remote connection might be expensive, thus Proxies will aid this issue by using lazy loading,
- Providing data caching, security, package access functionalities,
- Proxy objects save the amount of memory used and have a very low memory footprint of the real object. For example in RMI, clients are concerned as Stubs that only make calls to the remote methods. So clients have smaller object footprints.
Feasible scenarios of the proxy pattern
1.Virtual Proxy: This is a situation where the creation of the real object is expensive like big file/network resources,
2.Protection Proxy: In this scenario proxy object acts as a security point where the access rights of the client to the real object are checked.
3.Remote Proxy: A local surrogate object of remote proxy object that resides in a different address space. The method calls on the local surrogate object results in the remote object. This situation is visible in Remote Method Invocation.
Example Class
Purpose
In this example, we will be simulating two concepts;
1.Security: Unauthorized access must be banned to the direct access to the real object,
2.Access to Object: The access to the real object will be monitored, we imagine the txt file is big, thus instantiation will be handled by the proxy for low memory usage.
Code Snippet
enum AccessRight { ADMIN, REGULAR, ANONYMOUS; } interface MyFile { void readFile(); } class ActualFile implements MyFile { private final String fileName; public ActualFile(String fileName) { this.fileName = fileName; } @Override public void readFile() { System.out.println("Invoked class: " + this.getClass()); System.out.println("Reading file: " + fileName + "\n"); } } class ProxyFile implements MyFile { private ActualFile actualFile = null; private final String fileName; private final AccessRight accessRight; public ProxyFile(String fileName, AccessRight accessRight) { this.fileName = fileName; this.accessRight = accessRight; } @Override public void readFile() { System.out.println("Invoked class: " + this.getClass()); //Checking the access rights if (accessRight == AccessRight.ADMIN) { if (actualFile == null) { actualFile = new ActualFile(fileName); System.out.println("instantiating the " + actualFile.getClass() + " class"); actualFile.readFile(); } else { System.out.println("returning the existing " + actualFile.getClass() + " class, no instantiation"); actualFile.readFile(); } } else { System.err.println("You have insufficient access to this class"); return; } } } public class SampleFileReader { public static void main(String[] args) { MyFile regularUser = new ProxyFile("userdata.txt", AccessRight.REGULAR); MyFile adminUser = new ProxyFile("userdata.txt", AccessRight.ADMIN); regularUser.readFile(); adminUser.readFile(); adminUser.readFile(); } }
Explanation
Console Output:
Invoked class: class ProxyFile
You have insufficient access to this class
Invoked class: class ProxyFile
instantiating the class ActualFile class
Invoked class: class ActualFile
Reading file: userdata.txt
Invoked class: class ProxyFile
returning the existing class ActualFile class, no instantiation
Invoked class: class ActualFile
Reading file: userdata.txt
Ways of Creating Proxy Objects
a)Static Proxy: The programmer is responsible of creating a proxy object for every class,
b)Dynamic Proxy: Java is responsible of creating proxy objects dynamically that is natively achieved via reflection since JDK 1.3 or 3rd party libraries.
Purpose
We will be creating a Dynamic proxy for our audit logger that will wrap file reader class. It is assumed that audit logger is generic and must be called before the file reading is being performed
Code Snippet
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface MyFile { void readFile(boolean readOnly); } class ActualFile implements MyFile { private final String fileName; public ActualFile(String fileName) { this.fileName = fileName; } @Override public void readFile(boolean readOnly) { System.out.println("Invoked class: " + this.getClass()); System.out.println("Reading file: " + fileName + "\n"); } } class AuditLogger implements InvocationHandler { private Object targetActualObject; public AuditLogger(Object targetActualObject) { this.targetActualObject = targetActualObject; } public Object getTargetActualObject() { return targetActualObject; } public void setTargetActualObject(Object targetActualObject) { this.targetActualObject = targetActualObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("in " + this.getClass()); System.out.println("====Proxied Class Information===="); System.out.println("1. Proxied class: " + proxy.getClass()); System.out.println("2. Invoked method on the proxied class: " + method.getName()); System.out.println("3. Passed arguments on the method of the proxied class: " + args.toString()); System.out.println("================================="); Object object = method.invoke(targetActualObject, args); return object; } } public class SampleFileReaderInvocationHandler { public static void main(String[] args) { ActualFile myFile = new ActualFile("userdata.txt"); //myFile.getClass().getInterfaces() can also be replaced with new Class[]{MyFile.class} MyFile proxyObject = (MyFile) Proxy.newProxyInstance(SampleFileReaderInvocationHandler.class.getClassLoader(), myFile.getClass().getInterfaces(), new AuditLogger(myFile)); proxyObject.readFile(true); } }
Explanation
Console Output:
in class AuditLogger
====Proxied Class Information====
1. Proxied class: class $Proxy0
2. Invoked method on the proxied class: readFile
3. Passed arguments on the method of the proxied class: [Ljava.lang.Object;@3e34a1fc
=================================
Invoked class: class ActualFile
Reading file: userdata.txt
1.the newProxyInstance method of the Proxy object will dynamically generate a class which will implement the given interfaces as the second method argument.
2.then the proxied object will call the method invoke, the second parameter Method in the invoke method will refer to the designated method which is “readFile()” on the base class “ActualFile”
Dynamic Proxies at a glance
Dynamic proxies are created during run-time, a bit different than the proxy pattern and it makes use of byte code manipulation, the reflection class and compiling the Java code generated dynamically.
Furthermore, there are levels of the run-time code manipulation;
1.Low Level: This is the era of machine instruction’s level using such tools as ASM,
2.High Level: Briefly this is the Byte code. Having a new class not available as a byte code, but there will be a necessity of byte code generation in run-time and the class loader will load this byte code
working on Java classes only such tools as CGlib, AspectJ, ByteBuddy, Javassist
**Byte code: It is the Java program’s outcome after the successful compilation with the files extension of .class. This is where Java promise’s comes in cross-platform play “Write once, run anywhere”.
The bytecode is processed by JVM instead of the CPU. Still the compiled java classes are not fully compiled rather they remain as intermediate codes that have to be executed and interpreted by JVM.
**ClassLoader: Java ClassLoaders are used to load a compiled classes/bytecodes upon the request in run-time into the JVM and memory.
The purpose of dynamic proxying differs in such situtaions for example transactions, AOP, dynamic object mocking for unit testing etc.
It has been always on the scene and part of our day-to-day software development like Hibernate lazy loading entities, Spring AOP etc.
Elements of JDK Dynamic Proxies;
-java.lang.reflect.Proxy: The class enables to create a dynamic proxy for the passed interface,
-java.lang.reflect.InvocationHandler: The interface that is implemented by the Custom class where the actions are taken every time the application invokes a method on the proxy. Each Proxy class is associated with an InvocationHandler interface.