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

JDK 動態代理深度解析:從原理到手寫實現

     編輯:Java 相關知識

代理模式核心角色

代理模式三大核心角色:

  • 抽象主題:定義真實主題與代理共同接口,客戶端面向接口編程
  • 真實主題:實現具體業務邏輯的類
  • 代理主題:持有真實主題引用,控制訪問,可在調用前後織入橫切邏輯

JDK 動態代理機制

  • 基於接口:目標類必須實現一個或多個接口
  • 僅代理接口方法:非接口方法(如 Object 方法、私有方法)不被代理
  • 核心類:java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler
  • 生成原理:運行時動態生成實現目標接口的匿名類,將方法調用委派給 InvocationHandler.invoke()

手寫實戰:「分手使者」案例完整代碼

1. 定義抽象主題接口

package www.csdn.spring.jdk.proxy;

public interface SayGoodBye {
    void say(String content);
}

2. 真實主題實現

package www.csdn.spring.jdk.proxy;

public class SayGoodByeImpl implements SayGoodBye {
    @Override
    public void say(String content) {
        System.out.println("say: " + content);
    }
}

3. 代理類實現 InvocationHandler

package www.csdn.spring.jdk.proxy;

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

public class JDKProxy implements InvocationHandler {

    private Object target; // 真實主題引用

    // 工廠方法:生成代理實例
    public Object createProxyInstance(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  // 1. 類加載器
            target.getClass().getInterfaces(),   // 2. 實現的接口列表
            this                                  // 3. 調用處理器
        );
    }

    // 核心攔截邏輯
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnValue = null;

        beforeMethod(); // 前置增強

        returnValue = method.invoke(target, args); // 調用真實方法

        afterMethod(); // 後置增強

        return returnValue;
    }

    private void beforeMethod() {
        System.out.println("--------------------- 目標方法之前執行");
    }

    private void afterMethod() {
        System.out.println("--------------------- 目標方法之後執行");
    }
}

4. 客戶端測試

package www.csdn.spring.jdk.proxy;

import org.junit.Test;

public class TestSay {
    @Test
    public void say() {
        // 真實主題
        SayGoodByeImpl realSubject = new SayGoodByeImpl();

        // 創建代理
        JDKProxy jdkProxy = new JDKProxy();
        SayGoodBye proxySubject = (SayGoodBye) jdkProxy.createProxyInstance(realSubject);

        // 通過代理調用
        proxySubject.say("你和他分手吧!!!");
    }
}

5. 運行輸出

--------------------- 目標方法之前執行
say: 你和他分手吧!!!
--------------------- 目標方法之後執行

核心原理深度剖析

Proxy.newProxyInstance 三大參數

參數含義獲取方式
ClassLoader代理類加載器target.getClass().getClassLoader()
Class[] interfaces代理實現的接口列表target.getClass().getInterfaces()
InvocationHandler方法調用分發器自定義實現類

InvocationHandler.invoke 三大參數

  • proxy:代理對象自身引用(極少直接使用)
  • method:被調用的接口方法 Method 對象
  • args:調用參數數組

動態生成的代理類結構(反編譯視角)

public final class $Proxy0 extends Proxy implements SayGoodBye {
    protected $Proxy0(InvocationHandler h) { super(h); }
    
    public final void say(String content) {
        super.h.invoke(this, m1, new Object[]{content}); // m1 為 say 方法 Method 對象
    }
    
    // equals、hashCode、toString 也被代理(來自 Object)
}

JDK 動態代理 vs CGLIB 關鍵差異

維度JDK 動態代理CGLIB
前提條件目標類實現接口無需接口,繼承目標類
代理類型接口代理(實現接口)類代理(生成子類)
final 方法接口無 final 方法無法代理 final 方法
性能(歷史)早期較慢,JDK 8+ 優化顯著早期較快,現代 JDK 差異微小
Spring 默認策略有接口優先 JDK無接口或 proxy-target-class="true"

Spring AOP 中的應用

Spring AOP 默認策略:

  1. 目標 Bean 實現接口 → 使用 JDK 動態代理
  2. 目標 Bean 未實現接口 → 使用 CGLIB
  3. 強制 CGLIB:<aop:config proxy-target-class="true"> 或 @EnableAspectJAutoProxy(proxyTargetClass = true)

最佳實踐:面向接口編程,優先使用 JDK 動態代理,獲得更鬆耦合的架構;僅在無法實現接口(第三方類、遺留代碼)時退而求其次使用 CGLIB。

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