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

Java 設計模式精講:靜態代理與動態代理原理、實現與優劣對比

     編輯:Java 綜合教學

代理模式核心思想

為目標對象提供一個代理對象,控制對目標的訪問。客戶端不直接引用目標,通過代理間接操作,實現關注點分離:業務邏輯與横切關注點 (日誌、事務、權限、緩存) 解耦。

靜態代理:編碼期確定

場景:機票代購

  • 客戶 (TravelPeople) 想買票
  • 代理平台 (BuyTicketProxy) 替客戶下單
  • 航空公司 (目標) 執行出票

代碼結構

// 1. 共同接口
public interface BuyTicketInterface { void buyTicket(); }

// 2. 目標對象
public class TravelPeople implements BuyTicketInterface {
    public void buyTicket() {
        System.out.println("買 5/1 重慶→北京 14:00-24:00 航班");
    }
}

// 3. 代理對象 (持有目標引用)
public class BuyTicketProxy implements BuyTicketInterface {
    private TravelPeople target = new TravelPeople();
    public void buyTicket() {
        if (target != null) target.buyTicket(); // 可在此加前後邏輯
    }
}

// 4. 客戶端使用代理
public class TicketService {
    public static void main(String[] args) {
        BuyTicketInterface proxy = new BuyTicketProxy();
        proxy.buyTicket(); // 輸出目標邏輯
    }
}

優缺點總結

優點缺點
業務類純粹,復用性高、耦合低一個代理類只服務一類目標,方法多時代碼膨脹
接口新增方法,所有實現類與代理類同步修改,維護成本大

動態代理:運行期生成

代理類字節碼由 JVM 運行時通過反射動態生成,不存在 .class 文件。解決靜態代理「類爆炸」與「接口變更連鎖修改」問題。

核心 API 三劍客

  1. java.lang.reflect.Proxy:動態代理類父類,提供靜態工廠方法 newProxyInstance。
  2. java.lang.reflect.InvocationHandler:調用處理器接口,單一方法 invoke(proxy, method, args) 集中分派所有代理方法調用。
  3. java.lang.ClassLoader:載入動態生成的代理類字節碼。

動態代理四步驟

  1. 實現 InvocationHandler,持有目標對象引用,invoke 中執行前後邏輯並反射調用目標方法。
  2. 調用 Proxy.getProxyClass(loader, interfaces) 獲取代理類 Class 對象。
  3. 反射獲取構造器 Constructor<?> c = clazz.getConstructor(InvocationHandler.class)。
  4. 實例化代理 Interface proxy = (Interface) c.newInstance(handler)。

簡化版 (一步到位): Proxy.newProxyInstance(loader, interfaces, handler)。

完整實戰代碼

// 1. 調用處理器
public class BuyTicketHandler implements InvocationHandler {
    private Object target;
    public BuyTicketHandler(Object target) { this.target = target; }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("開始買票");          // 前置增強
        Object result = method.invoke(target, args); // 反射調用目標
        System.out.println("調用買票");          // 後置增強
        return result;
    }
}

// 2. 工廠獲取代理
public class BuyTicketProxy {
    public static BuyTicketInterface createProxy() {
        BuyTicketInterface target = new TravelPeople();
        InvocationHandler handler = new BuyTicketHandler(target);
        return (BuyTicketInterface) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler
        );
    }
}

// 3. 測試
BuyTicketInterface proxy = BuyTicketProxy.createProxy();
proxy.buyTicket();
// 輸出:
// 開始買票
// 買 5/1 重慶→北京 14:00-24:00 航班
// 調用買票

動態代理生成類特征 (源碼級洞察)

  • 包名:代理接口全 public → 頂層包;含非 public 接口 → 該接口所在包。
  • 修飾符:public final,可訪問、不可繼承。
  • 類名:$ProxyN (N 遞增)。同一接口組合複用已生成類,提升效率。
  • 繼承鏈:繼承 Proxy,實現目標所有接口。

優劣深度對比

維度靜態代理動態代理 (JDK)
代碼量隨方法線性增長集中在 invoke,O(1)
接口變更影響所有代理類需同步修改無需修改代理邏輯
目標類要求無必須實現接口
性能直接調用,極快反射調用,微小開銷
適用場景固定、少量方法方法多、頻變、AOP 橫切關注點

JDK 動態代理的遺憾與補完

無法代理類 (只能代理接口):因代理類已繼承 Proxy,Java 單繼承限制無法再繼承目標類。歷史遺留類無接口則無法使用。

解決方案: CGLIB / ByteBuddy 等字節碼操作庫,通過繼承目標類生成子類實現代理,無需接口。Spring AOP 默認:接口用 JDK,類用 CGLIB。

選型建議

  • 簡單、固定場景 → 靜態代理 (可讀性最強)。
  • 企業級 AOP、框架開發、方法多變 → JDK 動態代理 (有接口) / CGLIB (無接口)。
  • 高性能熱點路徑 → 靜態代理或手寫字節碼 (極少見)。

設計模式無銀彈,結合項目實際「易擴展、低耦合」為最高原則。

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