Spring開發:動態代理的藝術與實踐

华为云开发者联盟發表於2024-04-16

本文分享自華為雲社群《Spring高手之路17——動態代理的藝術與實踐》,作者: 磚業洋__。

1. 背景

動態代理是一種強大的設計模式,它允許開發者在執行時建立代理物件,用於攔截對真實物件的方法呼叫。這種技術在實現面向切面程式設計(AOP)、事務管理、許可權控制等功能時特別有用,因為它可以在不修改原有程式碼結構的前提下,為程式動態地注入額外的邏輯。

2. JDK動態代理

2.1 定義和演示

JDK動態代理是Java語言提供的一種基於介面的代理機制,允許開發者在執行時動態地建立代理物件,而無需為每個類編寫具體的代理實現。

這種機制主要透過 java.lang.reflect.Proxy 類和 java.lang.reflect.InvocationHandler 介面實現。下面是JDK動態代理的核心要點和如何使用它們的概述。

使用步驟

  1. 定義介面:首先定義一個或多個介面,代理物件將實現這些介面。

  2. 實現介面:建立一個類,它實現上述介面,提供具體的實現邏輯。

  3. 建立 InvocationHandler 實現:定義一個 InvocationHandler 的實現,這個實現中的 invoke 方法可以包含自定義邏輯。

  4. 建立代理物件:使用 Proxy.newProxyInstance 方法,傳入目標物件的類載入器、需要代理的介面陣列以及 InvocationHandler 的實現,來建立一個實現了指定介面的代理物件。

用簡單的例子來說明這個過程,全部程式碼如下:

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

interface HelloWorld {
    void sayHello();
}

class HelloWorldImpl implements HelloWorld {
    public void sayHello() {
        System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
        HelloWorldImpl realObject = new HelloWorldImpl();
        HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
                HelloWorldImpl.class.getClassLoader(), // 使用目標類的類載入器
                new Class[]{HelloWorld.class}, // 代理類需要實現的介面列表
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 在呼叫目標方法前可以插入自定義邏輯
                        System.out.println("Before method call");
                        // 呼叫目標物件的方法
                        Object result = method.invoke(realObject, args);
                        // 在呼叫目標方法後可以插入自定義邏輯
                        System.out.println("After method call");
                        return result;
                    }
                });

        proxyInstance.sayHello();
    }
}

執行結果如下:

Spring開發:動態代理的藝術與實踐

InvocationHandler 是動態代理的核心介面之一,當我們使用動態代理模式建立代理物件時,任何對代理物件的方法呼叫都會被轉發到一個實現了 InvocationHandler 介面的例項的 invoke 方法上。

我們經常看到InvocationHandler 動態代理的匿名內部類,這會在代理物件的相應方法被呼叫時執行。具體地說,每當對代理物件執行方法呼叫時,呼叫的方法不會直接執行,而是轉發到實現了InvocationHandlerinvoke 方法上。在這個 invoke 方法內部,我們可以定義攔截邏輯、呼叫原始物件的方法、修改返回值等操作。

在這個例子中,當呼叫 proxyInstance.sayHello() 方法時,實際上執行的是 InvocationHandler 的匿名內部類中的 invoke 方法。這個方法中,我們可以在呼叫實際物件的 sayHello 方法前後新增自定義邏輯(比如這裡的列印訊息)。這就是動態代理和 InvocationHandler 的工作原理。

我們來看關鍵的一句程式碼

Object result = method.invoke(realObject, args);

Java的動態代理中,method.invoke(realObject, args) 這句程式碼扮演著核心的角色,因為它實現了代理物件方法呼叫的轉發機制。下面分別解釋一下這行程式碼的兩個主要部分:method.invoke() 方法和 args 引數。

method.invoke(realObject, args)

  • 作用:這行程式碼的作用是呼叫目標物件(realObject)的具體方法。在動態代理的上下文中,invoke 方法是在代理例項上呼叫方法時被自動呼叫的。透過這種方式可以在實際的方法執行前後加入自定義的邏輯,比如日誌記錄、許可權檢查等。

  • method:method 是一個 java.lang.reflect.Method 類的例項,代表了正在被呼叫的方法。在 invoke 方法中,這個物件用來標識代理物件上被呼叫的具體方法。

注意:如果嘗試直接在invoke方法內部使用method.invoke(proxy, args)呼叫代理物件的方法,而不是呼叫原始目標物件的方法,則會導致無限迴圈。這是因為呼叫proxy例項上的方法會再次被代理攔截,從而無限呼叫invoke方法,傳參可別傳錯了。

  • invoke:Method 類的 invoke 方法用於執行指定方法。第一個引數是指明方法應該在哪個物件上呼叫(在這個例子中是 realObject),後續的引數 args 是呼叫方法時傳遞的引數。

args

  • 定義:args 是一個物件陣列,包含了呼叫代理方法時傳遞給方法的引數值。如果被呼叫的方法沒有引數,args 將會是 null 或者空陣列。

  • 作用:args 允許在 invoke 方法內部傳遞引數給實際要執行的方法。這意味著可以在動態代理中不僅控制是否呼叫某個方法,還可以修改呼叫該方法時使用的引數。

2.2 不同方法分別代理

我們繼續透過擴充套件 HelloWorld 介面來包含多個方法,並透過JDK動態代理演示許可權控制和功能開關操作的一種實現方式

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

interface HelloWorld {
    void sayHello();
    void sayGoodbye();
}

class HelloWorldImpl implements HelloWorld {
    public void sayHello() {
        System.out.println("Hello world!");
    }

    public void sayGoodbye() {
        System.out.println("Goodbye world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
        HelloWorld realObject = new HelloWorldImpl();
        HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
                HelloWorldImpl.class.getClassLoader(),
                new Class[]{HelloWorld.class},
                new InvocationHandler() {
                    // 新增一個簡單的許可權控制演示
                    private boolean accessAllowed = true;

                    // 簡單的功能開關
                    private boolean goodbyeFunctionEnabled = true;

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 許可權控制
                        if (!accessAllowed) {
                            System.out.println("Access denied");
                            return null; // 在實際場景中,可以丟擲一個異常
                        }

                        // 功能開關
                        if (method.getName().equals("sayGoodbye") && !goodbyeFunctionEnabled) {
                            System.out.println("Goodbye function is disabled");
                            return null;
                        }

                        // 方法執行前的通用邏輯
                        System.out.println("Before method: " + method.getName());

                        // 執行方法
                        Object result = method.invoke(realObject, args);

                        // 方法執行後的通用邏輯
                        System.out.println("After method: " + method.getName());

                        return result;
                    }
                });
        // 正常執行
        proxyInstance.sayHello();

        // 可以根據goodbyeFunctionEnabled變數決定是否執行
        proxyInstance.sayGoodbye();
    }
}

執行如下:

Spring開發:動態代理的藝術與實踐

如果accessAllowed 變數為false

Spring開發:動態代理的藝術與實踐

如果goodbyeFunctionEnabled 變數為false

Spring開發:動態代理的藝術與實踐

在這個例子中:

  • 許可權控制:透過檢查 accessAllowed 變數,我們可以模擬簡單的許可權控制。如果沒有許可權,可以直接返回或丟擲異常,避免執行方法。

  • 功能開關:透過檢查方法名稱和 goodbyeFunctionEnabled 變數,我們可以控制 sayGoodbye 方法是否被執行。這可以用來根據配置啟用或禁用特定功能。

這個例子展示了JDK動態代理在實際應用中如何進行方法級別的細粒度控制,同時保持程式碼的靈活性和可維護性。透過動態代理,我們可以在不修改原始類程式碼的情況下,為物件動態地新增額外的行為。

2.3 熔斷限流和日誌監控

為了更全面地展示JDK動態代理的能力,我們在先前的示例中新增熔斷限流和日誌監控的邏輯。這些是在高併發和分散式系統中常見的需求,可以透過動態代理以非侵入式的方式實現。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

interface HelloWorld {
    void sayHello();
}

class HelloWorldImpl implements HelloWorld {
    public void sayHello() {
        System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
        HelloWorld realObject = new HelloWorldImpl();
        HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
                HelloWorldImpl.class.getClassLoader(),
                new Class[]{HelloWorld.class},
                new AdvancedInvocationHandler(realObject));

        // 模擬多次呼叫以觀察限流和熔斷效果
        for (int i = 0; i < 10; i++) {
            proxyInstance.sayHello();
        }
    }
    static class AdvancedInvocationHandler implements InvocationHandler {
        private final Object target;
        private AtomicInteger requestCount = new AtomicInteger(0);
        private AtomicLong lastTimestamp = new AtomicLong(System.currentTimeMillis());
        private volatile boolean circuitBreakerOpen = false;
        private final long cooldownPeriod = 10000; // 冷卻時間10秒

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

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long now = System.currentTimeMillis();

            // 檢查熔斷器是否應該被重置
            if (circuitBreakerOpen && (now - lastTimestamp.get() > cooldownPeriod)) {
                circuitBreakerOpen = false; // 重置熔斷器
                requestCount.set(0); // 重置請求計數
                System.out.println("Circuit breaker has been reset.");
            }

            // 熔斷檢查
            if (circuitBreakerOpen) {
                System.out.println("Circuit breaker is open. Blocking method execution for: " + method.getName());
                return null; // 在實際場景中,可以返回一個兜底的響應或丟擲異常
            }

            // 限流檢查
            if (requestCount.incrementAndGet() > 5) {
                if (now - lastTimestamp.get() < cooldownPeriod) { // 10秒內超過5次請求,觸發熔斷
                    circuitBreakerOpen = true;
                    lastTimestamp.set(now); // 更新時間戳
                    System.out.println("Too many requests. Opening circuit breaker.");
                    return null; // 觸發熔斷時的處理
                } else {
                    // 重置計數器和時間戳
                    requestCount.set(0);
                    lastTimestamp.set(now);
                }
            }

            // 執行實際方法
            Object result = method.invoke(target, args);

            // 方法執行後的邏輯
            System.out.println("Executed method: " + method.getName());

            return result;
        }
    }
}

Spring開發:動態代理的藝術與實踐

在這個擴充套件示例中,我們實現了:

  • 熔斷機制:透過一個簡單的計數器和時間戳來模擬。如果在10秒內對任一方法的呼叫次數超過5次,我們就"開啟"熔斷器,阻止進一步的方法呼叫。在實際應用中,熔斷邏輯可能更加複雜,可能包括錯誤率的檢查、呼叫延遲的監控等。

  • 限流:這裡使用的限流策略很簡單,透過計數和時間戳來判斷是否在短時間內請求過多。在更復雜的場景中,可以使用令牌桶或漏桶演算法等更高階的限流策略。

  • 日誌監控:在方法呼叫前後列印日誌,這對於監控系統的行為和效能是非常有用的。在實際專案中,這些日誌可以整合到日誌管理系統中,用於問題診斷和效能分析。

  透過在 invoke 方法中加入這些邏輯,我們能夠在不修改原有業務程式碼的情況下,為系統新增複雜的控制和監控功能。如果到達流量閾值或系統處於熔斷狀態,可以阻止對後端服務的進一步呼叫,直接返回一個預設值或錯誤響應,避免系統過載。

3. CGLIB動態代理

CGLIBCode Generation Library)是一個強大的高效能程式碼生成庫,它在執行時動態生成新的類。與JDK動態代理不同,CGLIB能夠代理那些沒有實現介面的類。這使得CGLIB成為那些因為設計限制或其他原因不能使用介面的場景的理想選擇。

3.1 定義和演示

工作原理

CGLIB透過繼承目標類並在執行時生成子類來實現動態代理。代理類覆蓋了目標類的非final方法,並在呼叫方法前後提供了注入自定義邏輯的能力。這種方法的一個關鍵優勢是它不需要目標物件實現任何介面。

使用CGLIB的步驟

新增CGLIB依賴:首先,需要在專案中新增CGLIB庫的依賴。

如果使用Maven,可以新增如下依賴到pom.xml中:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version> <!-- 目前最新的版本 -->
</dependency>

建立MethodInterceptor:實現MethodInterceptor介面,這是CGLIB提供的回撥型別,用於定義方法呼叫的攔截邏輯。

生成代理物件:使用Enhancer類來建立代理物件。EnhancerCGLIB中用於生成新類的類。

改造一下1.1節的例子,可以對比看看,全部示例程式碼如下:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class HelloWorld {
    public void sayHello() {
        System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        // 設定需要代理的類
        enhancer.setSuperclass(HelloWorld.class);

        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Before method call");
                Object result = proxy.invokeSuper(obj, args); // 呼叫父類的方法
                System.out.println("After method call");
                return result;
            }
        });

        HelloWorld proxy = (HelloWorld) enhancer.create(); // 建立代理物件
        proxy.sayHello(); // 透過代理物件呼叫方法
    }
}

執行結果如下:

CGLIB vs JDK動態代理

  • 介面要求:JDK動態代理只能代理實現了介面的物件,而CGLIB能夠直接代理類。
  • 效能:CGLIB在生成代理物件時通常比JDK動態代理要慢,因為它需要動態生成新的類。但在呼叫代理方法時,CGLIB通常會提供更好的效能。
  • 方法限制:CGLIB不能代理final方法,因為它們不能被子類覆蓋。

CGLIB是一個強大的工具,特別適用於需要代理沒有實現介面的類的場景。然而,選擇JDK動態代理還是CGLIB主要取決於具體的應用場景和效能要求。

注意:在CGLIB中,如果使用MethodProxy.invoke(obj, args) ,而不是MethodProxy.invokeSuper(obj, args),並且obj是代理例項本身(CGLIB透過Enhancer建立的代理物件,而不是原始的被代理的目標物件),就會導致無限迴圈。invoke方法實際上是嘗試在傳遞的物件上呼叫方法,如果該物件是代理物件,則呼叫會再次被攔截,造成無限迴圈。

  • JDK動態代理中,確保呼叫method.invoke時使用的是目標物件,而不是代理物件。

  • CGLIB代理中,使用MethodProxy.invokeSuper而不是MethodProxy.invoke來呼叫被代理的方法,以避免無限迴圈。

3.2 不同方法分別代理(對比JDK動態代理寫法)

我們改寫1.2節的例子

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
class HelloWorldImpl {
    public void sayHello() {
        System.out.println("Hello world!");
    }

    public void sayGoodbye() {
        System.out.println("Goodbye world!");
    }
}
public class DemoApplication {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloWorldImpl.class); // 設定被代理的類
        enhancer.setCallback(new MethodInterceptor() {
            // 新增一個簡單的許可權控制演示
            private boolean accessAllowed = true;

            // 簡單的功能開關
            private boolean goodbyeFunctionEnabled = true;

            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                // 許可權控制
                if (!accessAllowed) {
                    System.out.println("Access denied");
                    return null; // 在實際場景中,可以丟擲一個異常
                }

                // 功能開關
                if (method.getName().equals("sayGoodbye") && !goodbyeFunctionEnabled) {
                    System.out.println("Goodbye function is disabled");
                    return null;
                }

                // 方法執行前的通用邏輯
                System.out.println("Before method: " + method.getName());

                // 執行方法
                Object result = proxy.invokeSuper(obj, args);

                // 方法執行後的通用邏輯
                System.out.println("After method: " + method.getName());

                return result;
            }
        });

        HelloWorldImpl proxyInstance = (HelloWorldImpl) enhancer.create(); // 建立代理物件
        proxyInstance.sayHello(); // 正常執行
        proxyInstance.sayGoodbye(); // 可以根據goodbyeFunctionEnabled變數決定是否執行
    }
}

執行結果如下:

Spring開發:動態代理的藝術與實踐

我們需要注意幾點更改:

  1. 因為CGLIB不是基於介面的代理,而是透過生成目標類的子類來實現代理,所以我們不再需要介面HelloWorld

  2. 我們將使用Enhancer類來建立代理例項,並提供一個MethodInterceptor來處理方法呼叫。

3.3 熔斷限流和日誌監控(對比JDK動態代理寫法)

我們改寫1.3節的例子

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

class HelloWorld {
    void sayHello() {
        System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
        HelloWorld realObject = new HelloWorld();
        HelloWorld proxyInstance = (HelloWorld) createProxy(realObject);

        // 模擬多次呼叫以觀察限流和熔斷效果
        for (int i = 0; i < 10; i++) {
            proxyInstance.sayHello();
        }
    }

    public static Object createProxy(final Object realObject) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloWorld.class);
        enhancer.setCallback(new AdvancedMethodInterceptor(realObject));
        return enhancer.create();
    }

    static class AdvancedMethodInterceptor implements MethodInterceptor {
        private final Object target;
        private final AtomicInteger requestCount = new AtomicInteger(0);
        private final AtomicLong lastTimestamp = new AtomicLong(System.currentTimeMillis());
        private volatile boolean circuitBreakerOpen = false;
        private final long cooldownPeriod = 10000; // 冷卻時間10秒

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

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            long now = System.currentTimeMillis();

            // 檢查熔斷器是否應該被重置
            if (circuitBreakerOpen && (now - lastTimestamp.get() > cooldownPeriod)) {
                circuitBreakerOpen = false; // 重置熔斷器
                requestCount.set(0); // 重置請求計數
                System.out.println("Circuit breaker has been reset.");
            }

            // 熔斷檢查
            if (circuitBreakerOpen) {
                System.out.println("Circuit breaker is open. Blocking method execution for: " + method.getName());
                return null; // 在實際場景中,可以返回一個兜底的響應或丟擲異常
            }

            // 限流檢查
            if (requestCount.incrementAndGet() > 5) {
                if (now - lastTimestamp.get() < cooldownPeriod) { // 10秒內超過5次請求,觸發熔斷
                    circuitBreakerOpen = true;
                    lastTimestamp.set(now); // 更新時間戳
                    System.out.println("Too many requests. Opening circuit breaker.");
                    return null; // 觸發熔斷時的處理
                } else {
                    // 重置計數器和時間戳
                    requestCount.set(0);
                    lastTimestamp.set(now);
                }
            }

            // 執行實際方法
            Object result = proxy.invokeSuper(obj, args); // 注意這裡呼叫的是invokeSuper

            // 方法執行後的邏輯
            System.out.println("Executed method: " + method.getName());

            return result;
        }
    }
}

執行結果

Spring開發:動態代理的藝術與實踐

在這個改寫中,我們使用CGLIBEnhancerMethodInterceptor來代替了JDKProxyInvocationHandlerMethodInterceptorintercept方法與InvocationHandlerinvoke方法在概念上是相似的,但它使用MethodProxyinvokeSuper方法來呼叫原始類的方法,而不是使用反射。這允許CGLIB在執行時生成代理類的位元組碼,而不是依賴於反射,從而提高了效能。此外,circuitBreakerOpen被宣告為volatile,是確保其在多執行緒環境中的可見性。

4. 動態代理圖示

Spring開發:動態代理的藝術與實踐
方法呼叫攔截:

客戶端透過代理物件呼叫方法,此時方法呼叫被代理物件攔截。

轉發給處理器或方法攔截器:

代理物件將方法呼叫轉發給一個特定的處理器,這取決於所使用的代理型別。對於JDK動態代理,這個處理器是InvocationHandler;對於CGLIB代理,是MethodInterceptor

執行額外操作(呼叫前):

在實際執行目標物件的方法之前,處理器有機會執行一些額外的操作,例如日誌記錄、安全檢查或事務管理等。

呼叫目標物件的方法:

處理器在必要時直接呼叫目標物件的方法。在JDK動態代理中,這通常透過反射實現;而在CGLIB中,可以透過MethodProxy.invokeSuper方法呼叫。

執行額外操作(呼叫後):

方法呼叫完成後,處理器再次有機會執行額外操作,比如修改返回值、記錄執行時間或進行事務的提交或回滾。

返回給客戶端:

最終,方法的返回值被透過代理物件返回給客戶端。

5. JDK動態代理 VS CGLIB動態代理對比

JDK動態代理

JDK動態代理是Java自帶的代理機制,它直接使用反射API來呼叫方法。

優點:

  • 無需第三方依賴:作為Java標準API的一部分,使用JDK動態代理不需要新增額外的庫或依賴。

  • 介面導向:強制使用介面進行代理,這符合面向介面程式設計的原則,有助於保持程式碼的清晰和靈活。

缺點:

  • 僅限介面:只能代理實現了介面的類,這在某些情況下限制了它的使用。

  • 效能開銷:由於使用反射API進行方法呼叫,可能會有一定的效能開銷,尤其是在大量呼叫時。

CGLIB動態代理

CGLIBCode Generation Library)透過在執行時生成被代理物件的子類來實現代理。

優點:

  • 不需要介面:可以代理沒有實現任何介面的類,這提供了更大的靈活性。

  • 效能較好:通常認為CGLIB的效能比JDK動態代理要好,特別是在代理方法的呼叫上,因為CGLIB使用了位元組碼生成技術,減少了使用反射的需要。

缺點:

  • 第三方庫:需要新增CGLIB庫作為專案依賴。

  • 無法代理final方法:由於CGLIB是透過生成子類的方式來代理的,所以無法代理那些被宣告為final的方法。

效能比較

  • 呼叫速度:CGLIB在代理方法呼叫方面通常比JDK動態代理更快。這是因為CGLIB透過直接操作位元組碼來生成新的類,避免了反射帶來的效能開銷。

  • 啟動效能:CGLIB在生成代理物件時可能會比JDK動態代理慢,因為它需要在執行時生成新的位元組碼。如果代理物件在應用啟動時就被建立,這可能會略微影響啟動時間。

選擇建議

  • 如果類已經實現了介面,或者希望強制使用介面程式設計,那麼JDK動態代理是一個好選擇。

  • 如果需要代理沒有實現介面的類,或者對效能有較高的要求,特別是在代理方法的呼叫上,CGLIB可能是更好的選擇。

  • 在現代的Java應用中,很多框架(如Spring)都提供了對這兩種代理方式的透明支援,並且可以根據實際情況自動選擇使用哪一種。例如,Spring AOP預設會使用JDK動態代理,但如果遇到沒有實現介面的類,它會退回到CGLIB

6. 動態代理的實際應用場景

面向切面程式設計(AOP):
  • 問題解決:在不改變原有業務邏輯程式碼的情況下,為程式動態地新增額外的行為(如日誌記錄、效能監測、事務管理等)。

  • 應用例項:Spring AOP 使用動態代理為方法呼叫提供了宣告式事務管理、安全性檢查和日誌記錄等服務。根據目標物件是否實現介面,Spring AOP可以選擇使用JDK動態代理或CGLIB代理。

事務管理:
  • 問題解決:自動化處理資料庫事務的邊界,如開始、提交或回滾事務。

  • 應用例項:Spring框架中的宣告式事務管理使用代理技術攔截那些被@Transactional註解標記的類或方法,確保方法執行在正確的事務管理下進行。

許可權控制和安全性:
  • 問題解決:在執行敏感操作之前自動檢查使用者許可權,確保只有擁有足夠許可權的使用者才能執行某些操作。

  • 應用例項:企業應用中,使用代理技術攔截使用者的請求,進行許可權驗證後才允許訪問特定的服務或執行操作。

延遲載入:
  • 問題解決:物件的某些屬性可能載入成本較高,透過代理技術,可以在實際使用這些屬性時才進行載入。

  • 應用例項:Hibernate和其他ORM框架使用代理技術實現了延遲載入(懶載入),以提高應用程式的效能和資源利用率。

服務介面呼叫的攔截和增強:
  • 問題解決:對第三方庫或已有服務進行包裝,新增額外的邏輯,如快取結果、引數校驗等。

  • 應用例項:在微服務架構中,可以使用代理技術對服務客戶端進行增強,實現如重試、熔斷、限流等邏輯。

在現代框架中的應用

  • Spring框架:SpringAOP模組和事務管理廣泛使用了動態代理技術。根據目標物件的型別(是否實現介面),Spring可以自動選擇JDK動態代理或CGLIB代理。

  • Hibernate:Hibernate使用動態代理技術實現懶載入,代理實體類的關聯物件,在實際訪問這些物件時才從資料庫中載入它們的資料。

  • MyBatis:MyBatis框架使用動態代理技術對映介面和SQL語句,允許開發者透過介面直接與資料庫互動,而無需實現類。

歡迎一鍵三連~

有問題請留言,大家一起探討學習

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章