JDK 動態代理是 Spring AOP、RPC 框架的核心基礎設施。本文從 Proxy.newProxyInstance() 入口出發,層層追蹤至 ProxyGenerator.generateProxyClass() 字節碼生成細節,並反編譯生成的代理類驗證運行時結構。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
Objects.requireNonNull(h);
Class<?>[] intfs = interfaces.clone();
SecurityManager sm = System.getSecurityManager();
if (sm != null) checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
// 核心:查找或生成代理類
Class<?> cl = getProxyClass0(loader, intfs);
// 實例化代理對象
Constructor<?> cons = cl.getConstructor(constructorParams);
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(() -> { cons.setAccessible(true); return null; });
}
return cons.newInstance(new Object[]{h});
}
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache
= new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
if (interfaces.length > 65535) throw new IllegalArgumentException("interface limit exceeded");
return proxyClassCache.get(loader, interfaces); // 緩存獲取或生成
}
ConcurrentHashMap + WeakReference 鍵,防止 ClassLoader 洩漏Key0/Key1/Key2/KeyX)public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 1. 校驗接口可見性、無重複、均為 interface
// 2. 確定包名:非 public 接口必須同包,否則用 com.sun.proxy
// 3. 生成類名:$Proxy + 原子計數器($Proxy0, $Proxy1...)
// 4. 生成字節碼
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
// 5. 定義類
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
}
private byte[] generateClassFile() {
// Step 1: 收集所有需代理方法
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
for (Class<?> intf : interfaces)
for (Method m : intf.getMethods()) addProxyMethod(m, intf);
checkReturnTypes(proxyMethods.values()); // 校驗重載方法返回類型一致性
// Step 2: 構造字段表、方法表
methods.add(generateConstructor()); // 構造器接收 InvocationHandler
for (List<ProxyMethod> sigmethods : proxyMethods.values())
for (ProxyMethod pm : sigmethods) {
fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
methods.add(pm.generateMethod()); // 生成代理方法體
}
methods.add(generateStaticInitializer()); // 靜態初始化 Method 對象
// Step 3: 輸出 ClassFile 格式
// magic(CAFEBABE), version, constant_pool, access_flags, this_class, super_class, interfaces, fields, methods, attributes
}
final class $Proxy0 extends Proxy implements pro {
// 靜態 Method 字段:每個代理方法對應一個
private static Method m1; // equals
private static Method m2; // toString
private static Method m3; // text (業務方法)
private static Method m0; // hashCode
public $Proxy0(InvocationHandler var1) { super(var1); }
// 所有方法最終委託 h.invoke(this, method, args)
public final void text() {
try { super.h.invoke(this, m3, (Object[])null); }
catch (RuntimeException | Error e) { throw e; }
catch (Throwable e) { throw new UndeclaredThrowableException(e); }
}
static {
// 靜態塊解析 Method 對象
m3 = Class.forName("spring.commons.api.study.CreateModel.pro").getMethod("text", new Class[0]);
...
}
}
InvocationHandler h 字段與基礎代理能力UndeclaredThrowableException(繼承 RuntimeException)WeakCache 允許 ClassLoader 卸載時代理類被 GC 回收System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 生成 $Proxy0.class 文件於當前目錄,可用 javap 或反編譯工具分析
JDK 動態代理通過運行時字節碼生成(ASM 風格手工拼裝 ClassFile)、弱引用緩存複用、InvocationHandler 統一分發三大機制,實現了零侵入的接口代理。理解其生成邏輯,對掌握 Spring AOP 底層、排查代理類加載問題、設計高性能 RPC 框架具有指導意義。