[Java] 从 class 文件看动态代理

作者:互联网

2026-03-28

Java教程

相信大家都用过 JDK 中的动态代理功能。我们从 class 文件来看看,JDK 所生成的代理类长什么样子。

要点

  • 执行 java 命令时,开启 -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true 选项就可以将代理类保存至 class 文件里
  • 在代理类中,我们可以找到 m0/m1/m2/... 之类的 java.lang.reflect.Methodtext{java.lang.reflect.Method} 类型的字段,它们会与以下方法对应
    • hashCode() 方法(来自 java.lang.Object
    • equals(Object) 方法(来自 java.lang.Object
    • toString() 方法(来自 java.lang.Object
    • 接口中定义的方法
  • Runnable 接口为例,当我们用代理类的实例调用 run() 方法时,这个实例会调用 InvocationHandler 里的 invoke(Object, Method, Object[]) 方法

代码

请将以下代码保存为 MyProxy.java ⬇️

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class MyProxy {

    public static void main(String[] args) {
        InvocationHandler invocationHandler = (proxy, method, params) -> {
            System.out.println("Hello world");
            return null;
        };
        Runnable runnable = (Runnable) Proxy.newProxyInstance(
                MyProxy.class.getClassLoader(), new Class[]{Runnable.class}, invocationHandler);
        runnable.run();
    }
}

编译与运行

用以下命令可以编译 MyProxy.java (我是用的 JDK 版本是 21)

javac -parameters MyProxy.java

编译后会生成 MyProxy.class 文件。执行以下命令可以运行 MyProxy 类中的 main 方法 ⬇️

java MyProxy

运行结果如下 ⬇️

Hello world

将代理类保存至 class 文件中

如果想看到 JDK 所生成的代理类的内容,可以执行如下命令

java -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true MyProxy

至于为什么使用这个选项就可以将代理类保存到 class 文件里,可以参考以下代码 ⬇️ (以下截图来自 JDK 25 的源码)

执行该命令后,当前目录下会有新的目录/文件生成。执行 tree . 命令,可以看到如下结果

.
├── jdk
│   └── proxy1
│       └── $Proxy0.class
├── MyProxy.class
└── MyProxy.java

3 directories, 3 files

分析代理类

通过执行 javap -v -p jdk.proxy1.$Proxy0 命令可以查看 $Proxy0.class 文件的内容,但是自己去反编译太花时间了,而且很容易出错,我们直接借助 Intellij IDEA 来看 $Proxy0.class 文件的内容。完整的结果如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jdk.proxy1;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Runnable {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;

    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void run() {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        ClassLoader var0 = $Proxy0.class.getClassLoader();

        try {
            m0 = Class.forName("java.lang.Object", false, var0).getMethod("hashCode");
            m1 = Class.forName("java.lang.Object", false, var0).getMethod("equals", Class.forName("java.lang.Object", false, var0));
            m2 = Class.forName("java.lang.Object", false, var0).getMethod("toString");
            m3 = Class.forName("java.lang.Runnable", false, var0).getMethod("run");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(((Throwable)var2).getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(((Throwable)var3).getMessage());
        }
    }

    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(var0.toString());
        }
    }
}

为了便于理解,我画了张简略的类图 ⬇️

我们先看 main 方法整体的逻辑 ⬇️

然后再细看 runnable.run(); 这行代码执行时发生了什么(请注意:runnable 对象是 jdk.proxy1.$Proxy0 这个代理类的一个实例)。

jdk.proxy1.$Proxy0 中有 m0/m1/m2/m3 字段,它们的类型都是 java.lang.reflect.Methodtext{java.lang.reflect.Method}。在 jdk.proxy1.$Proxy0 中可以找到如下的 static 语句块

static {
    ClassLoader var0 = $Proxy0.class.getClassLoader();

    try {
        m0 = Class.forName("java.lang.Object", false, var0).getMethod("hashCode");
        m1 = Class.forName("java.lang.Object", false, var0).getMethod("equals", Class.forName("java.lang.Object", false, var0));
        m2 = Class.forName("java.lang.Object", false, var0).getMethod("toString");
        m3 = Class.forName("java.lang.Runnable", false, var0).getMethod("run");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(((Throwable)var2).getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(((Throwable)var3).getMessage());
    }
}

看起来正常情况下,catch 语句块不会被执行。那么就可以这样概括了 ⬇️

jdk.proxy1.$Proxy0 中的 m0/m1/m2/m3 字段分别与以下方法对应

  • hashCode() 方法(来自 java.lang.Object
  • equals(Object) 方法(来自 java.lang.Object
  • toString() 方法(来自 java.lang.Object
  • run() 方法(来自 java.lang.Runnable

jdk.proxy1.$Proxy0static 语句块里,会为 m0/m1/m2/m3 字段赋值。

然后再看 run() 方法执行时发生了什么。先看看 jdk.proxy1.$Proxy0run() 方法的逻辑 ⬇️

public final void run() {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

这里的 super.h 所获取的对象其实就是我们在 main 方法里创建的 invocationHandler,原因如下 ⬇️

所以 super.h.invoke(this, m3, (Object[])null); 这行代码的作用相当于 ⬇️ invocationHandler.invoke(this, m3, (Object[])null);

invocationHandlerinvoke(Object, Method, Object[]) 方法的逻辑是这样的 ⬇️ (如下图红框所示)

所以最终的效果就是,在标准输出打印 "Hello world"

其他

画 "jdk.proxy1.$Proxy0 的类图" 所用到的代码

我借助了 PlantUML 的插件来画那张图,用到的代码如下

@startuml
'https://plantuml.com/class-diagram

title jdk.proxy1.$Proxy0 的类图
caption 请注意: 图中只画了本文关心的字段/方法

interface java.io.Serializable
class java.lang.reflect.Proxy
interface java.lang.Runnable
class jdk.proxy1.$Proxy0

java.io.Serializable <|.. java.lang.reflect.Proxy
java.lang.reflect.Proxy <|-- jdk.proxy1.$Proxy0
java.lang.Runnable <|.. jdk.proxy1.$Proxy0

class java.lang.reflect.Proxy {
    # InvocationHandler h
    - Proxy()
    # Proxy(InvocationHandler h)
}

interface java.lang.Runnable {
    void run()
}

note left of java.lang.reflect.Proxy::"Proxy(InvocationHandler h)"

protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

end note

class jdk.proxy1.$Proxy0 {
    - {static} final Method m0
    - {static} final Method m1
    - {static} final Method m2
    - {static} final Method m3
    + $Proxy0(InvocationHandler var1)
    + final int hashCode()
    + final boolean equals(Object var1)
    + final String toString()
    + final void run()
}

note left of jdk.proxy1.$Proxy0::$Proxy0

public $Proxy0(InvocationHandler var1) {
    super(var1);
}

end note

note left of jdk.proxy1.$Proxy0::hashCode
这个方法里是一个 try-catch 语句块, 其中核心的代码是下面这一行

return (Integer)super.h.invoke(this, m0, (Object[])null);

end note

note left of jdk.proxy1.$Proxy0::equals
这个方法里是一个 try-catch 语句块, 其中核心的代码是下面这一行

return (Boolean)super.h.invoke(this, m1, new Object[]{var1});

end note

note left of jdk.proxy1.$Proxy0::toString
这个方法里是一个 try-catch 语句块, 其中核心的代码是下面这一行
return (String)super.h.invoke(this, m2, (Object[])null);
end note

note left of jdk.proxy1.$Proxy0::run
这个方法里是一个 try-catch 语句块, 其中核心的代码是下面这一行

super.h.invoke(this, m3, (Object[])null);

end note

note bottom of jdk.proxy1.$Proxy0
jdk.proxy1.$Proxy0 类里有如下的 static 语句块

static {
    ClassLoader var0 = $Proxy0.class.getClassLoader();

    try {
        m0 = Class.forName("java.lang.Object", false, var0).getMethod("hashCode");
        m1 = Class.forName("java.lang.Object", false, var0).getMethod("equals", Class.forName("java.lang.Object", false, var0));
        m2 = Class.forName("java.lang.Object", false, var0).getMethod("toString");
        m3 = Class.forName("java.lang.Runnable", false, var0).getMethod("run");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(((Throwable)var2).getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(((Throwable)var3).getMessage());
    }
}

end note

@enduml

画 "MyProxy 中的 main 方法执行时发生了什么?" 所用到的代码

我借助了 PlantUML 的插件来画那张图,用到的代码如下

@startwbs
'https://plantuml.com/wbs-diagram


title MyProxy 中的 main 方法执行时发生了什么?

* MyProxy 中的 main 方法执行时发生了什么?
**[#lightblue]:将 java.lang.reflect.InvocationHandler 的一个实例
保存在 invocationHandler 变量中;
**[#lightgreen]:调用 java.lang.reflect.ProxynewProxyInstance(ClassLoader, Class[], InvocationHandler) 方法
将得到的实例转化为 java.lang.Runnable 类型
并保存至 runnable 变量中;
*** 一些准备工作(本文不关心其中的细节)
*** 调用 jdk.proxy1.$Proxy0 的构造函数
****[#lightblue]:这个构造函数需要一个
java.lang.reflect.InvocationHandler 对象
invocationHandler 会被用在这里;
*** 其他逻辑(本文不关心其中的细节)
**[#lightgreen] 调用  runnable 对象的 run() 方法

legend left
<&star> 两个浅蓝色节点都和 invocationHandler 变量直接相关
<&star> 两个浅绿色节点都和 runnable 变量直接相关
end legend

@endwbs