- Published on
Java Dynamic Proxy
- Authors
- Name
Introduction
A proxy is an object that wraps another object and intercepts method calls to the wrapped object. The proxy can then add additional logic before or after the method call. The proxy can also decide to not even forward the method call to the wrapped object and instead return a value or throw an exception. The proxy can also wrap the return value of the method call with another object. The proxy can even wrap the object with a completely different object. It is extensively used in frameworks like Spring and Hibernate because it is a convenient way to implement cross-cutting concerns like logging, security, and transactions.
Example
For example, let's say we have a Shape
interface and several implementations of the interface. We want to measure the time it takes to draw a shape. We can create a proxy that implements the Shape
interface and wraps an instance of a Shape
implementation. The proxy can then log the time before and after the call to the draw()
method.
public interface Shape {
void draw();
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
Thread.sleep(3000);
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
Thread.sleep(5000);
}
}
In order to create a proxy, we can use the java.lang.reflect.Proxy
class. The Proxy
class has a static method called newProxyInstance()
that takes three arguments: - ClassLoader
: The class loader to use to load the proxy class. We can use the class loader of the target object. - Class<?>[]
: The interfaces that the proxy class should implement. In our case, it is the Shape
interface. - InvocationHandler
: The handler that intercepts method calls to the proxy object.
public class ShapeProxy implements InvocationHandler {
private final Shape shape;
public ShapeProxy(Shape shape) {
this.shape = shape;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(shape, args);
long endTime = System.currentTimeMillis();
System.out.println("Time taken to draw: " + (endTime - startTime) + " milliseconds");
return result;
}
}
The invoke()
method is called whenever a method is called on the proxy object. The proxy
argument is the proxy object itself. The method
argument is the method that was called on the proxy object. The args
argument is the arguments that were passed to the method. The invoke()
method returns the result of the method call.
We can create a proxy object by calling the newProxyInstance()
method on the Proxy
class.
public class ProxyExample {
public static void main(String[] args) throws InterruptedException {
Shape rectangle = new Rectangle();
Shape circle = new Circle();
Shape rectangleProxy = (Shape) Proxy.newProxyInstance(
ProxyExample.class.getClassLoader(),
new Class[]{Shape.class},
new ShapeProxy(rectangle)
);
Shape circleProxy = (Shape) Proxy.newProxyInstance(
ProxyExample.class.getClassLoader(),
new Class[]{Shape.class},
new ShapeProxy(circle)
);
rectangleProxy.draw();
circleProxy.draw();
}
}
As you can see, we have created two proxy objects, one for the Rectangle
object and one for the Circle
object. When we call the draw()
method on the proxy objects, the invoke()
method of the ShapeProxy
class is called. The invoke()
method logs the time before and after the call to the draw()
method.
For the client, the proxy object is indistinguishable from the target object. The client can call the draw()
method on the proxy object just like it would on the target object. The proxy object then intercepts the call and adds additional logic before and after the call.
If you are familiar with Spring, you might have noticed that the @Transactional
annotation works in a similar way. Spring uses a proxy to intercept calls to methods that are annotated with @Transactional
. The proxy starts a transaction before the method is called and commits the transaction after the method returns. If the method throws an exception, the proxy rolls back the transaction.
Conclusion
Proxying is a powerful technique. In this article, we learned how to create a proxy using the java.lang.reflect.Proxy
class. We also learned how to intercept method calls to the proxy object using the InvocationHandler
interface.