ByteBuddy and Proxies

Here’s another one that I am sure I’m going to forget how to do so I’m writing it down.

ByteBuddy is a terrific little tool for working with Java bytecode.  It, like many tools, however, is somehow both exquisitely documented and infuriatingly opaque.

ByteBuddy works with a domain-specific language (DSL) to represent the world of manipulating Java bytecode at runtime.  For a, uh, seasoned veteran (yeah, let’s go with that) like me, grappling with the so-called fluent API is quite difficult.  But I’ve figured out that everything is there if you need it.  You just need the magic recipe.  Sometimes even with the help of an IDE the magic recipe is akin to spellcasting.

So here is the magic recipe for defining a runtime proxy that forwards certain method invocations to the return value of a method that yields up the “real” object being proxied:

import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import static net.bytebuddy.implementation.MethodCall.invoke;
import static net.bytebuddy.matcher.ElementMatchers.named;
DynamicType.Builder<?> builder = //… acquire the builder, then:
.defineField("proxiedInstance", theClassBeingProxied, Visibility.PRIVATE) // (1)
.implement(new DefaultParameterizedType(null, Proxy.class, theClassBeingProxied)) // (2)
.intercept(FieldAccessor.ofBeanProperty()) // (3)
.method(someMatcher) // (4)
.intercept(invoke(MethodCall.MethodLocator.ForInstrumentedMethod.INSTANCE).onMethodCall(invoke(named("getProxiedInstance"))));
// 1: Adds a field to the proxy class named proxiedInstance. It will hold the "real" object.
// 2: Proxy.class is a made-up interface defining getProxiedInstance()/setProxiedInstance(T),
// where T is the type of the thing being proxied; e.g. Proxy<Frob>.
// DefaultParameterizedType is a made-up implementation of java.lang.reflect.ParameterizedType.
// 3: Magic ByteBuddy incantation to implement the Proxy<Frob> interface by making two methods
// that read from and write to the proxiedInstance field just defined
// 4: Choose what methods to intercept here; see the net.bytebuddy.matcher.ElementMatchers class
// in particular
// 5: The serious magic is here. It means, roughly, "whatever the method the user just called,
// turn around and invoke it on the return value of the getProxiedInstance() method". That
// INSTANCE object is not documented anywhere, really; you just have to know that it is
// suitable for use here in this DSL "sentence".