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

Java 動態代理:從原理到實戰的完整指南

     編輯:Java 程式設計入門

代理模式核心思想

代理 為委託類(被代理類)提供替身,兩大價值:

  1. 隱藏實現:客戶端只見代理,不見真實對象
  2. 解耦增強:不修改委託類碼,在調用前後織入邏輯(日誌、交易、權限、快取)

靜態 vs 動態代理

維度靜態代理動態代理
代理類來源編譯期手寫 .java執行期 JVM 生成 $Proxy0.class
方法增強每個方法手工加邏輯統一 InvocationHandler 攔截
維護成本介面方法多時極高極低,只需改處理器邏輯
適用場景固定少量增強點AOP、框架級通用增強

JDK 動態代理三大核心角色

┌─────────────┐     ┌──────────────────┐     ┌─────────────┐
│  Client     │────▶│  $Proxy0         │────▶│  InvocationHandler │
│  (呼叫端)   │     │  (動態生成代理)  │     │  (攔截器)      │
└─────────────┘     └──────────────────┘     └──────┬──────┘
                                                     │
                                                     ▼
                                            ┌─────────────────┐
                                            │  Target Object  │
                                            │  (真實委託類)    │
                                            └─────────────────┘
  1. 介面:委託類必須實作介面(JDK 代理限制)
  2. InvocationHandler:實作 invoke(proxy, method, args),統一攔截
  3. Proxy.newProxyInstance():動態生成實作目標介面的代理類

完整實戰範例

1. 定義業務介面與實作

public interface UserService {
    void addUser(String name);
    User getUser(Long id);
}

public class UserServiceImpl implements UserService {
    public void addUser(String name) { System.out.println("新增用戶: " + name); }
    public User getUser(Long id) { return new User(id, "User" + id); }
}

2. 通用攔截器(處理器)

public class LoggingHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[前置] " + method.getName() + " 參數: " + Arrays.toString(args));
        Object result = method.invoke(target, args);  // 呼叫真實對象
        System.out.println("[後置] " + method.getName() + " 回傳: " + result);
        return result;
    }
}

3. 建立代理並測試

public class ProxyDemo {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new LoggingHandler(target)
        );

        proxy.addUser("Alice");
        System.out.println("---");
        System.out.println(proxy.getUser(1L));
    }
}

生成代理類原始碼解析(反編譯 $Proxy0)

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m_addUser, m_getUser, m_equals, m_toString, m_hashCode;

    public $Proxy0(InvocationHandler h) { super(h); }

    public void addUser(String name) {
        super.h.invoke(this, m_addUser, new Object[]{name});
    }
    public User getUser(Long id) {
        return (User) super.h.invoke(this, m_getUser, new Object[]{id});
    }
    // equals/toString/hashCode 同理轉發給 handler
    static {
        m_addUser = UserService.class.getMethod("addUser", String.class);
        m_getUser = UserService.class.getMethod("getUser", Long.class);
        // ...
    }
}

關鍵點:

  • 代理類繼承 java.lang.reflect.Proxy,持有 InvocationHandler h
  • 每個介面方法轉發 h.invoke(this, method, args)
  • 靜態區塊用反射快取 Method 物件,避免重複查找

JDK vs CGLIB 動態代理對比

特性JDK ProxyCGLIB
前提條件目標類實作介面無需介面,繼承目標類
生成方式實作介面繼承子類(FastClass 機制)
final 方法正常代理無法代理(無法覆寫)
效能(JDK 8+)極佳(invoke 擊穿優化)略快但差距微小
Spring 預設有介面用 JDK無介面/強制 proxy-target-class=true

最佳實踐

  • 業務介面設計優先,天然支援 JDK 代理
  • 避免在 invoke 做耗時操作,影響所有被代理方法
  • 區分 @Transactional 等註解驅動的代理 vs 手工 Proxy.newProxyInstance
  • 除錯時可設 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 輸出 $Proxy*.class
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved