1. Overview
When we use the dynamic proxy, the JDK will dynamically generate a $Proxy class. Usually, the fully qualified class name of this $Proxy class is somewhat similar to com.sun.proxy.$Proxy0. As the Java Documentation says, the “$Proxy” is a reserved name prefix for proxy classes.
In this tutorial, we’re going to explore this $Proxy class.
2. The $Proxy Class
Before getting started, let’s distinguish between the java.lang.reflect.Proxy class and $Proxy class. The java.lang.reflect.Proxy is a JDK built-in class. And, in contrast, the $Proxy class is dynamically generated at runtime. From a class hierarchy perspective, the $Proxy class inherits the java.lang.reflect.Proxy class.
2.1. A Dynamic Proxy Example
To have a basis for discussion, let’s define two interfaces: BasicOperation and AdvancedOperation. The BasicOperation interface contains the add and subtract methods:
public interface BasicOperation {
int add(int a, int b);
int subtract(int a, int b);
}
And, the AdvancedOperation interface has the multiply and divide methods:
public interface AdvancedOperation {
int multiply(int a, int b);
int divide(int a, int b);
}
To get a newly generated proxy class – the $Proxy class – we can invoke the Proxy::getProxyClass method:
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?>[] interfaces = {BasicOperation.class, AdvancedOperation.class};
Class<?> proxyClass = Proxy.getProxyClass(classLoader, interfaces);
However, the above proxyClass only exists in a running JVM and we cannot directly view its class members.
2.2. Dump $Proxy Class
For close scrutiny of this $Proxy class, we’d better dump it to disk. When using Java 8, we can specify the “sun.misc.ProxyGenerator.saveGeneratedFiles” option on the command line:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
Or we can set this option by invoking the System::setProperty method:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
In Java 9 and later versions, we should use the “jdk.proxy.ProxyGenerator.saveGeneratedFiles” option instead. Why is there such a difference? Because of the Java Module System, the package of the ProxyGenerator class has changed. In Java 8, the ProxyGenerator is in the “sun.misc” package; however, since Java 9, the ProxyGenerator has moved into the “java.lang.reflect” package.
In case we still don’t know which option is suitable, we can look up the saveGeneratedFiles field of the ProxyGenerator class to determine the correct one.
Be careful here: The ProxyGenerator class reads this property only once. And, this implies that the System::setProperty method will have no effect after the JVM has explicitly or implicitly produced any $Proxy classes. To be specific, invoking the Proxy::getProxyClass or Proxy::newProxyInstance method will explicitly generate the $Proxy class. On the other hand, when we read annotations, especially within the Unit Test framework, the JVM will implicitly or automatically generate the $Proxy class to represent annotation instances.
The exact location of the dumped class file is directly related to its fully qualified class name. For example, if the newly generated class name is “com.sun.proxy.$Proxy0“, then the dumped class file will be “com/sun/proxy/$Proxy0.class” in the current directory:
2.3. The Members of $Proxy Class
Now, it’s time to inspect the class members of this generated $Proxy class.
Let’s first inspect the class hierarchy. The $Proxy0 class has java.lang.reflect.Proxy as its superclass, which implicitly explains why dynamic proxy only supports interfaces. Also, the $Proxy0 class implements our previously defined BasicOperation and AdvancedOperation interfaces:
public final class $Proxy0 extends Proxy implements BasicOperation, AdvancedOperation
For readability, we have changed the field names of the $Proxy0 class into more meaningful ones. The hashCodeMethod, equalsMethod, and toStringMethod fields trace back to the Object class; the addMethod and subtractMethod fields are related to the BasicOperation interface; the multiplyMethod and divideMethod fields map to the AdvanceOperation interface:
private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
private static Method addMethod;
private static Method subtractMethod;
private static Method multiplyMethod;
private static Method divideMethod;
Finally, the methods defined in the $Proxy0 class follow the same logic: All their implementations delegate to the InvocationHandler::invoke method. And, the $Proxy0 class will get an InvocationHandler instance from its constructor:
public $Proxy0(InvocationHandler handler) {
super(handler);
}
public final int hashCode() {
try {
return (Integer) super.h.invoke(this, hashCodeMethod, (Object[]) null);
}
catch (RuntimeException | Error ex1) {
throw ex1;
}
catch (Throwable ex2) {
throw new UndeclaredThrowableException(ex2);
}
}
3. How Proxy Works
After we have inspected the $Proxy class itself, it’s time to go one step further: how to generate the $Proxy class and how to load the $Proxy class? The key logic lies in the java.lang.reflect.Proxy and ProxyGenerator classes.
As new Java versions are released, the implementation details of the Proxy and ProxyGenerator classes continue to evolve. Roughly speaking, the ProxyGenerator is responsible for generating the $Proxy class’ byte array, and the Proxy class is responsible for loading this byte array into the JVM.
Now, let’s use Java 8, Java 11, and Java 17 for our discussion because they are LTS (Long-Term Support) editions.
3.1. Java 8
In Java 8, we can describe the $Proxy class generation process in five steps:
The Proxy::getProxyClass or Proxy::newProxyInstance method is our starting point — either one will invoke the Proxy::getProxyClass0 method. And, the Proxy::getProxyClass0 method is a private method and will further invoke the ProxyClassFactory::apply method.
The ProxyClassFactory is a static nested class defined in the Proxy class. And, its apply method figures out the package name, class name, and access flags of the upcoming class. Then, the apply method will invoke the ProxyGenerator::generateProxyClass method.
In Java 8, the ProxyGenerator class is a public class defined in the “sun.misc” package. It has migrated to the “java.lang.reflect” package since Java 9. And, the generateProxyClass method will create a ProxyGenerator instance, invoke its generateClassFile method whose responsibility is bytecode generation, optionally dump the class file, and return the resulting byte array.
After the bytecode generation succeeds, the Proxy::defineClass0 method is responsible for loading that byte array into the running JVM. Finally, we get a dynamically generated $Proxy class.
3.2. Java 11
Compared with the Java 8 edition, Java 11 has introduced three major changes:
- The Proxy class adds a new getProxyConstructor method and a static nested ProxyBuilder class
- For Java Module System, the ProxyGenerator has migrated to the “java.lang.reflect” package and become a package-private class
- To load the generated byte array into the JVM, the Unsafe::defineClass comes into play
3.3. Java 17
Compared with the Java 11 edition, Java 17 has two major changes:
- From the implementation perspective, the ProxyGenerator class utilizes the JDK built-in ASM to do the bytecode generation
- The JavaLangAccess::defineClass method is responsible for loading the generated bytecode into the JVM
4. Annotation Using Proxy
In Java, an annotation type is a special kind of interface type. But, we may be wondering how to create an annotation instance. In fact, we don’t need to. When we use the Java Reflection API to read an annotation, the JVM will dynamically generate a $Proxy class as the annotation type’s implementation:
FunctionalInterface instance = Consumer.class.getDeclaredAnnotation(FunctionalInterface.class);
Class<?> clazz = instance.getClass();
boolean isProxyClass = Proxy.isProxyClass(clazz);
assertTrue(isProxyClass);
In the above code snippet, we use the Consumer class to get its FunctionalInterface instance, then get the instance’s class, and finally, use the Proxy::isProxyClass method to check whether the class is a $Proxy class.
5. Conclusion
In this tutorial, we first introduced a dynamic proxy example, then dumped the generated $Proxy class and inspected its members. To go one step further, we explained how the Proxy and ProxyGenerator classes work together to generate and load the $Proxy class in different Java versions. Finally, we mentioned that an annotation type is also implemented by using the $Proxy class.
As usual, the source code for this tutorial can be found over on GitHub.