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

Java 動態代理與 CGLIB 完整指南:從原理到原始碼級解析

     編輯:Java 相關知識

前言

代理模式是 AOP、事務管理、遠端呼叫等框架的基石。Java 提供 JDK 動態代理與 CGLIB 兩大主流實現。本文從靜態代理痛點切入,深度解析動態代理生成原理、InvocationHandler 回調機制、CGLIB 繼承式代理差異,並附帶原始碼級反編譯分析。

一、靜態代理的痛點


// 目標介面
interface Hosee { String sayhi(); }

// 目標實現
class HoseeImpl implements Hosee {
    public String sayhi() { return "Welcome"; }
}

// 代理類(每個目標類需寫一個代理類)
class HoseeProxy implements Hosee {
    private Hosee target;
    public HoseeProxy(Hosee target) { this.target = target; }
    public String sayhi() {
        System.out.println("I'm proxy!");
        return target.sayhi();
    }
}

弊端:目標類增加 → 代理類成倍增加,維護成本極高。核心原因:代理關係在編譯期固定。

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

2.1 核心 API


class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before");
        Object result = method.invoke(target, args);
        System.out.println("After");
        return result;
    }
}

2.2 原始碼級揭秘:Proxy 生成的代理類長什麼樣?

設定 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") 後反編譯 $Proxy0.class:


public final class $Proxy0 extends Proxy implements HoseeDynamic {
    private static Method m1; // equals
    private static Method m3; // sayhi
    private static Method m0; // hashCode
    private static Method m2; // toString
    
    public $Proxy0(InvocationHandler h) { super(h); }
    
    public final String sayhi() {
        try {
            return (String) this.h.invoke(this, m3, null); // 全委派給 InvocationHandler
        } catch (Throwable t) { throw new UndeclaredThrowableException(t); }
    }
    // equals/hashCode/toString 同理委派
    static {
        // 靜態區塊快取 Method 物件
        m3 = Class.forName("HoseeDynamic").getMethod("sayhi", new Class[0]);
    }
}

關鍵發現:

  • 代理類繼承 Proxy、實現目標介面
  • 所有方法(含 Object 三大方法)均委派給 InvocationHandler.invoke()
  • Method 物件靜態快取,避免反射開銷

2.3 回調機制本質

Proxy.newProxyInstance() 內部流程:驗證 → 優化 → 快取 → 同步 → 生成位元碼(ProxyGenerator.generateProxyClass()) → 定義類。位元碼生成是核心,運行期才確定代理行為。

三、JDK 動態代理的局限

  • 目標類必須實現介面:無介面類無法代理
  • 僅能代理介面方法,無法代理類的具體方法

四、CGLIB:繼承式動態代理

4.1 核心原理

  • 針對類生成子類,覆寫非 final 方法
  • 底層使用 ASM 字節碼操作庫
  • 無法代理 final 類與 final 方法

4.2 使用範例


class CGlibHosee {
    public String sayhi() { return "Welcome"; }
}

class CGlibProxy implements MethodInterceptor {
    public Object bind(Object 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("Before");
        Object result = proxy.invokeSuper(obj, args); // 呼叫父類方法
        System.out.println("After");
        return result;
    }
}

4.3 MethodProxy.invokeSuper vs Method.invoke

方式效能說明
proxy.invokeSuper()高FastClass 機制,直接索引調用父類方法
method.invoke(target, args)低標準反射,需存取檢查、參數封裝

五、兩大代理對比總結

維度JDK 動態代理CGLIB
代理類型介面代理類繼承代理
目標類要求實現介面非 final 類
生成機制位元碼生成實現介面ASM 生成子類覆寫方法
效能調用較慢(反射)調用較快
Spring 默認策略有介面用 JDK,無介面用 CGLIB

六、最佳實踐建議

  1. 優先設計介面,享受 JDK 代理的標準化優勢
  2. 無介面第三方類、需代理具體方法時使用 CGLIB
  3. 避免代理 final 方法/類,或改用組合模式
  4. 高效能場景考慮 MethodProxy.invokeSuper 或字節碼框架

掌握動態代理原理,不僅能深度理解 Spring AOP、RPC 框架,更能在需要無侵入式擴展時靈活選型。

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