Skip to main content

Dynamic Proxy

Dynamic proxies require special handling for GraalVM native image.

Automatic Detection

Native Image does a static analysis that detects calls the following methods:

  • java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)
  • java.lang.reflect.Proxy.getProxyClass(ClassLoader, Class<?>[])

From the method calls, Native Image tries to determine the list of interfaces that define dynamic proxies. Proxy classes are generated at image build time.

The static analysis can only support list of interfaces that comes from a constant array or an array that is allocated in the same method.

In the code below, LoggingInvocationHandler is the InvocationHandler of the proxy instance. In the wrap method, the incoming Callable object is wrapped by the invocation handler and a proxy instance is returned.

Dynamic proxy
package io.vividcode.graalvm.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.concurrent.Callable;

public class DynamicProxyExample {

public static void main(String[] args) throws Exception {
DynamicProxyExample proxy = new DynamicProxyExample();
Callable<?> callable = proxy.wrap(() -> "Hello World");
System.out.println(callable.call());
}

public Callable<?> wrap(Callable<?> callable) {
ClassLoader classLoader = DynamicProxyExample.class.getClassLoader();
InvocationHandler handler = new LoggingInvocationHandler<Callable<?>>(callable);
return (Callable<?>)
Proxy.newProxyInstance(classLoader, new Class<?>[] {Callable.class}, handler);
}

/** Logging method invocations */
private static class LoggingInvocationHandler<T> implements InvocationHandler {

private final T wrapped;

LoggingInvocationHandler(T wrapped) {
this.wrapped = wrapped;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(
"Before invocation of " + method.getName() + ", parameters: " + Arrays.toString(args));
Object result = method.invoke(wrapped, args);
System.out.println("After invocation of " + method.getName() + ", result: " + result);
return result;
}
}
}

When executing the native image, it correctly shows following output:

Before invocation of call, parameters: null
After invocation of call, result: Hello World
Hello World

Manual Configuration

If the list of interfaces that implemented by dynamic proxies is not constant, this list needs to be declared explicitly and passed to native-image command. This is done by using the -H:DynamicProxyConfigurationFiles option.

Below is an example of dynamic proxy config file. The interfaces property represents a list of interfaces implemented by a dynamic proxy.

Dynamic proxy config
[
{
"interfaces": ["java.util.concurrent.Callable"]
}
]
info

Refer to the JSON schema of proxy config.