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

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

     編輯:Java 相關知識

一、代理模式核心意圖

代理模式為其他對象提供一個代理對象以控制對目標對象的存取。適用於:

  • 客戶端不想/不能直接引用目標對象(遠程、昂貴創建、權限控制)
  • 需在目標方法執行前後添加橫切邏輯(日誌、事務、緩存、監控)
  • 延遲初始化、複製時寫入、訪問控制等

三大核心角色

角色職責關鍵點
抽象角色聲明目標與代理共同介面Java 中用 interface 或 abstract class
真實角色實際業務邏輯實現最終被調用的對象
代理角色持有真實對象引用,實現同一介面,轉發調用並可增強靜態代理:編譯期確定;動態代理:運行時生成

二、靜態代理實戰

介面定義

public interface Subject {
    void request();
}

真實主題

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("From real subject.");
    }
}

代理主題(手寫)

public class ProxySubject implements Subject {
    private RealSubject realSubject; // 持有真實對象引用
    
    @Override
    public void request() {
        preRequest();           // 前置增強
        if (realSubject == null) realSubject = new RealSubject();
        realSubject.request();  // 委派真實對象
        postRequest();          // 後置增強
    }
    
    private void preRequest() { /* 日誌、權限檢查、緩存查找 */ }
    private void postRequest() { /* 耗時統計、結果緩存、資源清理 */ }
}

客戶端調用

Subject sub = new ProxySubject();
sub.request(); // 實際執行 ProxySubject.request()

靜態代理痛點

  1. 類爆炸:每個真實類需手寫一個代理類
  2. 強耦合:代理類編譯期依賴具體真實類
  3. 無法應對未知類型:插件系統、框架攔截器無法預知被代理類

三、JDK 動態代理機制詳解

核心類與介面

類/介面包核心作用
InvocationHandlerjava.lang.reflect代理邏輯處理器,單一方法 invoke(proxy, method, args)
Proxyjava.lang.reflect動態代理類生成器,靜態工廠方法 newProxyInstance

動態代理實戰步驟

1. 介面必須改為 interface(JDK 代理只能代理介面)

public interface Subject {
    void request();
}

2. 真實主題不變

public class RealSubject implements Subject {
    @Override public void request() { System.out.println("From real subject."); }
}

3. 實現 InvocationHandler

import java.lang.reflect.*;

public class DynamicInvocationHandler implements InvocationHandler {
    private final Object target; // 持有真實對象,類型為 Object 解耦具體類
    
    public DynamicInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before calling: " + method.getName());
        Object result = method.invoke(target, args); // 反射調用真實方法
        System.out.println("After calling: " + method.getName());
        return result;
    }
}

4. 客戶端生成代理實例

import java.lang.reflect.*;

public class Client {
    public static void main(String[] args) {
        RealSubject real = new RealSubject();
        InvocationHandler handler = new DynamicInvocationHandler(real);
        
        // 一步生成代理實例
        Subject proxy = (Subject) Proxy.newProxyInstance(
            real.getClass().getClassLoader(),  // 類加載器
            real.getClass().getInterfaces(),   // 代理實現的介面陣列
            handler                            // 處理器
        );
        
        proxy.request(); // 觸發 handler.invoke()
        
        // 驗證代理類特性
        System.out.println("Proxy class: " + proxy.getClass().getName()); // $Proxy0
        System.out.println("Is Proxy: " + Proxy.isProxyClass(proxy.getClass())); // true
    }
}

Proxy.newProxyInstance 內部流程

  1. 根據介面陣列動態生成實現這些介面的類字節碼(ProxyGenerator.generateProxyClass)
  2. 定義類、建構子(接受 InvocationHandler)、實現介面方法(委派給 handler.invoke)
  3. 實例化該類,注入 handler,返回代理實例
  4. 代理實例 is-a 所有指定介面,is-a Proxy,但 not 真實類

四、動態代理 vs 靜態代理對比

維度靜態代理JDK 動態代理
代理類產生時間編譯期運行時
代理類數量每個目標類一個單一通用邏輯服務多目標
目標類型要求任意類必須實現介面
方法攔截粒度手寫控制統一 invoke 攔截所有方法
效能開銷直接調用反射調用(JDK 8+ 已大幅優化)
適用場景固定、少量、簡單增強框架級、介面導向、批量攔截

五、CGLIB 動態代理:補足 JDK 無介面限制

JDK 動態代理要求目標實現介面。CGLIB(Code Generation Library)通過繼承目標類並重寫非 final 方法,實現無介面代理。Spring AOP 默認:有介面用 JDK,無介面用 CGLIB。

Maven 依賴

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    3.3.0
</dependency>

核心 API 對比

特性JDK ProxyCGLIB
核心類ProxyEnhancer
回調介面InvocationHandlerMethodInterceptor
代理方式實現介面繼承子類
final 方法可代理無法攔截(無法重寫)
私有方法不可代理不可代理
生成類命名$Proxy0$$EnhancerByCGLIB$$TargetClass$$uuid

CGLIB 範例

import net.sf.cglib.proxy.*;

public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class); // 目標類無需介面
        enhancer.setCallback(new MethodInterceptor() {
            @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;
            }
        });
        
        RealSubject proxy = (RealSubject) enhancer.create();
        proxy.request();
    }
}

六、進階主題與最佳實踐

1. 代理鏈與多層代理

Spring AOP 通過 ProxyChain 實現多個 Advice 串聯,每個 invoke 決定是否調用 chain.doProxy() 傳遞給下一層。

2. 序列化問題

JDK 動態代理實例無法直接序列化(不實現 Serializable)。解決:自定義 ProxyClassFactory 生成實現 Serializable 的代理類,或使用 CGLIB 並實現 Serializable。

3. 效能優化建議

  • 熱點方法考慮 MethodHandle / VarHandle 替代反射
  • 避免在 invoke 內做重型邏輯,異步化橫切關注點
  • 大量代理實例場景緩存 ProxyClass(JDK 內部已緩存)

4. 調試技巧

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
// 生成的 $Proxy0.class 會寫入當前目錄,可用 javap 反編譯分析

總結

代理模式是 AOP、RPC、緩存、事務等框架基礎設施的基石。JDK 動態代理以介面為契約、反射為機制,構建了 Java 標準庫級的運行時類型生成能力;CGLIB通過字節碼繼承補足了無介面場景。理解兩者生成原理、適用邊界、效能特徵,能讓你在框架選型、自定義攔截器、排查代理相關 Bug 時遊刃有餘。

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