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

33 阅读4分钟

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

要点

image.png

代码

请将以下代码保存为 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 的源码)

image.png

执行该命令后,当前目录下会有新的目录/文件生成。执行 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());
        }
    }
}

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

p.png

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

image.png

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

jdk.proxy1.$Proxy0 中有 m0/m1/m2/m3 字段,它们的类型都是 java.lang.reflect.Method\text{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,原因如下 ⬇️ image.png

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

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

image.png

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

其他

画 "要点" 一图所用到的代码

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

@startmindmap
'https://plantuml.com/mindmap-diagram

title 要点
caption \n\n
' caption 中的内容只是为了防止掘金平台自动生成的水印遮盖图中的文字

*:执行 <i>java</i> 命令时,
开启 <b><i>-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true</i></b> 选项
就可以将代理类保存至 <i>class</i> 文件里;
*:在代理类中,
可以找到 <i>m0/m1/m2/...</i> 之类的 <i>java.lang.reflect.Method</i> 类型的字段
它们与以下方法对应

* <i>hashCode()</i> 方法 (来自 <i>java.lang.Object</i>)
* <i>equals(Object)</i> 方法 (来自 <i>java.lang.Object</i>)
* <i>toString()</i> 方法 (来自 <i>java.lang.Object</i>)
* 接口中定义的方法;

*:以 <i>java.lang.Runnable</i> 接口为例
在我们调用代理类实例的 <i>run()</i> 方法时
这个实例会调用 <b><i>java.lang.reflect.InvocationHandler</i></b> 里的
<b><i>invoke(Object, Method, Object[])</i></b> 方法;

@endmindmap

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

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

@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()
}

note left of java.lang.reflect.Proxy::"Proxy(InvocationHandler h)"
<code>
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}
</code>
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
<code>
public $Proxy0(InvocationHandler var1) {
    super(var1);
}
</code>
end note

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

note left of jdk.proxy1.$Proxy0::equals
这个方法里是一个 <i>try-catch</i> 语句块, 其中核心的代码是下面这一行
<code>
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
</code>
end note

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

note left of jdk.proxy1.$Proxy0::run
这个方法里是一个 <i>try-catch</i> 语句块, 其中核心的代码是下面这一行
<code>
super.h.invoke(this, m3, (Object[])null);
</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

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

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

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


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

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

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

@endwbs