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

Java 靜態代理與動態代理深度解析:從買肉類比到 InvocationHandler 實作

     編輯:Java 相關知識

生活類比:買肉

  • 農夫:生產者,提供 buyMeat(money) -> meat。
  • 屠夫:代理,從農夫買肉,加工(注水 +5 斤),再賣給你。
  • 你:客戶端,只認「商販」介面,不關心具體是農夫還是屠夫。

1. 靜態代理

代理類由程式設計師手寫,編譯期確定。


interface Trader { int buyMeat(int money); }

class Farmer implements Trader {
    public int buyMeat(int money) { return money * 2; } // 1元買2斤
}

class Butcher implements Trader {
    private final Farmer farmer = new Farmer();
    public int buyMeat(int money) {
        int meat = farmer.buyMeat(money);
        return meat + 5; // 注水
    }
}

class You {
    public void work() {
        Trader trader = new Butcher(); // 可換 new Farmer()
        int meat = trader.buyMeat(10);
        System.out.println("得到肉:" + meat + " 斤");
    }
}

缺點:每個目標類都需手寫代理類,重複代碼多,維護成本高。

2. 動態代理

執行期透過 java.lang.reflect.Proxy 自動生成代理類,核心邏輯集中在 InvocationHandler。

InvocationHandler 介面


public interface InvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
  • proxy:代理物件本身(極少直接使用)。
  • method:正被調用的方法對象(反射)。
  • args:呼叫參數陣列。

完整動態代理實作


import java.lang.reflect.*;

public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 目標物件
        Trader farmer = new Farmer();

        // 代理邏輯
        InvocationHandler handler = (proxy, method, args) -> {
            System.out.println("[代理] 即將調用:" + method.getName());
            Object result = method.invoke(farmer, args); // 轉發給目標
            if (method.getName().equals("buyMeat") && result instanceof Integer) {
                result = (Integer) result + 5; // 增強:注水
            }
            System.out.println("[代理] 調用完成,結果:" + result);
            return result;
        };

        // 生成代理物件
        Trader proxy = (Trader) Proxy.newProxyInstance(
            Trader.class.getClassLoader(),
            new Class[]{Trader.class},
            handler
        );

        // 客戶端調用
        int meat = proxy.buyMeat(10);
        System.out.println("最終得到:" + meat + " 斤"); // 25
    }
}

interface Trader { int buyMeat(int money); }
class Farmer implements Trader {
    public int buyMeat(int money) { return money * 2; }
}

3. 靜態 vs 動態代理對比

維度靜態代理動態代理
代理類來源手寫 .java執行期字節碼生成
適用場景固定、少量目標類大量類、通用橫切邏輯
效能直接調用,最快反射調用,稍慢 (可用 MethodHandle 優化)
維護性分散集中在 InvocationHandler

4. 實戰應用場景

  • Spring AOP:@Transactional、@Cacheable、日誌、權限檢查皆由動態代理織入。
  • RPC 框架:Dubbo、gRPC 客戶端 Stub 本質是動態代理,網路通訊細節封裝在 Handler。
  • 測試 Mock:Mockito 透過動態代理生成 Mock 物件,錄製/驗證互動。
  • 延遲載入:Hibernate LazyInitialization、MyBatis 結果映射代理。

5. 進階:CGLIB / ByteBuddy

JDK 動態代理僅支援介面。代理具體類需用 CGLIB(繼承子類覆寫方法)或 ByteBuddy(更強大的字節碼操作庫)。Spring 預設:介面用 JDK,類用 CGLIB。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved