Please be aware that I am presenting a solution based on my own (FOSS) framework Byte Buddy which is however already mentioned as a potential solution in one of the comments.
Here is a simple proxy approach which creates a subclass. First, we introduce a type for creating proxies for ByteBuffer
s:
interface ByteBufferProxy {
ByteBuffer getOriginal();
void setOriginal(ByteBuffer byteBuffer);
}
Furthermore, we need to introduce an interceptor to use with a MethodDelegation
:
class Interceptor {
@RuntimeType
public static Object intercept(@Origin(cacheMethod = true) Method method,
@This ByteBufferProxy proxy,
@AllArguments Object[] arguments)
throws Exception {
// Do stuff here such as:
System.out.println("Calling " + method + " on " + proxy.getOriginal());
return method.invoke(proxy.getOriginal(), arguments);
}
}
This interceptor is capable of intercepting any method as the @RuntimeType
casts the return type in case that it does not fit the Object
signature. As you are merely delegating, you are safe. Plase read the documentation for details. As you can see from the annotations, this interceptor is only applicable for instances of ByteBufferProxy
. Bases on this assumption, we want to:
- Create a subclass of
ByteBuffer
.
- Add a field to store the original (proxied) instance.
- Implement
ByteBufferProxy
and implement the interface methods to access the field for the stored instance.
- Override all other methods to call the interceptor that we defined above.
This we can do as follows:
@Test
public void testProxyExample() throws Exception {
// Create proxy type.
Class<? extends ByteBuffer> proxyType = new ByteBuddy()
.subclass(ByteBuffer.class)
.method(any()).intercept(MethodDelegation.to(Interceptor.class))
.defineField("original", ByteBuffer.class, Visibility.PRIVATE)
.implement(ByteBufferProxy.class).intercept(FieldAccessor.ofBeanProperty())
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
// Create fake constructor, works only on HotSpot. (Use Objenesis!)
Constructor<? extends ByteBufferProxy> constructor = ReflectionFactory
.getReflectionFactory()
.newConstructorForSerialization(proxyType,
Object.class.getDeclaredConstructor());
// Create a random instance which we want to proxy.
ByteBuffer byteBuffer = ByteBuffer.allocate(42);
// Create a proxy and set its proxied instance.
ByteBufferProxy proxy = constructor.newInstance();
proxy.setOriginal(byteBuffer);
// Example: demonstrates interception.
((ByteBuffer) proxy).get();
}
final
methods are obviously not intercepted. However as the final
methods in ByteBuffer
only serve as convenience methods (e.g. put(byte[])
calls put(byte[],int,int)
with the additional arguments 0
and the array length), you are still able to intercept any method invocation eventually as these "most general" methods are still overridable. You could even trace the original invocation via Thread.currentCallStack()
.
Byte Buddy normally copies all constructors of its super class if you do not specify another ConstructorStrategy
. With no accessible constructor, it simply creates a class without constructors what is perfectly legal in the Java class file format. You cannot define a constructor because, by definition, this constructor would need to call another constructor what is impossible. If you defined a constructor without this property, you would get a VerifierError
as long as you do not disable the verifier altogether (what is a terrible solution as it makes Java intrinsically unsafe to run).
Instead, for instantiation, we call a popular trick that is used by many mocking frameworks but which requires an internal call into the JVM. Note that you should probably use a library such as Objenesis instead of directly using the ReflectionFactory
because Objenesis is more robust when code is run on a different JVM than HotSpot. Also, rather use this in non-prduction code. Do however not worry about performance. When using a reflective Method
that can be cached by Byte Buddy for you (via cacheMethod = true
), the just-in-time compiler takes care of the rest and there is basically no performance overhead (see the benchmark on bytebuddy.net for details.) While reflective lookup is expensive, reflective invocation is not.
I just released Byte Buddy version 0.3 and I am currently working on documentation. In Byte Buddy 0.4, I plan to introduce an agent builder which allows you to redefine classes during load-time without knowing a thing about agents or byte code.