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

Java 代理模式深度解析:靜態代理 vs 動態代理(JDK/CGLIB)原理與 Spring AOP 應用

     編輯:Java 綜合教學

Java 代理模式深度解析:靜態代理 vs 動態代理(JDK/CGLIB)原理與 Spring AOP 應用

代理模式是結構型設計模式中的核心,通過引入代理對象控制對目標對象的訪問。Java 中分為 靜態代理(編譯期生成)與 動態代理(運行時生成),後者是 Spring AOP、RPC 框架的基石。本文系統對比兩類代理原理、代碼實現、優缺點,並深入剖析 JDK 動態代理源碼與 CGLIB 機制。

一、核心概念對比

維度靜態代理動態代理
生成時機編譯期(程序員手寫/工具生成 .java → .class)運行時(反射/字節碼操作動態生成字節碼)
代理類形式實際 .class 文件內存中動態生成的 Class 對象
目標類要求無特殊要求JDK:必須實現接口;CGLIB:不能是 final 類/方法
靈活性低(一對一,固定邏輯)高(可攔截任意方法、統一橫切邏輯)
性能直接調用,無額外開銷反射/Method.invoke 開銷(JDK);FastClass 索引調用(CGLIB)
典型場景簡單包裝、適配器AOP、事務、緩存、RPC、日誌、權限

二、靜態代理代碼範例

// 1. 共同接口
public interface UserDao {
    void save();
    void update();
}

// 2. 目標對象
public class UserDaoImpl implements UserDao {
    @Override
    public void save() { System.out.println("保存數據..."); }
    @Override
    public void update() { System.out.println("更新數據..."); }
}

// 3. 代理對象(手寫固定邏輯)
public class UserDaoProxy implements UserDao {
    private UserDao target; // 關聯目標對象

    public UserDaoProxy(UserDao target) { this.target = target; }

    @Override
    public void save() {
        System.out.println("開啟事務");      // 前置增強
        target.save();                      // 委托調用
        System.out.println("提交事務");      // 後置增強
    }

    @Override
    public void update() {
        System.out.println("開啟事務");
        target.update();
        System.out.println("提交事務");
    }
}

// 4. 客戶端使用
UserDao dao = new UserDaoProxy(new UserDaoImpl());
dao.save(); // 輸出:開啟事務 → 保存數據 → 提交事務

痛點:每個目標方法都需在代理中重寫,代碼冗餘、維護困難。

三、JDK 動態代理——接口代理標準實現

核心兩大組件

  1. InvocationHandler:攔截器接口,定義 invoke(proxy, method, args) 統一處理邏輯
  2. Proxy:工廠類,newProxyInstance(loader, interfaces, handler) 生成代理實例

代碼實現

// 1. 實現 InvocationHandler
public class LogHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置:記錄日誌 " + method.getName());
        Object result = method.invoke(target, args); // 反射調用目標
        System.out.println("後置:記錄日誌 " + method.getName());
        return result;
    }
}

// 2. 生成代理並使用
UserDao target = new UserDaoImpl();
UserDao proxy = (UserDao) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        new LogHandler(target));

proxy.save(); // 同樣輸出前置/後置日誌

Proxy.newProxyInstance 源碼核心流程(簡化)

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) {
    // 1. 獲取/生成代理 Class(緩存機制)
    Class cl = getProxyClass(loader, interfaces);
    // 2. 獲取構造器(唯一構造器:Proxy(InvocationHandler))
    Constructor cons = cl.getConstructor(constructorParams);
    // 3. 實例化代理對象,注入 handler
    return cons.newInstance(new Object[]{h});
}

生成的代理類繼承 Proxy、實現目標接口,重寫所有方法調用 handler.invoke(this, method, args)。

四、CGLIB 動態代理——類代理補完方案

適用場景

  • 目標類未實現接口
  • 需要代理非 public 方法
  • 性能敏感場景(FastClass 機制優於反射)

核心原理

  1. 繼承目標類,生成子類字節碼(ASM 库操作字節碼)
  2. 重寫父類非 final 方法,攔截調用
  3. FastClass:為代理類與目標類各生成一個索引類,方法調用轉為 index = fastClass.getIndex(method) → fastClass.invoke(index, obj, args),避免反射開銷

代碼範例

// 目標類無接口
public class UserService {
    public void add() { System.out.println("添加用戶"); }
}

// CGLIB 代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB 前置");
        Object result = proxy.invokeSuper(obj, args); // 調用父類方法
        System.out.println("CGLIB 後置");
        return result;
    }
});
UserService proxy = (UserService) enhancer.create();
proxy.add();

限制

  • 無法代理 final 類/方法(無法繼承/重寫)
  • 構造器不會被攔截
  • 預設情況下,Object 方法(equals/hashCode/toString)不攔截

五、Spring AOP 代理選擇策略

條件代理方式配置控制
目標實現接口JDK 動態代理(默認)proxy-target-class="false" (默認)
目標未實現接口 / 強制類代理CGLIBproxy-target-class="true" / @EnableAspectJAutoProxy(proxyTargetClass=true)

最佳實踐:面向接口編程,優先 JDK 代理;避免對 final 方法/類使用 AOP;性能極致場景可評估 CGLIB。

六、性能對比與調優建議

  • JDK 代理:Java 8+ 引入 MethodHandle/LambdaMetafactory 優化,調用開銷大幅降低
  • CGLIB:FastClass 機制使方法調用接近直接調用,但類加載、字節碼生成開銷較大
  • 啟動階段:Spring 緩存代理類,避免重複生成
  • 調優:減少切面數量、縮小切點範圍、避免在熱點循環中獲取代理

七、總結

代理模式本質是 「關注點分離」:目標對象專注業務邏輯,代理對象承載橫切邏輯。

  • 靜態代理:理解基礎,適用簡單場景
  • JDK 動態代理:接口代理標準,反射機制,Spring AOP 默認
  • CGLIB:類代理補完,字節碼繼承+FastClass,性能優於反射
深入掌握動態代理原理,能讓你在排查 Spring AOP 不生效、事務失效、循環依賴、序列化問題時具備穿透本質的能力。

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