相信大家都用过 JDK 中的动态代理功能。我们从 class 文件来看看,JDK 所生成的代理类长什么样子。
要点
代码
请将以下代码保存为 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 字段,它们的类型都是 。在 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.$Proxy0 的 static 语句块里,会为 m0/m1/m2/m3 字段赋值。
然后再看 run() 方法执行时发生了什么。先看看 jdk.proxy1.$Proxy0 中 run() 方法的逻辑 ⬇️
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);
而 invocationHandler 的 invoke(Object, Method, Object[]) 方法的逻辑是这样的 ⬇️ (如下图红框所示)
所以最终的效果就是,在标准输出打印 "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