動態代理是 Spring AOP、RPC 框架、緩存攔截、事務管理等無數框架的基石。JDK 原生 `java.lang.reflect.Proxy` 運行時生成實現指定介面的代理類,將方法調用分派到 `InvocationHandler`。本文剖析核心機制、實戰權限控制場景、字節碼結構、性能優化與 CGLIB 對比。
Subject (介面)
↑ 實現
RealSubject (真實對象) ←—— 委託 ——→ InvocationHandler (攔截邏輯)
↑ ↑
└────────── Proxy.newProxyInstance ───────┘
生成代理類 $Proxy0
實現 Subject
持有 InvocationHandler 引用
方法調用 → handler.invoke(proxy, method, args)
// 唯一入口:生成代理實例
public static Object newProxyInstance(
ClassLoader loader, // 代理類加載器 (通常用目標類加載器)
Class>[] interfaces, // 代理實現的介面列表 (至少一個)
InvocationHandler h // 方法調用攔截器
)
// InvocationHandler 核心方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
public interface UserService {
String getName();
String getEmail();
void setName(String name);
void setEmail(String email);
void deleteUser(); // 敏感操作
void changePassword(String newPwd);
}
public class UserServiceImpl implements UserService {
private String name;
private String email;
// 實現略...
}
public class PermissionInvocationHandler implements InvocationHandler {
private final UserService target;
private final String currentUserRole; // ADMIN / USER / GUEST
public PermissionInvocationHandler(UserService target, String role) {
this.target = target;
this.currentUserRole = role;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 1. 管理員全權限
if ("ADMIN".equals(currentUserRole)) {
return method.invoke(target, args);
}
// 2. 普通用戶:可讀、可改自身資料、可改密碼,不可刪除用戶
if ("USER".equals(currentUserRole)) {
if (methodName.startsWith("get") ||
methodName.equals("setName") ||
methodName.equals("setEmail") ||
methodName.equals("changePassword")) {
return method.invoke(target, args);
}
if (methodName.equals("deleteUser")) {
throw new AccessDeniedException("USER role cannot delete user");
}
}
// 3. 訪客:僅可讀
if ("GUEST".equals(currentUserRole)) {
if (methodName.startsWith("get")) {
return method.invoke(target, args);
}
throw new AccessDeniedException("GUEST role read-only");
}
throw new AccessDeniedException("Unknown role: " + currentUserRole);
}
}
public class ProxyFactory {
public static UserService createProxy(UserService target, String role) {
return (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new PermissionInvocationHandler(target, role)
);
}
}
// 測試
public static void main(String[] args) {
UserService real = new UserServiceImpl();
real.setName("Alice");
real.setEmail("alice@example.com");
UserService adminProxy = ProxyFactory.createProxy(real, "ADMIN");
UserService userProxy = ProxyFactory.createProxy(real, "USER");
UserService guestProxy = ProxyFactory.createProxy(real, "GUEST");
System.out.println("Admin delete: "); adminProxy.deleteUser(); // 成功
System.out.println("User delete: ");
try { userProxy.deleteUser(); } catch (AccessDeniedException e) { System.out.println("Denied: " + e.getMessage()); }
System.out.println("Guest setName: ");
try { guestProxy.setName("Bob"); } catch (AccessDeniedException e) { System.out.println("Denied: " + e.getMessage()); }
}
生成的 `$Proxy0` 類特徵:
// 反編譯 $Proxy0 概貌
public final class $Proxy0 extends Proxy implements UserService {
private static final Method m0, m1, m2, m3, m4, m5; // getName, getEmail, setName, setEmail, deleteUser, changePassword
static {
try {
m0 = UserService.class.getMethod("getName");
m1 = UserService.class.getMethod("getEmail");
m2 = UserService.class.getMethod("setName", String.class);
m3 = UserService.class.getMethod("setEmail", String.class);
m4 = UserService.class.getMethod("deleteUser");
m5 = UserService.class.getMethod("changePassword", String.class);
} catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); }
}
public $Proxy0(InvocationHandler h) { super(h); }
public String getName() {
try { return (String) super.h.invoke(this, m0, null); }
catch (Error|RuntimeException e) { throw e; }
catch (Throwable e) { throw new UndeclaredThrowableException(e); }
}
// 其他方法同理...
}
| 限制 | 說明 | 應對 |
|---|---|---|
| 只能代理介面 | 無法代理類、final 方法、private 方法 | 業務定義介面;需代理類用 CGLIB/ByteBuddy |
| 目標方法不可為 final | final 方法不走代理邏輯直接執行 | 避免核心業務方法標 final |
| 代理對象非目標類型實例 | `proxy instanceof TargetClass` 為 false | 面向介面編程,持有介面引用 |
| this 指向代理對象 | 目標內部調用 `this.method()` 不走代理 | 注入代理引用或用 AOP 框架 (Spring) 解決 |
| 序列化需特殊處理 | Proxy 實現 Serializable 但需自定義替換邏輯 | 實現 `writeReplace` 返回代理標識 |
| 維度 | JDK Proxy | CGLIB | ByteBuddy |
|---|---|---|---|
| 代理目標 | 介面 | 類 (繼承) | 介面/類/抽象類/註解 |
| 機制 | JVM 原生生成實現介面類 | ASM 生成子類重寫方法 | ASM API 生成任意類型 |
| final 方法 | 不代理 | 不代理 (無法重寫) | 不代理 |
| 性能 (調用開銷) | 中等 (反射調用) | 較好 (FastClass 索引調用) | 極佳 (可生成內聯代碼) |
| Spring 默認策略 | 目標實現介面 → JDK Proxy | 目標無介面/強制 proxyTargetClass=true | Spring 6+ 可選 ByteBuddy |
| 適用場景 | 標準 AOP、RPC、介面導向設計 | 舊代碼無介面、第三方類增強 | 框架開發、複雜字節碼操作、測試 Mock |
JDK 動態代理以「介面 + InvocationHandler」極簡模型,賦能 AOP、RPC、安全、監控等橫切關注點分離。掌握其生成機制、限制邊界、性能特徵,是閱讀 Spring 源碼、設計擴展點、排查代理相關 Bug 的核心功力。現代框架開發建議:業務面向介面 + JDK Proxy 為主,ByteBuddy 解決介面限制與極致性能,CGLIB 維護兼容。