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

Java 靜態代理與動態代理深度解析:原理、實現與場景對比

     編輯:Java 相關知識

代理模式核心思想

為目標對象提供一個代理,控制對目標的訪問。兩大優勢:

  1. 隱藏實現細節:客戶端只依賴接口,不依賴具體實現類
  2. 解耦擴展:在不修改目標類代碼的前提下,添加額外邏輯(日誌、事務、權限、緩存)

一、靜態代理

定義

代理類在編譯期就已存在(.java → .class),通常由開發者手寫或工具生成。代理類與目標類實現同一接口或繼承同一父類。

經典結構

// 接口
public interface Sell {
    void sell();
    void ad();
}

// 目標類(被代理)
public class Vendor implements Sell {
    public void sell() { System.out.println("廠家賣貨"); }
    public void ad() { System.out.println("廠家廣告"); }
}

// 靜態代理類
public class BusinessAgent implements Sell {
    private Vendor vendor; // 聚合目標對象
    public BusinessAgent(Vendor vendor) { this.vendor = vendor; }

    public void sell() {
        System.out.println("代理:售前檢查");
        vendor.sell(); // 委託
        System.out.println("代理:售後服務");
    }
    public void ad() {
        System.out.println("代理:廣告投放前審核");
        vendor.ad();
        System.out.println("代理:廣告效果統計");
    }
}

優缺點

優點缺點
簡單直觀,易理解一個目標類對應一個代理類,代碼冗餘嚴重
可針對單方法定制邏輯接口新增方法,目標與代理都要改,維護成本高
無運行時性能損耗無法統一處理橫切關注點(如所有方法加事務)

二、動態代理

定義

代理類在運行時通過反射動態生成(JDK Proxy 或 CGLIB),無需手寫代理類源碼。

核心組件

  1. InvocationHandler:調用處理器接口,唯一方法 invoke(proxy, method, args)
  2. Proxy.newProxyInstance():生成代理對象的靜態工廠方法

JDK 動態代理實現步驟

// 1. 目標類必須實現接口
public class Vendor implements Sell { ... }

// 2. 實現 InvocationHandler(統一橫切邏輯)
public class DynamicProxy implements InvocationHandler {
    private Object target; // 持有目標對象引用
    public DynamicProxy(Object target) { this.target = target; }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before: " + method.getName());
        Object result = method.invoke(target, args); // 反射調用目標方法
        System.out.println("after: " + method.getName());
        return result;
    }
}

// 3. 客戶端生成代理並調用
public class Main {
    public static void main(String[] args) {
        Vendor vendor = new Vendor();
        DynamicProxy handler = new DynamicProxy(vendor);
        // 關鍵:生成實現 Sell 接口的代理類實例
        Sell proxy = (Sell) Proxy.newProxyInstance(
            Sell.class.getClassLoader(),   // 類加載器
            new Class[]{Sell.class},       // 代理實現的接口列表
            handler                        // 調用處理器
        );
        proxy.sell(); // 實際調用 handler.invoke()
        proxy.ad();
    }
}

動態代理原理圖解

Client → Proxy Instance (實現 Sell)
              ↓
        InvocationHandler.invoke()
              ↓
        Method.invoke(target, args) → Vendor.sell()

本質:兩層靜態代理疊加。InvocationHandler 是 Vendor 的代理;生成的 Proxy 實例是 InvocationHandler 的代理。

保存生成的代理類文件(調試用)

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 會在項目目錄生成 $Proxy0.class,可用 jad 反編譯查看

三、CGLIB 代理(補充)

  • 針對未實現接口的類,通過繼承生成子類代理
  • 核心:Enhancer + MethodInterceptor
  • Spring AOP 默認:有接口用 JDK,無接口用 CGLIB(可強制 proxyTargetClass=true)

四、靜態 vs 動態 選型指南

場景推薦理由
目標類固定、方法少、邏輯差異大靜態代理代碼可讀性高,無反射開銷
統一處理所有方法(事務、日誌、權限)動態代理一處定義,處處生效,符合 AOP 思想
目標類無接口、第三方庫類CGLIB繼承代理,無需接口
高性能、高併發核心鏈路靜態代理 / 手寫字節碼避免反射調用開銷

五、實戰最佳實踐

  1. 接口隔離:業務接口與代理邏輯解耦,便於 JDK 動態代理
  2. 保護目標方法:final 方法不可被代理(CGLIB 也無法攔截)
  3. 序列化支持:動態代理對象默認不可序列化,需實現 Serializable 並處理 writeReplace
  4. 調試技巧:保存 $Proxy0.class 反編譯,理解代理結構
  5. Spring AOP 實戰:@Aspect + @Around 替代手寫 InvocationHandler,聲明式事務 @Transactional 即典型應用

總結

代理模式是 AOP 的基石。靜態代理 適合簡單、固定場景;JDK 動態代理 基於接口、反射生成,是 Spring AOP 默認選擇;CGLIB 補足無接口場景。理解 InvocationHandler.invoke() 統一攔截入口,掌握「雙層代理」原理,即可舉一反三應對各種框架源碼閱讀與自定義攔截需求。

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