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

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

代码

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

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)

bash 复制代码
javac -parameters MyProxy.java

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

bash 复制代码
java MyProxy

运行结果如下 ⬇️

text 复制代码
Hello world

将代理类保存至 class 文件中

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

bash 复制代码
java -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true MyProxy

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

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

text 复制代码
.
├── jdk
│   └── proxy1
│       └── $Proxy0.class
├── MyProxy.class
└── MyProxy.java

3 directories, 3 files

结果分析

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

java 复制代码
//
// 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());
        }
    }
}

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

可以这样概括, 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 字段赋值。

其他

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

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

puml 复制代码
@startuml
'https://plantuml.com/class-diagram

title <i>jdk.proxy1.$Proxy0</i> 的类图
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()
}

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::hashCode
<code>
public final int hashCode() {
    try {
        return (Integer)super.h.invoke(this, m0, (Object[])null);
    } catch (...) {
}
</code>
end note

note left of jdk.proxy1.$Proxy0::equals
<code>
public final boolean equals(Object var1) {
    try {
        return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    } catch (...) {
}
</code>
end note

note left of jdk.proxy1.$Proxy0::toString
<code>
public final String toString() {
    try {
        return (String)super.h.invoke(this, m2, (Object[])null);
    } catch (...) {
}
</code>
end note

note left of jdk.proxy1.$Proxy0::run
<code>
public final void run() {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (...) {
}
</code>
end note

note bottom of jdk.proxy1.$Proxy0
<i>jdk.proxy1.$Proxy0</i> 类里有如下的 <i>static</i> 语句块
<code>
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());
    }
}
</code>
end note

@enduml
相关推荐
C++ 老炮儿的技术栈1 小时前
两个线程对socket 进行读和写,需要加锁吗
java·服务器·网络
萍萍学习2 小时前
蓝桥杯JAVA-4
java·职场和发展·蓝桥杯
顶点多余2 小时前
深度剖析Linux 线程概念
java·linux·jvm
多租户观察室2 小时前
工作流新生态:2026年工作流与Coding的重新分工
前端·人工智能·后端·低代码
_MyFavorite_2 小时前
JAVA重点基础、进阶知识及易错点总结(8)List 接口(ArrayList、LinkedList、Vector)
java·开发语言·list
第二层皮-合肥2 小时前
基于C#的工业测控软件-依赖库
java·开发语言
Carsene2 小时前
开源项目文档架构设计:Git Submodule 实现文档与代码的优雅分离
前端·后端
openallzzz2 小时前
【面经分享】Java实习
java·开发语言
indexsunny2 小时前
互联网大厂Java面试:从Spring Boot到微服务的逐步挑战
java·数据库·spring boot·redis·微服务·面试·电商