首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 靜態代理與動態代理深度解析:從手寫到 JDK Proxy 核心源碼

     編輯:Java 相關知識

代理模式核心角色

  • Subject (接口):定義代理與被代理的公共契約
  • RealSubject (被代理/委託類):真正執行業務邏輯
  • Proxy (代理類):控制訪問、增強功能(日誌、事務、權限)

一、靜態代理:編譯期確定,一對一綁定

代碼結構


// 1. 公共接口
public interface Service {
    void add();
}

// 2. 被代理類
public class ServiceImpl implements Service {
    @Override
    public void add() {
        System.out.println("執行添加用戶業務...");
    }
}

// 3. 代理類(手寫,編譯期就存在 .class)
public class ServiceProxy implements Service {
    private final Service target;
    
    public ServiceProxy(Service target) {
        this.target = target;
    }
    
    @Override
    public void add() {
        System.out.println("[代理] 開啟事務");
        target.add();
        System.out.println("[代理] 提交事務");
    }
}

// 4. 測試
public class StaticProxyTest {
    public static void main(String[] args) {
        Service target = new ServiceImpl();
        Service proxy = new ServiceProxy(target);
        proxy.add();
    }
}

痛點

  • 一個接口對應一個代理類,接口多、方法多時代碼膨脹
  • 接口新增方法,代理類、被代理類都要改,違背開閉原則

二、JDK 動態代理:運行時生成,一攬子解決

核心類

  • java.lang.reflect.Proxy:代理類生成工廠
  • java.lang.reflect.InvocationHandler:統一調用分發器

四步驟創建動態代理

// 1. 實現 InvocationHandler
public class ServiceInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public ServiceInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[動態代理] 方法執行前: " + method.getName());
        Object result = method.invoke(target, args); // 反射調用目標
        System.out.println("[動態代理] 方法執行後: " + method.getName());
        return result;
    }
}

// 2. 使用 Proxy.newProxyInstance 生成代理實例
public class DynamicProxyTest {
    public static void main(String[] args) {
        Service target = new ServiceImpl();
        InvocationHandler handler = new ServiceInvocationHandler(target);
        
        // 關鍵三參數:類加載器、實現的接口數組、處理器
        Service proxy = (Service) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler
        );
        
        proxy.add(); // 觸發 invoke()
    }
}

三、Proxy 核心源碼深度剖析 (JDK 8)

newProxyInstance 關鍵流程

public static Object newProxyInstance(ClassLoader loader,
                                      Class[] interfaces,
                                      InvocationHandler h) {
    // 1. 參數校驗
    Objects.requireNonNull(h);
    final Class[] intfs = interfaces.clone();
    // ... 安全檢查 ...
    
    // 2. 獲取/生成代理類 Class 對象(緩存機制)
    Class cl = getProxyClass0(loader, intfs);
    
    // 3. 獲取構造器(唯一參數為 InvocationHandler)
    final Constructor cons = cl.getConstructor(constructorParams);
    
    // 4. 實例化代理對象
    return cons.newInstance(new Object[]{h});
}

代理類生成邏輯

  • 緩存:proxyClassCache 以 (ClassLoader, Interfaces[]) 為 Key,避免重複生成
  • 命名:com.sun.proxy.$Proxy0、$Proxy1...
  • 繼承關係:extends Proxy implements Interface1, Interface2...
  • 方法實現:每個接口方法生成如下代碼:
    public final void add() throws {
        try {
            super.h.invoke(this, m0, null); // m0 為 Method 對象
        } catch (RuntimeException | Error e) { throw e; }
        catch (Throwable e) { throw new UndeclaredThrowableException(e); }
    }
    

四、動態代理的限制與補充

❌ 無法代理類(只能代理接口)

Java 單繼承機制導致:動態生成的類已繼承 Proxy,無法再繼承業務類。解決方案:CGLIB / ByteBuddy(子類繼承方式,字節碼增強)。

⚠️ 只能代理非 final、非 private、非 static 方法

final/private/static 方法無法被重寫,代理類中不會生成對應方法,調用直接走目標對象或拋異常。

🔧 默認方法

Java 8 接口默認方法,動態代理不會自動調用默認實現,除非在 invoke 中手動處理:

if (method.isDefault()) {
    // 手動調用默認方法 (Java 8+)
    MethodHandle mh = MethodHandles.lookup()
        .findSpecial(target.getClass(), method.getName(), 
                     MethodType.methodType(method.getReturnType(), method.getParameterTypes()), target.getClass());
    return mh.bindTo(target).invokeWithArguments(args);
}

五、手寫模擬 Proxy 生成過程(教學用)

原理:動態拼接 Java 源碼 → JavaCompiler 編譯 → ClassLoader 加載 → 反射實例化。生產環境絕不可用,僅供理解原理。

六、總結對比

維度靜態代理JDK 動態代理CGLIB 動態代理
生成時機編譯期運行時運行時
目標類型接口/類只能接口類/接口
性能最高(直接調用)反射調用,略低FastClass 機制,較快
適用場景固定、少量增強接口多、通用攔截無接口、第三方類增強

最佳實踐建議

  1. Spring AOP 默認:有接口用 JDK,無接口用 CGLIB(可強制 proxy-target-class=true 全用 CGLIB)
  2. 代理對象不要在構造器中調用被代理方法(代理未完全初始化)
  3. InvocationHandler 保持無狀態、線程安全,目標對象狀態由目標自己管理
  4. 大量代理生成注意 ProxyGenerator 緩存,避免類加載器洩漏
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved