Cristian Riță
Published on

Java Dynamic Proxy

Authors
  • Name
    Twitter

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.

Did you enjoy this article? You might also like my Spring Framework newsletters: