Open Credo

July 14, 2015 | Software Consultancy

New Tricks with Dynamic Proxies in Java 8 (part 2)

Building simple proxies

In the previous post I introduced Java dynamic proxies, and sketched out a way they could be used in testing to simplify the generation of custom Hamcrest matchers. In this post, I’m going to dive into some techniques for implementing proxies in Java 8. We’ll start with a simple case, and build up towards something more complex and full-featured.


Dominic Fox

Dominic Fox

New Tricks with Dynamic Proxies in Java 8 (part 2)

Building simple proxies

Consider an instance of java.reflection.InvocationHandler that simply passes every method call through to an underlying instance:

To create a proxy using this invocation handler, we use the newProxyInstance static utility method on the java.reflection.Proxy class:

The method newProxyInstance takes three parameters: the classloader to use, an array of interfaces that the created proxy must implement, and the InvocationHandler to handle method calls with. It will create a new proxy class at runtime if none already exists, and return an instance of that class which dispatches method calls to the supplied InvocationHandler. Once a proxy class has been created for a particular classloader and set of interfaces, that class is cached and re-used – it is as efficient as a hand-rolled implementation of the bridge between the desired interface and InvocationHandler would have been.

For convenience, here’s a utility method for creating new proxies which derives the classloader from the target interface, and allows additional interfaces to be specified using a varargs parameter:

We can use it to create a new pass-through proxy like so:

Performance Implications

What is the performance impact of using dynamic proxies? I ran the following tests using Contiperf:

Two implementations of RandomNumberGenerator, one concrete and the other proxied, were asked for a million random numbers each. Each test was repeated a thousand times, with a 200ms warm-up. The results (to two significant places) were as follows:

Concrete instance Proxied instance
Max ms 31 34
Average ms 22.4 25
Median ms 22 24

With an average difference of 2.6ms per million invocations, these results suggest an overhead (on my laptop) for simple pass-through proxying of around 2.6 nanoseconds per call, once the JVM has had a chance to optimize everything.

Obviously a more complicated process of method dispatch will introduce a greater overhead, but this shows that the performance impact of merely introducing proxying is negligible for most purposes.

What’s different in Java 8

Java 8 introduces three new language features which are relevant for our purposes here. The first is static methods on interfaces, which can be used to supply a proxied implementation of the interface to which they belong, e.g.

Previously, we would have had to add the aPerson method to some other class; now we can conveniently bundle it together with the interface it instantiates. (Note that static methods aren’t implemented by a proxy, as they’re attached to the interface rather than the instance).

The second relevant new language feature is default methods on interfaces. These can be very useful, but require some special handling. Suppose our Person class has a dateOfBirth field of type LocalDate, and we would like to be able to use a String (e.g. “2000-05-05”) to specify the expected date. We can provide this option with a default method on the matcher interface that performs the necessary type conversion, as follows:

In order for this to work, the proxy’s InvocationHandler must be able to recognize default method invocations and dispatch them appropriately (how to do this is covered below).

The final new language feature is lambda expressions. These don’t have much impact on the kinds of interfaces we generate proxies for, but they do facilitate a particular approach to building InvocationHandlers, which is the subject of the next section.

A functional method interpretation stack

The basic task of an InvocationHandler is to decide what to do with a method call, perform the necessary action, and return a result. We can split this task into two parts: one which decides what to do for a given method, and another which executes that decision. First, we define a @FunctionalInterface for the method call handler, which defines the executable behaviour for a given method.

Then we define a MethodInterpreter interface which finds the correct MethodCallHandler for each method.

Note that MethodInterpreter extends, and provides a default implementation for, InvocationHandler. This means that any lambda expression which can be assigned to MethodInterpreter can also be automatically “promoted” into an InvocationHandler.

Once we have made this separation, we can wrap any MethodInterpreter in a chain of interceptors to modify its behaviour. Each interceptor in the chain will be responsible either for handling some particular kind of case, or for modifying the behaviour of interceptors further down the chain.

For example, assuming that the same method will always be interpreted in the same way, we can wrap our MethodInterpreter so that its interpretation is cached, replacing the cost of “interpreting” a method with the cost of looking up a MethodCallHandler in a Map:

For long-lived proxy instances on which the same methods will be called many times, such as service classes, caching the method interpretation can provide a small performance boost.

There are two kinds of “special case” that may be worth handling separately. The first is calls to generic Object methods, such as equals, hashCode and toString. These we will typically want to pass through to some underlying “instance” object that represents the identity (and holds the state) of the proxy. The second is calls to default methods, which as mentioned above require some special handling. We’ll deal with default methods first.

Here’s the function which wraps a MethodInterpreter so that it can handle calls to default methods:

Either the method is a default method, in which case we return a MethodCallHandler which dispatches the call directly to the default method, or we use the supplied nonDefaultInterpreter to work out what to do with it.

The DefaultMethodCallHandler class uses some reflection trickery to get a suitable MethodHandle for the default method, and dispatches the call to that:

This is fairly ugly code, but fortunately we only have to write it once.

For binding calls to equals, hashCode and toString, or to any other methods defined on an interface which some “target” object implements, we implement a wrapper which checks to see whether the called method can be handled by the target object, and fails over to an unboundHandler for any methods that aren’t implemented by the target:

We might even decide that this “target” object is the only handler available to field method calls at this point in the chain, and that calls to methods not supported by the object should fail with an exception:

Finally, we can wire in interceptors that can observe and modify method calls and decide whether or not to pass them down the chain of MethodCallHandlers. We do this by defining another @FunctionalInterface, MethodCallInterceptor:

and then applying the interception to an InterpretingMethodHandler like this:

At this point, we have the ability to construct a stack of wrapped MethodInterpreters that will progressively build up a method call handler for each method handled by an InvocationHandler. We can now use these to help generate proxies of various kinds. I’ll conclude this post by showing how to build a proxy that provides method intercepting behaviour similar to that of the Spring AOP framework.

Proxy example: Intercepting proxy

The interceptingProxy method below creates an intercepting proxy that wraps an underlying implementation of some interface, sending every call against the interface to the underlying object but providing the supplied MethodCallInterceptor with the opportunity to record or modify the call:

Note the ordering of wrappers, in particular that intercepting comes before handlingDefaultMethods, so that default method invocations are also intercepted, and that caching wraps everything. Here it is in use, recording method calls against a Person instance into a List of callDetails.

In the final post in this series, I’ll explore some more sophisticated and useful examples. As before, code implementing the above can be found in the proxology github repository.


Related links

New Tricks with Dynamic Proxies in Java (Part 1)
New Tricks with Dynamic Proxies in Java (Part 3)



Twitter LinkedIn Facebook Email