Fundamentals 12 min read

Why Does JDK Dynamic Proxy Require an Interface? A Deep Dive into Proxy Mechanics

This article explains the inner workings of JDK dynamic proxies, demonstrates a complete example with interface and handler implementations, dissects the Proxy class source code, clarifies why interfaces are mandatory, and shows alternative proxy creation techniques, providing a thorough understanding for Java developers.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Why Does JDK Dynamic Proxy Require an Interface? A Deep Dive into Proxy Mechanics

Simple Example

First define an interface and its implementation:

<code>public interface Worker {
    void work();
}

public class Programmer implements Worker {
    @Override
    public void work() {
        System.out.println("coding...");
    }
}
</code>

Create a custom InvocationHandler to enhance the method logic:

<code>public class WorkHandler implements InvocationHandler {
    private Object target;
    WorkHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("work")) {
            System.out.println("before work...");
            Object result = method.invoke(target, args);
            System.out.println("after work...");
            return result;
        }
        return method.invoke(target, args);
    }
}
</code>

Generate the proxy instance in main :

<code>public static void main(String[] args) {
    Programmer programmer = new Programmer();
    Worker worker = (Worker) Proxy.newProxyInstance(
            programmer.getClass().getClassLoader(),
            programmer.getClass().getInterfaces(),
            new WorkHandler(programmer));
    worker.work();
}
</code>

Running the code prints:

<code>before work...
coding...
after work...
</code>

Proxy Source Code Analysis

The Proxy.newProxyInstance method performs parameter validation, obtains or generates a proxy class via getProxyClass0 , retrieves its constructor, and creates an instance using reflection.

<code>public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
    Objects.requireNonNull(h);
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } // catch omitted
}
</code>

The key steps are:

Parameter validation in checkProxyAccess .

Proxy class generation or cache lookup in getProxyClass0 .

Constructor retrieval via getConstructor .

Instance creation with newInstance (reflection).

The proxy class is generated by ProxyClassFactory.apply , which creates a unique class name (e.g., com.sun.proxy.$Proxy0 ) and bytecode via ProxyGenerator.generateProxyClass . The generated class extends Proxy and implements the target interface.

Mysterious Proxy Object

Debugging shows the proxy class name com.sun.proxy.$Proxy0 . To keep the generated class file, set the system property sun.misc.ProxyGenerator.saveGeneratedFiles=true either via JVM argument or programmatically.

<code>-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
</code>
<code>System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
</code>

The saved .class file can be decompiled (e.g., with jad ) to reveal the proxy implementation, which includes static initialization of Method objects for the interface methods and overrides of equals , hashCode , toString , and the interface method delegating to the InvocationHandler .

Why an Interface Is Required

JDK dynamic proxy works by creating a subclass of Proxy that holds an InvocationHandler . Because Java supports only single inheritance, the generated proxy class can extend Proxy but must implement the target interface to expose the desired methods. Hence, an interface is mandatory.

Alternative Approach

Another way to create a proxy is to obtain the proxy class directly and instantiate it via reflection:

<code>Class<?> proxyClass = Proxy.getProxyClass(Test3.class.getClassLoader(), Worker.class);
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
InvocationHandler workHandler = new WorkHandler(new Programmer());
Worker worker = (Worker) constructor.newInstance(workHandler);
worker.work();
</code>

This demonstrates the same underlying mechanism without the convenience method.

Summary

The article dissects the JDK dynamic proxy generation process, explains why interfaces are essential, and shows how the proxy class is built, cached, and instantiated. Understanding this mechanism is valuable for working with frameworks like Spring, MyBatis, and Feign that rely heavily on dynamic proxies.

JavaReflectionJDKDynamic ProxyInvocationHandler
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.