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

Java 代理模式與動態代理深度解析:從靜態到運行時生成的演進

     編輯:Java 程式設計入門

前言:為什麼需要代理?

在軟體設計中,代理模式 是一種結構型模式,核心思想是:「為目標對象提供一個替身 (代理),控制對目標的存取,並在不修改目標程式碼的前提下增強功能」。生活中的例子比比皆是:房仲代理買房、經紀人代理藝人商演、票務代理售票。核心價值在於 關注點分離:目標專注核心業務,代理處理横切關注點 (日誌、事務、權限、快取、延遲載入等)。

一、靜態代理:手寫代理類

結構角色

  • 抽象角色 (Subject):定義業務介面 (Interface)。
  • 真實角色 (RealSubject):實作核心業務邏輯。
  • 代理角色 (Proxy):實作同一介面,持有真實角色引用,轉發呼叫並可在前後插入邏輯。

程式碼實作

// 1. 抽象角色
public interface Business {
    void doAction();
}

// 2. 真實角色
public class BusinessImpl implements Business {
    @Override
    public void doAction() {
        System.out.println("執行核心業務邏輯...");
    }
}

// 3. 代理角色
public class BusinessProxy implements Business {
    private Business realSubject;

    public BusinessProxy(Business realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void doAction() {
        // 前置增強
        doBefore();
        
        // 委派真實對象
        realSubject.doAction();
        
        // 後置增強
        doAfter();
    }

    private void doBefore() { System.out.println("[Proxy] 前置處理:記錄日誌、檢查權限..."); }
    private void doAfter()  { System.out.println("[Proxy] 後置處理:釋放資源、統計耗時..."); }
}

// 4. 客戶端測試
public class Client {
    public static void main(String[] args) {
        Business real = new BusinessImpl();
        Business proxy = new BusinessProxy(real); // 依賴注入真實對象
        proxy.doAction();
    }
}

靜態代理痛點

  1. 類別爆炸:每個目標類都需手寫一個代理類,維護成本極高。
  2. 違反開閉原則:介面新增方法,真實類與代理類都要同步修改。
  3. 重複程式碼:前置/後置邏輯 (如事務、日誌) 在每個代理類中重複。

二、JDK 動態代理:運行時生成代理類

JDK 動態代理利用 反射機制,在運行時根據指定介面動態生成匿名代理類位元組碼,解決了「類別爆炸」問題。核心類:`java.lang.reflect.Proxy`、`java.lang.reflect.InvocationHandler`。

核心限制

只能代理實作了介面的類,無法代理普通類 (非介面) 或 final 類/方法。這是因為動態生成的類繼承自 `Proxy`,Java 不支援多重繼承。

程式碼實作

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 定義業務介面 (必須是介面)
public interface BusinessFoo { void foo(); }
public interface BusinessBar { String bar(String msg); }

// 2. 真實實作
public class BusinessFooImpl implements BusinessFoo {
    @Override public void foo() { System.out.println("BusinessFooImpl.foo()"); }
}
public class BusinessBarImpl implements BusinessBar {
    @Override public String bar(String msg) { 
        System.out.println("BusinessBarImpl.bar()"); 
        return msg; 
    }
}

// 3. 通用調用處理器 (InvocationHandler) - 核心邏輯集中在此
public class DynamicProxyHandler implements InvocationHandler {
    private final Object target; // 持有真實對象引用

    public DynamicProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 統一前置邏輯 (AOP 切面)
        System.out.println("[DynamicProxy] Before: " + method.getName());
        
        // 2. 反射調用真實方法
        Object result = method.invoke(target, args);
        
        // 3. 統一後置邏輯
        System.out.println("[DynamicProxy] After: " + method.getName());
        
        return result;
    }

    // 工廠方法:生成代理實例
    public static Object newProxyInstance(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),     // 類加載器
            target.getClass().getInterfaces(),      // 實作的介面陣列
            new DynamicProxyHandler(target)         // 處理器
        );
    }
}

// 4. 客戶端測試
public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 為 Foo 建立代理
        BusinessFoo fooProxy = (BusinessFoo) DynamicProxyHandler.newProxyInstance(new BusinessFooImpl());
        fooProxy.foo();

        System.out.println("---");

        // 為 Bar 建立代理 (同一 Handler 復用)
        BusinessBar barProxy = (BusinessBar) DynamicProxyHandler.newProxyInstance(new BusinessBarImpl());
        String ret = barProxy.bar("Hello Dynamic Proxy");
        System.out.println("Return: " + ret);
    }
}

運行流程解析

  1. 呼叫 `Proxy.newProxyInstance(loader, interfaces, handler)`,JVM 動態生成一個實作指定介面的匿名類 (繼承 `Proxy`)。
  2. 客戶端呼叫代理方法 (如 `fooProxy.foo()`)。
  3. JVM 將呼叫轉發給 `InvocationHandler.invoke(proxy, method, args)`。
  4. 開發者在 `invoke` 中編寫增強邏輯,並透過 `method.invoke(target, args)` 反射呼叫真實對象。
  5. 結果返回給客戶端。

三、CGLIB 動態代理:突破介面限制 (補充知識)

JDK 動態代理要求目標實作介面。CGLIB (Code Generation Library) 通過繼承目標類並覆寫非 final 方法,實現對 普通類 的代理。Spring AOP 預設策略:有介面用 JDK,無介面用 CGLIB (可強制 `proxyTargetClass=true`)。

// CGLIB 範例 (需引入 cglib 依賴)
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {
    private Object target;

    public Object getProxy(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 繼承目標類
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("[CGLIB] Before: " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // 呼叫父類 (目標) 方法
        System.out.println("[CGLIB] After: " + method.getName());
        return result;
    }
}

四、靜態 vs JDK 動態 vs CGLIB 對比總結

維度靜態代理JDK 動態代理CGLIB 動態代理
目標類要求實作介面必須實作介面普通類 (非 final)
生成方式編譯期手寫運行時反射生成位元組碼運行時繼承生成子類位元組碼
性能 (JDK 8+)最快 (直接調用)較快 (invoke 優化後接近直接調用)較快 (FastClass 機制索引調用)
方法限制無無法代理 final/private/static 方法無法代理 final/private/static 方法
適用場景固定、少量目標Spring AOP 預設、介面導向設計無介面類、第三方庫類增強

五、實戰應用:Spring AOP 底層原理

Spring AOP 正是建立在動態代理之上:

  • 切面 對應 `InvocationHandler` / `MethodInterceptor` 中的增強邏輯。
  • 切點 透過正則/註解匹配決定攔截哪些方法。
  • 通知 對應前置、後置、環繞、異常、最終通知。

理解動態代理原理,有助於排查 Spring AOP 失效場景 (如:內部方法呼叫繞過代理、final 方法無法增強、非 Spring Bean 無法代理)。

總結

代理模式是 AOP、RPC、ORM、快取等框架的基石。靜態代理 適合簡單場景;JDK 動態代理 是介面導向設計的標準選擇,效能優異;CGLIB 彌補了無介面類的代理缺口。掌握 `InvocationHandler.invoke` 與 `MethodInterceptor.intercept` 的編寫技巧,即掌握了框架級擴展的核心鑰匙。

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