bigywater.tech
Java · 158

动态代理:JDK Proxy 和代理调用链怎么生成

JDK 动态代理的类是怎么来的、调用怎么走、异常怎么传?这篇用 Proxy.newProxyInstance 做小实验,再把运行时生成的代理类导出来用 javap 看(CGLIB 一类基于继承的代理不在本篇范围)。

public final
代理类的修饰符,父类是 java.lang.reflect.Proxy
UndeclaredThrowableException
handler 里直接 m.invoke 时,目标的 IOException 到调用方的样子
0.35 → 3.28 ns
直接调用 vs 动态代理的单次调用

代理类

实测 · 本机执行输出
  类名 = jdk.proxy1.$ProxyN,isProxyClass = true,修饰符 = public final,父类 = Proxy,实现 = [Main$Svc]
  依次调用 hello、add、toString、hashCode、equals,InvocationHandler 收到的方法:[Svc.hello, Svc.add, Object.toString, Object.hashCode, Object.equals]
  Object 的 toString/hashCode/equals 也被转发到了 handler;getClass() 没有(final 方法)
  代理类声明的方法:[add, dflt, equals, fail, hashCode, hello, proxyClassLookup, toString]

异常

实测 · 本机执行输出
  handler 抛 RuntimeException → 调用方收到 IllegalStateException:runtime
  handler 抛接口没声明的受检异常 Exception → 调用方收到 UndeclaredThrowableException,getCause = java.lang.Exception: checked-not-declared
  handler 里直接 m.invoke 调目标,目标抛 IOException(接口声明了)→ 调用方收到 UndeclaredThrowableException,getCause = InvocationTargetException(m.invoke 把目标的异常包进了 InvocationTargetException,它不是接口声明的异常)
  handler 里拆开 InvocationTargetException 再抛原异常 → 调用方收到 IOException disk

default 方法与调用链

实测 · 本机执行输出
  接口的 default 方法 dflt() 通过 InvocationHandler.invokeDefault 调用 = default-method
  handler 里的调用栈(栈顶 4 层):Main.lambda$main$5 ← jdk.proxy1.$ProxyN.hello ← Main.main ←
  代理套代理:outer.add(2, 3) 的调用链:
    [外层代理] add
    [内层代理] add
    结果 = 5

调用开销

实测 · 本机执行输出
  最后一轮(前 5 轮预热)每次调用:直接 0.35 ns,经过 JDK 动态代理(handler 里再 Method.invoke 目标)3.28 ns

导出的代理类

实测 · 本机执行输出
  导出的文件: ['jdk/proxy1/$ProxyN0.class']
    public jdk.proxy1.$ProxyN(java.lang.reflect.InvocationHandler);
    public final int hashCode();
    public final boolean equals(java.lang.Object);
    public final java.lang.String toString();
    public final java.lang.String hello(java.lang.String);
    public final int add(int, int);
    public final void fail() throws java.io.IOException;
    public final java.lang.String dflt();
  hello 方法的字节码:
    0: aload_0
    1: getfield #24 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
    4: aload_0
    5: getstatic #67 // Field m3:Ljava/lang/reflect/Method;
    8: iconst_1
    9: anewarray #49 // class java/lang/Object
    12: dup
    13: iconst_0

分步演示

JDK 动态代理
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:JDK 17 与 27 的代理类生成差异、CGLIB/ByteBuddy 等基于继承的代理、接口在不同模块或类加载器里的代理(可见性规则)、代理类的缓存、性能的严格基准(本篇的计时是自写循环)
    京ICP备2026067502号-1京公网安备11010502063487号