July 14, 2015 | Software Consultancy
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.
WRITTEN BY
Consider an instance of java.reflection.InvocationHandler
that simply passes every method call through to an underlying instance:
public class PassthroughInvocationHandler implements InvocationHandler { private final Object target; public PassthroughInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(target, args); } }
To create a proxy using this invocation handler, we use the newProxyInstance
static utility method on the java.reflection.Proxy
class:
@SuppressWarnings("unchecked") public static T proxying(T target, Class iface) { return (T) Proxy.newProxyInstance( iface.getClassLoader(), new Class<?>[] { iface }, new PassthroughInvocationHandler(target)); }
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:
public static T simpleProxy(Class<? extends T> iface, InvocationHandler handler, Class<?>...otherIfaces) { Class<?>[] allInterfaces = Stream.concat( Stream.of(iface), Stream.of(otherIfaces)) .distinct() .toArray(Class<?>[]::new); return (T) Proxy.newProxyInstance( iface.getClassLoader(), allInterfaces, handler); }
We can use it to create a new pass-through proxy like so:
public static T passthroughProxy(Class<? extends T> iface, T target) { return simpleProxy(iface, new PassthroughInvocationHandler(target)); }
What is the performance impact of using dynamic proxies? I ran the following tests using Contiperf:
public class PassthroughInvocationHandlerPerformanceTest { @Rule public final ContiPerfRule rule = new ContiPerfRule(); public interface RandomNumberGenerator { long getNumber(); } private static final Random random = new Random(); private static final RandomNumberGenerator concreteInstance = random::nextLong; private static final RandomNumberGenerator proxiedInstance = passthroughProxy( RandomNumberGenerator.class, concreteInstance); @Test @PerfTest(invocations = 1000, warmUp = 200) public void invokeConcrete() { getAMillionRandomLongs(concreteInstance); } @Test @PerfTest(invocations = 1000, warmUp = 200) public void invokeProxied() { getAMillionRandomLongs(proxiedInstance); } private void getAMillionRandomLongs(RandomNumberGenerator generator) { for (int i = 0; i < 1000000; i++) { generator.getNumber(); } } }
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.
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.
public interface PersonMatcher extends Matcher { static PersonMatcher aPerson() { return MagicMatcher.proxying(PersonMatcher.class); } PersonMatcher withName(String expected); PersonMatcher withName(Matcher matcher); PersonMatcher withAge(int expected); PersonMatcher withAge(Matcher ageMatcher); }
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:
PersonMatcher withDateOfBirth(LocalDate expected); default PersonMatcher withDateOfBirth(String expected) { return withDateOfBirth(LocalDate.parse(expected, DateTimeFormatter.ISO_DATE)); }
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.
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.
@FunctionalInterface public interface MethodCallHandler { Object invoke(Object proxy, Object[] args) throws Throwable; }
Then we define a MethodInterpreter
interface which finds the correct MethodCallHandler
for each method.
@FunctionalInterface public interface MethodInterpreter extends InvocationHandler { @Override default Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodCallHandler handler = interpret(method); return handler.invoke(proxy, args); } MethodCallHandler interpret(Method 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
:
public static MethodInterpreter caching(MethodInterpreter interpreter) { ConcurrentMap<Method, MethodCallHandler> cache = new ConcurrentHashMap<>(); return method -> cache.computeIfAbsent(method, interpreter::interpret); }
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:
public static MethodInterpreter handlingDefaultMethods(MethodInterpreter nonDefaultInterpreter) { return method -> method.isDefault() ? DefaultMethodCallHandler.forMethod(method) : nonDefaultInterpreter.interpret(method); }
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:
final class DefaultMethodCallHandler { private DefaultMethodCallHandler() { } private static final ConcurrentMap<Method, MethodCallHandler> cache = new ConcurrentHashMap<>(); public static MethodCallHandler forMethod(Method method) { return cache.computeIfAbsent(method, m -> { MethodHandle handle = getMethodHandle(m); return (proxy, args) -> handle.bindTo(proxy).invokeWithArguments(args); }); } private static MethodHandle getMethodHandle(Method method) { Class<?> declaringClass = method.getDeclaringClass(); try { Constructor constructor = MethodHandles.Lookup.class .getDeclaredConstructor(Class.class, int.class); constructor.setAccessible(true); return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE) .unreflectSpecial(method, declaringClass); } catch (IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e) { throw new RuntimeException(e); } } }
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:
public static MethodInterpreter binding(Object target, MethodInterpreter unboundInterpreter) { return method -> { if (method.getDeclaringClass().isAssignableFrom(target.getClass())) { return (proxy, args) -> method.invoke(target, args); } return unboundInterpreter.interpret(method); }; }
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:
public static MethodInterpreter binding(Object target) { return binding(target, method -> { throw new IllegalStateException(String.format( "Target class %s does not support method %s", target.getClass(), method)); }); }
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
:
@FunctionalInterface public interface MethodCallInterceptor { Object intercept(Object proxy, Method method, Object[] args, MethodCallHandler handler) throws Throwable; default MethodCallHandler intercepting(Method method, MethodCallHandler handler) { return (proxy, args) -> intercept(proxy, method, args, handler); } }
and then applying the interception to an InterpretingMethodHandler
like this:
public static MethodInterpreter intercepting(MethodInterpreter interpreter, MethodCallInterceptor interceptor) { return method -> interceptor.intercepting(method, interpreter.interpret(method)); }
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.
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:
public static T interceptingProxy(T target, Class iface, MethodCallInterceptor interceptor) { return simpleProxy(iface, caching(intercepting( handlingDefaultMethods(binding(target)), interceptor))); } }
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
.
public interface Person { String getName(); void setName(String name); int getAge(); void setAge(int age); default String display() { return String.format("%s (%s)", getName(), getAge()); } } public static final class PersonImpl implements Person { // Standard bean implementation } @Test public void interceptsCalls() { Person instance = new PersonImpl(); List callDetails = new ArrayList<>(); MethodCallInterceptor interceptor = (proxy, method, args, handler) -> { Object result = handler.invoke(proxy, args); callDetails.add(String.format("%s: %s -> %s", method.getName(), Arrays.toString(args), result)); return result; }; Person proxy = Proxies.interceptingProxy(instance, Person.class, interceptor); proxy.setName("Arthur Putey"); proxy.setAge(42); assertThat(proxy.display(), equalTo("Arthur Putey (42)")); assertThat(callDetails, contains( "setName: [Arthur Putey] -> null", "setAge: [42] -> null", "getName: null -> Arthur Putey", "getAge: null -> 42", "display: null -> Arthur Putey (42)")); }
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.
New Tricks with Dynamic Proxies in Java (Part 1)
New Tricks with Dynamic Proxies in Java (Part 3)
This blog is written exclusively by the OpenCredo team. We do not accept external contributions.
Agile India 2022 – Systems Thinking for Happy Staff and Elated Customers
Watch Simon Copsey’s talk from the Agile India Conference on “Systems Thinking for Happy Staff and Elated Customers.”Lean-Agile Delivery & Coaching Network and Digital Transformation Meetup
Watch Simon Copsey’s talk from the Lean-Agile Delivery & Coaching Network and Digital Transformation Meetup on “Seeing Clearly in Complexity” where he explores the Current…When Your Product Teams Should Aim to be Inefficient – Part 2
Many businesses advocate for efficiency, but this is not always the right goal. In part one of this article, we explored how product teams can…