The Proxy Pattern
Proxy is a common software design pattern. Wikipedia does a good job describing it like this:
[..] In short, a proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. [..]
UML class diagram:
A client requires a Subject (typically an interface). This subject is implemented by a real implementation (here: RealSubject). A proxy implements the same interface and delegates operations to the real subject while adding its own functionality.
In the next sections we will see how this pattern can be implemented in Java.
Creating a simple proxy
We start with an interface UserProvider (the Subject in the above diagram):
public interface UserProvider { User getUser(int id); }
This interface is implemented by UserProviderImpl (the real implementation):
public class UserProviderImpl implements UserProvider { @Override public User getUser(int id) { return ... } }
UserProvider is used by UsefulService (the client):
public class UsefulService { private final UserProvider userProvider; public UsefulService(UserProvider userProvider) { this.userProvider = userProvider; } // useful methods }
To initialize a UsefulService instance we just have to pass a UserProvider object to the constructor:
UserProvider userProvider = new DatabaseUserProvider(); UsefulService service = new UsefulService(userProvider); // use service
Now let's add a Proxy object for UserProvider that does some simple logging:
public class LoggingUserProviderProxy implements UserProvider { private final UserProvider userProvider; public LoggingUserProviderProxy(UserProvider userProvider) { this.userProvider = userProvider; } @Override public User getUser(int id) { System.out.println("Retrieving user with id " + id); return userProvider.getUser(id); } }
We want to create a proxy for UserProvider, so our proxy needs to implement UserProvider. Within the constructor we accept the real UserProvider implementation. In the getUser(..) method we first write a message to standard out before we delegate the method call to the real implementation.
To use our Proxy we have to update our initialization code:
UserProvider userProvider = new UserProviderImpl(); LoggingUserProviderProxy loggingProxy = new LoggingUserProviderProxy(userProvider); UsefulService usefulService = new UsefulService(loggingProxy); // use service
Now, whenever UsefulService uses the getUser() method we will see a console message before a User object is returned from UserProviderImpl. With the Proxy pattern we were able to add logging without modifying the client (UsefulService) and the real implementation (UserProviderImpl).
The problem with manual proxy creation
The previous solution has a major downside: Our Proxy implementation is bound to the UserProvider interfaces and therefore hard to reuse.
Proxy logic is often quite generic. Typical use-cases for proxies include caching, access to remote objects or lazy loading.
However, a proxy needs to implement a specific interface (and its methods). This contradicts with re-usability.
Solution: JDK Dynamic Proxies
The JDK provides a standard solution to this problem, called Dynamic Proxies. Dynamic Proxies let us create a implementation for a specific interface at runtime. Method calls on this generated proxy are delegated to an InvocationHandler.
With Dynamic Proxies the proxy creation looks like this:
UserProvider userProvider = new DatabaseUserProvider(); UserProvider proxy = (UserProvider) Proxy.newProxyInstance( UserProvider.class.getClassLoader(), new Class[]{ UserProvider.class }, new LoggingInvocationHandler(userProvider) ); UsefulService usefulService = new UsefulService(proxy);
With Proxy.newProxyInstance(..) we create a new proxy object. This method takes three arguments:
- The classloader that should be used
- A list of interfaces that the proxy should implement (here UserProvider)
- A InvocationHandler implementation
InvocationHandler is an interface with a single method: invoke(..). This method is called whenever a method on the proxy object is called.
Our simple LoggingInvocationHandler looks like this:
public class LoggingInvocationHandler implements InvocationHandler { private final Object invocationTarget; public LoggingInvocationHandler(Object invocationTarget) { this.invocationTarget = invocationTarget; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(String.format("Calling method %s with args: %s", method.getName(), Arrays.toString(args))); return method.invoke(invocationTarget, args); } }
The invoke(..) method has three parameters:
- The proxy object on which a method has been called
- The method that has been called
- A list of arguments that has been passed to the called method
We first log the method and the arguments to stdout. Next we delegate the method call to the object that has been passed in the constructor (note we passed the real implementation in the previous snippet).
The separation of proxy creation (and interface implementation) and proxy logic (via InvocationHandler) supports re-usability. Note we do not have any dependency to the UserProvider interface in our InvocationHandler implementation. In the constructor we accept a generic Object. This gives us the option to reuse the InvocationHandler implementation for different interfaces.
Limitations of Dynamic Proxies
Dynamic Proxies always require an interface. We cannot create proxies based on (abstract) classes.
If this really a great issue for you can look into the byte code manipulation library cglib. cglib is able to create proxy via subclassing and therefore is able to create proxies for classes without requiring an interface.
Conclusion
The Proxy Pattern can be quite powerful. It allows us to add functionality without modifying the real implementation or the client.
Proxies are often used to add some generic functionality to existing classes. Examples include caching, access to remote objects, transaction management or lazy loading.
With Dynamic Proxies we can separate proxy creation from proxy implementation. Proxy method calls are delegated to an InvocationHandler which can be re-used.
Note that in some situations the Proxy Pattern can be quite similar to the Decorator pattern (see this Stackoverflow discussion).
Comments
AnLK19 - Sunday, 1 October, 2023
Thanks, it's really helpful
Leave a reply