EdMelo EdMelo - 1 month ago 21
Java Question

How to create a dynamic proxy using ByteBuddy

In Java it is possible to create dynamic proxies using an implementation of

InvocationHandler
. Despite JVM optimizations, using reflection will always have some overhead invoking a method.

To try to solve this problem, I tried to use ByteBuddy to create the proxy classes at runtime, but the documentation didn't seem clear enough on this aspect.

How do I create a
MethodCallProxy
in order to forward a method invocation to some class instance?

Edit:

To better clarify my problem, I am providing an example of what I want to achieve:

I am building an RPC system. On each side of a method invocation, I have an interface defining the contract (when both caller/callee are running under the JVM).

@Contract
interface ISomeService {
fun someMethod(arg0: String, arg1: SomePojo): PojoResult
}


At the call site, I inject a proxy that intercepts all method calls and forwards them to the callee.

ByteBuddy()
.subclass(Any::class.java)
.implement(serviceClass)

// Service contract method delegation
.method(isDeclaredBy(serviceClass)).intercept(
MethodDelegation
.to(ServiceProxyInterceptor())
.filter(not(isDeclaredBy(Any::class.java)))
)

.make()
.load(this)
.loaded as Class<T>


And, finally, at the callee, I have several handlers, one for each service method, responsible for unmarshalling the invocation parameters and forwarding them to the service implementation.

@Service
class SomeServiceImpl {
fun someMethod(arg0: String, arg1: SomePojo): PojoResult {
// ...
}
}


I could solve this problem using code generation, but the resulting
jar
file can become very big. Thus, I want to create a generic version of these handlers and, in each instance, attach a proxy that intercepts every method call to
ISomeService
and forwards them to
SomeServiceImpl
.

Answer

There are many ways of creating proxy classes in Byte Buddy. The exact way depends on your use-case. The easiest way might be to use the InvocationHandlerAdapter. Given that you want to create a proxy for SomeClass, you can create one using:

Class<? extends SomeClass> proxy = new ByteBuddy()
  .subclass(SomeClass.class)
  .method(ElementMatchers.any())
  .intercept(InvocationHandlerAdapter.of(invocationHandler))
  .make()
  .load(SomeClass.class.getClassLoader());

If you want to create a proxy with a delegate to different instance, you would additionally define a field. This can be done by the following instructions:

Class<? extends SomeClass> proxy = new ByteBuddy()
  .subclass(SomeClass.class)
  .defineField("handler", InvocationHandler.class, Visibility.PUBLIC)
  .method(ElementMatchers.any())
  .intercept(InvocationHandlerAdapter.toField("handler"))
  .make()
  .load(SomeClass.class.getClassLoader());

You would set the above field via reflection or by implementing a setter interface such as for example:

interface HandlerSetter {
  InvocationHandler getHandler();
  void setHandler(InvocationHandler handler);
}

Class<? extends SomeClass> proxy = new ByteBuddy()
  .subclass(SomeClass.class)
  .defineField("handler", InvocationHandler.class, Visibility.PUBLIC)
  .implement(HandlerSetter.class)
  .intercept(FieldAccessor.ofField("handler"))
  .method(ElementMatchers.any())
  .intercept(InvocationHandlerAdapter.toField("handler"))
  .make()
  .load(SomeClass.class.getClassLoader());

You can now instantiate the class and cast the class to the interface for setting the handler.

Beyond the InvocationHandler, there are many other ways to create a proxy. One way would be using MethodDelegation which is more flexible, often faster and allows you to invoke a super method on demand. A forwarding insrumentation can also be applied using a MethodCall or a Forwarding instrumentation. You can find detailed information in the respective classes javadoc.

Comments