Search This Blog

Proxies done right with Guava's AbstractInvocationHandler

Not too often but sometimes we are forced to write custom dynamic proxy class using java.lang.reflect.Proxy. There is really no magic in this mechanism and it's worth knowing even you will never really use it - because Java proxies are ubiquitous in various frameworks and libraries.

The idea is quite simple: dynamically create an object that implements one or more interfaces but every time any method of these interfaces is called our custom callback handler is invoked. This handler receives a handle to a method that was called (java.lang.reflect.Method instance) and is free to behave in any way. Proxies are often used to implement seamless mocking, caching, transactions, security - i.e. they are a foundation for AOP.

Our goal is to run send() asynchronously so that several subsequent invocations are not blocking but queue up and are executed in external thread pool concurrently rather than in calling thread. First we need factory code that will create a proxy instance:

Code above makes few bold assumptions, for example that an underlying object (real instance that we are proxying) implements exactly one interface. In real life a class can of course implement multiple interfaces, so can proxies - but we simplify this a bit for educational purposes. Now for starters we shall create no-op proxy that delegates to underlying object without any added value:

Now even if RealMailServer.send() takes a second to complete, invoking it twice via asyncMailServer.send() takes no time because both invocations run asynchronously in background.

Broken equals(), hashCode() and toString()

Some developers are not aware of potential issues with default InvocationHandler implementation. Quoting the official documentation:

An invocation of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above.

In our case case this means that for example toString() is executed in the same thread pool as other methods of MailServer, quite surprising. Now imagine you have a local proxy where every method invocation triggers remote call. Dispatching equals(), hashCode() and toString() via network is definitely not what we want.

Fixing with AbstractInvocationHandler

AbstractInvocationHandler from Guava is a simple abstract class that correctly deals with issues above. By default it dispatches equals(), hashCode() and toString() to Object class rather than passing it to invocation handler. Refactoring from straight InvocationHandler to AbstractInvocationHandler is dead simple:

That's it! I decided to override toString() to help debugging. equals() and hashCode() are inherited from Object which is fine for the beginning. Now please look around your code base and search for custom proxies. If you were not using AbstractInvocationHandler or similar so far, chances are you introduces few subtle bugs.