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

Java 代理模式深度解析:靜態代理 vs 動態代理實戰

     編輯:Java 相關知識

代理模式核心思想

代理模式為目標對象提供一個替身,控制對其訪問,可在不修改目標代碼前提下增強功能(日誌、事務、權限、緩存等)。分為靜態代理(編譯期確定)與動態代理(運行時生成)。

一、靜態代理實現

1. 共同接口

package com.zj.proxy;

public interface Subject {
    void operation1();
    void operation2(String arg);
}

2. 真實主題

package com.zj.proxy;

public class RealSubject implements Subject {
    @Override
    public void operation1() {
        System.out.println("RealSubject: 執行 operation1");
    }

    @Override
    public void operation2(String arg) {
        System.out.println("RealSubject: 執行 operation2 參數=" + arg);
    }
}

3. 代理類

package com.zj.proxy;

public class ProxySubject implements Subject {
    private final Subject target; // 被代理對象

    public ProxySubject(Subject target) {
        this.target = target;
    }

    @Override
    public void operation1() {
        System.out.println("Proxy: 前置增強 - operation1");
        target.operation1();
        System.out.println("Proxy: 後置增強 - operation1");
    }

    @Override
    public void operation2(String arg) {
        System.out.println("Proxy: 前置增強 - operation2, arg=" + arg);
        target.operation2(arg);
        System.out.println("Proxy: 後置增強 - operation2");
    }
}

4. 客戶端測試

package com.zj.proxy.client;

import com.zj.proxy.*;

public class StaticProxyDemo {
    public static void main(String[] args) {
        Subject real = new RealSubject();
        
        System.out.println("=== 直接調用 ===");
        real.operation1();
        real.operation2("ZJ");

        System.out.println("\n=== 代理調用 ===");
        Subject proxy = new ProxySubject(real);
        proxy.operation1();
        proxy.operation2("ZJ");
    }
}

輸出結果

=== 直接調用 ===
RealSubject: 執行 operation1
RealSubject: 執行 operation2 參數=ZJ

=== 代理調用 ===
Proxy: 前置增強 - operation1
RealSubject: 執行 operation1
Proxy: 後置增強 - operation1
Proxy: 前置增強 - operation2, arg=ZJ
RealSubject: 執行 operation2 參數=ZJ
Proxy: 後置增強 - operation2

二、JDK 動態代理實現

核心類:java.lang.reflect.Proxy、InvocationHandler

1. 通用調用處理器

package com.zj.proxy.dynamic;

import java.lang.reflect.*;

public class DynamicProxyHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("=== 動態代理攔截 ===");
        System.out.println("代理類: " + proxy.getClass().getName());
        System.out.println("方法: " + method.getName());
        System.out.println("參數: " + Arrays.toString(args));

        // 前置增強
        long start = System.nanoTime();
        
        Object result = method.invoke(target, args); // 調用目標方法
        
        // 後置增強
        System.out.println("耗時: " + (System.nanoTime() - start) + " ns");
        return result;
    }
}

2. 客戶端使用

package com.zj.proxy.client;

import java.lang.reflect.Proxy;
import com.zj.proxy.*;
import com.zj.proxy.dynamic.DynamicProxyHandler;

public class DynamicProxyDemo {
    public static void main(String[] args) {
        Subject real = new RealSubject();
        
        // 生成代理實例
        Subject proxy = (Subject) Proxy.newProxyInstance(
            Subject.class.getClassLoader(),           // 類加載器
            new Class[] { Subject.class },            // 實現的接口列表
            new DynamicProxyHandler(real)             // 處理器
        );

        System.out.println("代理類名稱: " + proxy.getClass().getName());
        // 輸出: com.sun.proxy.$Proxy0
        
        proxy.operation1();
        proxy.operation2("Dynamic");
    }
}

輸出結果

代理類名稱: com.sun.proxy.$Proxy0
=== 動態代理攔截 ===
代理類: com.sun.proxy.$Proxy0
方法: operation1
參數: []
RealSubject: 執行 operation1
耗時: 12345 ns
=== 動態代理攔截 ===
代理類: com.sun.proxy.$Proxy0
方法: operation2
參數: [Dynamic]
RealSubject: 執行 operation2 參數=Dynamic
耗時: 6789 ns

三、關鍵差異對比

維度靜態代理JDK 動態代理
代理類生成時間編譯期運行時 (字節碼生成)
目標類要求實現接口必須實現一個或多個接口
代碼復用性低 (每個目標類需寫代理類)高 (一個 Handler 處理所有接口)
方法攔截粒度手動控制每個方法統一在 invoke() 中處理
性能開銷極低 (直接調用)較高 (反射調用、方法查找)
調試難度易 (代碼可見)難 (動態生成類不可見)

四、進階:CGLIB 代理(補充)

JDK 動態代理要求目標實現接口。若目標是普通類,需用 CGLIB(繼承子類重寫方法):

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

// 使用示例
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class); // 針對類而非接口
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    System.out.println("CGLIB 前置增強");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("CGLIB 後置增強");
    return result;
});
RealSubject proxy = (RealSubject) enhancer.create();

五、Spring AOP 中的應用

Spring 根據目標類特性自動選擇:

  • 實現接口 → JDK 動態代理 (默認)
  • 無接口 / proxy-target-class=true → CGLIB
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 強制 CGLIB
public class AopConfig {}

六、最佳實踐建議

  1. 面向接口編程:天然支持 JDK 動態代理,解耦合
  2. 代理邏輯集中化:將通用橫切關注點(事務、日誌、緩存)封裝在 Handler/Interceptor 中
  3. 避免過度代理:簡單業務無需代理,直接調用
  4. 性能敏感場景:考慮靜態代理或編譯期織入
  5. 保持代理透明:代理不應改變目標方法語義,僅增強行為
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved