Java代理模式之靜態(tài)代理與動態(tài)代理的區(qū)別及優(yōu)缺點
1. 代理模式的定義
代理模式(Proxy Pattern)是指通過代理對象控制對目標對象的訪問,并在不改變目標對象的情況下添加額外的功能或控制訪問。代理對象和目標對象實現(xiàn)相同的接口,使得客戶端可以通過代理對象間接地訪問目標對象。
代理模式屬于結構型設計模式,它在系統(tǒng)中引入了一個代理對象,該對象代替了客戶端直接訪問目標對象,從而可以在目標對象的基礎上增加一些額外的功能或控制訪問。
2. 代理模式的原理
代理模式的核心思想是引入一個代理對象來控制對目標對象的訪問。代理對象和目標對象實現(xiàn)相同的接口,使得客戶端可以通過代理對象間接地訪問目標對象。代理對象負責處理客戶端的請求,并在必要時將請求轉發(fā)給目標對象。在這個過程中,代理對象可以添加額外的邏輯,如權限檢查、緩存、日志記錄等。
代理模式的主要角色有:
- 抽象主題(Subject):定義了代理對象和目標對象的共同接口,在Java中通常是一個接口或抽象類。
- 目標對象(RealSubject):定義了代理對象所代表的真實對象,是業(yè)務邏輯的具體執(zhí)行者。
- 代理對象(Proxy):持有對目標對象的引用,并實現(xiàn)了與目標對象相同的接口,在方法調(diào)用前后進行額外操作。
代理模式的工作流程如下:
- 客戶端向代理對象發(fā)送請求。
- 代理對象收到請求后,可以在請求被轉發(fā)給目標對象之前或之后執(zhí)行一些額外的邏輯。
- 代理對象將請求轉發(fā)給目標對象。
- 目標對象執(zhí)行具體的業(yè)務邏輯并返回結果。
- 代理對象可以在目標對象返回結果之前或之后執(zhí)行一些額外的操作。
- 代理對象將結果返回給客戶端。
通過引入代理對象,代理模式可以控制對目標對象的訪問,并在不改變目標對象的情況下添加額外的功能或控制訪問。
3. 代理模式的實現(xiàn)方式
在Java中,代理模式主要有兩種實現(xiàn)方式:靜態(tài)代理和動態(tài)代理。
3.1 靜態(tài)代理
靜態(tài)代理是指在編譯時就已經(jīng)確定了代理對象和目標對象的關系,代理類是通過手動編寫代碼來實現(xiàn)的。在靜態(tài)代理中,代理類和目標類都實現(xiàn)相同的接口,代理類持有目標對象,并在方法調(diào)用前后進行額外的操作。
靜態(tài)代理的工作原理如下:
- 定義一個接口(或抽象類)作為目標接口,目標對象實現(xiàn)這個接口。
- 創(chuàng)建一個代理類,實現(xiàn)目標接口,并持有目標對象的引用。
- 在代理類中重寫目標接口的方法,在方法調(diào)用前后執(zhí)行需要的額外操作。
- 客戶端使用代理對象來訪問目標對象。
靜態(tài)代理的特點:
- 需要手動編寫代理類,工作量較大。
- 目標對象必須實現(xiàn)接口。
- 代理類和目標類的關系在編譯時就確定了,無法動態(tài)改變。
靜態(tài)代理的應用場景:
- 安全控制:代理類可以在調(diào)用目標方法前進行權限檢查等安全控制。
- 遠程調(diào)用:代理類可以封裝網(wǎng)絡通信相關的細節(jié),使得調(diào)用遠程方法就像調(diào)用本地方法一樣簡單。
- 性能監(jiān)控:代理類可以收集方法的執(zhí)行時間、調(diào)用次數(shù)等信息,用于性能監(jiān)控和統(tǒng)計分析。
下面是一個簡單的靜態(tài)代理的示例代碼:
// 定義接口 public interface Image { void display(); } // 目標類 public class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename = filename; loadFromDisk(); } private void loadFromDisk() { System.out.println("Loading image: " + filename); } @Override public void display() { System.out.println("Displaying image: " + filename); } } // 代理類 public class ImageProxy implements Image { private String filename; private RealImage realImage; public ImageProxy(String filename) { this.filename = filename; } @Override public void display() { if (realImage == null) { realImage = new RealImage(filename); } realImage.display(); } } // 測試類 public class ProxyPatternDemo { public static void main(String[] args) { // 使用代理對象顯示圖片 Image image = new ImageProxy("test.jpg"); image.display(); // 圖片已加載,直接顯示,無需重新加載 image.display(); } }
在上面的示例中,Image
是一個接口,RealImage
是目標類,負責加載和顯示圖片。ImageProxy
是代理類,通過持有目標類的引用,在需要時創(chuàng)建并使用目標類。ProxyPatternDemo
是一個測試類,用于演示靜態(tài)代理的使用。
在測試類中,首先創(chuàng)建一個代理對象 ImageProxy
,并調(diào)用 display()
方法顯示圖片。代理對象會在第一次調(diào)用 display()
方法時,創(chuàng)建真實對象 RealImage
并調(diào)用其 display()
方法加載和顯示圖片。在后續(xù)調(diào)用 display()
方法時,代理對象直接調(diào)用真實對象的 display()
方法,無需重新加載圖片。
靜態(tài)代理的缺點是需要手動編寫代理類,工作量較大。如果接口中的方法較多或頻繁變動,就需要頻繁修改代理類的代碼,增加了維護的難度。此外,靜態(tài)代理的代理類和目標類之間存在緊耦合關系,一旦目標類發(fā)生變化,代理類也需要相應修改。
為了解決靜態(tài)代理的缺點,Java還提供了動態(tài)代理機制。
3.2 動態(tài)代理
動態(tài)代理是指在運行時生成代理對象,而無需手動編寫代理類。Java的動態(tài)代理機制是基于反射實現(xiàn)的,通過使用java.lang.reflect.Proxy
類和java.lang.reflect.InvocationHandler
接口來實現(xiàn)動態(tài)代理。
在動態(tài)代理中,代理類的創(chuàng)建和方法調(diào)用都是在運行時完成的。代理對象是在內(nèi)存中動態(tài)創(chuàng)建的,并實現(xiàn)了目標對象的接口,同時持有目標對象的引用。在方法調(diào)用時,代理對象通過調(diào)用InvocationHandler
接口中的方法來處理請求,可以在方法調(diào)用前后執(zhí)行額外的操作。
動態(tài)代理的工作原理如下:
- 定義一個接口,作為目標接口。
- 創(chuàng)建一個
InvocationHandler
接口的實現(xiàn)類,該類負責處理方法調(diào)用并執(zhí)行額外的操作。 - 使用
Proxy
類的靜態(tài)方法newProxyInstance()
生成代理對象,同時指定目標對象和InvocationHandler
。 - 客戶端使用代理對象來訪問目標對象的方法。
動態(tài)代理的特點:
- 不需要手動編寫代理類,代理對象在運行時動態(tài)生成。
- 目標對象可以不實現(xiàn)接口,只需定義目標對象的共同接口。
- 代理對象和目標對象的關系在運行時確定,可以動態(tài)改變。
動態(tài)代理的應用場景:
- AOP(面向切面編程):動態(tài)代理可以在目標方法執(zhí)行前后執(zhí)行額外的操作,如權限檢查、事務管理等。它是實現(xiàn)AOP的一種常見方式。
- 延遲加載:動態(tài)代理可以在目標方法被調(diào)用時才進行加載和初始化,實現(xiàn)延遲加載的效果。
- 日志記錄:動態(tài)代理可以在目標方法執(zhí)行前后記錄日志信息。
下面是一個簡單的動態(tài)代理的示例代碼:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 定義接口 interface Image { void display(); } // 目標類 class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename = filename; loadFromDisk(); } private void loadFromDisk() { System.out.println("Loading image: " + filename); } @Override public void display() { System.out.println("Displaying image: " + filename); } } // InvocationHandler 實現(xiàn)類 class ImageInvocationHandler implements InvocationHandler { private Object target; public ImageInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method: " + method.getName()); Object result = method.invoke(target, args); System.out.println("After method: " + method.getName()); return result; } } // 測試類 public class ProxyPatternDemo { public static void main(String[] args) { // 創(chuàng)建目標對象 Image realImage = new RealImage("test.jpg"); // 創(chuàng)建 InvocationHandler 實例 ImageInvocationHandler handler = new ImageInvocationHandler(realImage); // 創(chuàng)建代理對象 Image imageProxy = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(), new Class[]{Image.class}, handler); // 使用代理對象顯示圖片 imageProxy.display(); } }
在上面的示例中,Image
是一個接口,RealImage
是目標類,負責加載和顯示圖片。ImageInvocationHandler
是InvocationHandler
接口的實現(xiàn)類,用于處理方法調(diào)用并執(zhí)行額外的操作。ProxyPatternDemo
是一個測試類,用于演示動態(tài)代理的使用。
在測試類中,首先創(chuàng)建一個目標對象 RealImage
,然后創(chuàng)建一個 ImageInvocationHandler
實例,并將目標對象傳入構造函數(shù)。接下來,通過調(diào)用 Proxy
類的 newProxyInstance()
方法來生成代理對象。最后,使用代理對象調(diào)用 display()
方法顯示圖片。
在方法調(diào)用時,代理對象會調(diào)用 InvocationHandler
接口中的 invoke()
方法處理方法調(diào)用。在示例中,我們在 invoke()
方法中實現(xiàn)了打印方法名的額外操作,并通過反射調(diào)用目標對象的方法。
以上就是Java代理模式之靜態(tài)代理與動態(tài)代理的區(qū)別及優(yōu)缺點的詳細內(nèi)容,更多關于Java 靜態(tài)與動態(tài)代理的資料請關注腳本之家其它相關文章!
相關文章
Java調(diào)用Deepseek-R1.1.5b大模型的超詳細教程(附代碼)
這篇文章主要為大家介紹了Java調(diào)用Deepseek-R1.1.5b大模型的超詳細教程并附上了代碼,文中的教程講解詳細,有需要的小伙伴可以參考一下2025-03-03SpringBoot配置使Mybatis打印SQL執(zhí)行時的實際參數(shù)值操作
這篇文章主要介紹了SpringBoot配置使Mybatis打印SQL執(zhí)行時的實際參數(shù)值操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12