一文搞懂Java常見的三種代理模式(靜態(tài)代理、動態(tài)代理和cglib代理)
Java靜態(tài)代理
Java中的靜態(tài)代理是一種設(shè)計模式,它通過創(chuàng)建一個代理類來代替原始類,從而控制對原始類的訪問。代理類和原始類都實現(xiàn)相同的接口,使得客戶端在使用時無需關(guān)心具體的實現(xiàn)細節(jié)。靜態(tài)代理在編譯時就已經(jīng)確定代理類和原始類的關(guān)系,因此稱為靜態(tài)代理。
下面是Java靜態(tài)代理UML類圖:
示例代碼: 假設(shè)有一個文件上傳的接口 FileUploader
,其真實實現(xiàn)為 RealFileUploader
,我們需要在上傳文件前后記錄日志,這時就可以使用靜態(tài)代理。
首先,定義抽象接口 FileUploader
:
public interface FileUploader { void upload(String file); }
然后,實現(xiàn)真實上傳 RealFileUploader
:
public class RealFileUploader implements FileUploader { @Override public void upload(String file) { // 實際的文件上傳邏輯 System.out.println("Uploading file: " + file); } }
接下來,實現(xiàn)代理 ProxyFileUploader
:
public class ProxyFileUploader implements FileUploader { private FileUploader realFileUploader; public ProxyFileUploader(FileUploader realFileUploader) { this.realFileUploader = realFileUploader; } @Override public void upload(String file) { // 額外的處理,比如記錄日志 System.out.println("Before uploading: " + file); // 調(diào)用真實主題的上傳方法 realFileUploader.upload(file); // 額外的處理,比如記錄日志 System.out.println("After uploading: " + file); } }
在使用時,我們可以這樣創(chuàng)建代理類并進行文件上傳:
public class Main { public static void main(String[] args) { // 創(chuàng)建真實主題 FileUploader realFileUploader = new RealFileUploader(); // 創(chuàng)建代理主題,并將真實主題傳入 FileUploader proxyFileUploader = new ProxyFileUploader(realFileUploader); // 通過代理主題進行文件上傳 proxyFileUploader.upload("example.txt"); } }
運行上述代碼,輸出將會是:
Before uploading: example.txt
Uploading file: example.txt
After uploading: example.txt
Java動態(tài)代理
Java動態(tài)代理是一種在運行時創(chuàng)建代理類的機制,它允許在不提前知道代理類的具體類型的情況下,動態(tài)地創(chuàng)建一個代理對象來代替原始類。相比于靜態(tài)代理,動態(tài)代理更加靈活,可以代理任意的接口類型,不需要為每個被代理的類編寫專門的代理類,而是通過Java的反射機制在運行時動態(tài)生成代理類。動態(tài)代理主要使用 java.lang.reflect.Proxy
類和 java.lang.reflect.InvocationHandler
接口來實現(xiàn)。動態(tài)代理又被稱為JDK代理或接口代理。
下面看下動態(tài)代理的UML圖:
示例代碼: 與前面的靜態(tài)代理示例相同,我們?nèi)匀皇褂梦募蟼鞯慕涌?FileUploader
,其真實實現(xiàn)為 RealFileUploader
。接下來,我們將通過動態(tài)代理在上傳文件前后記錄日志。
首先,定義抽象主題接口 FileUploader
和實現(xiàn)真實主題 RealFileUploader
,與之前相同,不再重復。
然后,實現(xiàn) InvocationHandler 接口,創(chuàng)建動態(tài)代理的處理器類 DynamicProxyHandler
:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class DynamicProxyHandler implements InvocationHandler { private Object realObject; public DynamicProxyHandler(Object realObject) { this.realObject = realObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 額外的處理,比如記錄日志 System.out.println("Before invoking: " + method.getName()); // 調(diào)用真實主題的對應(yīng)方法 Object result = method.invoke(realObject, args); // 額外的處理,比如記錄日志 System.out.println("After invoking: " + method.getName()); return result; } }
在使用時,我們可以這樣創(chuàng)建動態(tài)代理對象并進行文件上傳:
import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { // 創(chuàng)建真實主題 FileUploader realFileUploader = new RealFileUploader(); // 創(chuàng)建動態(tài)代理的處理器 InvocationHandler handler = new DynamicProxyHandler(realFileUploader); // 創(chuàng)建代理類 FileUploader proxyFileUploader = (FileUploader) Proxy.newProxyInstance( FileUploader.class.getClassLoader(), new Class[]{FileUploader.class}, handler ); // 通過動態(tài)代理進行文件上傳 proxyFileUploader.upload("example.txt"); } }
運行上述代碼,輸出將會是:
Before invoking: upload
Uploading file: example.txt
After invoking: upload
同樣,通過動態(tài)代理,我們在文件上傳前后成功添加了額外的處理(記錄日志),同時客戶端代碼無需關(guān)心具體的日志記錄邏輯,實現(xiàn)了解耦。動態(tài)代理在很多場景下非常有用,例如AOP(面向切面編程)等。
CGLIB代理
CGLIB(Code Generation Library)是一個開源的第三方庫,用于在Java運行時生成字節(jié)碼并創(chuàng)建代理類。與Java標準庫中的動態(tài)代理(基于接口)不同,CGLIB代理可以代理普通類,即使它們沒有實現(xiàn)任何接口。CGLIB使用ASM庫來生成字節(jié)碼,并通過繼承的方式創(chuàng)建代理類,因此也被稱為子類代理。CGLIB廣泛用于各種框架和庫中,如Spring AOP。
下面詳細介紹CGLIB代理的結(jié)構(gòu)和示例:
- 示例代碼: 假設(shè)有一個簡單的類
Calculator
,我們希望在其方法執(zhí)行前后記錄日志,可以使用CGLIB代理。
首先,引入CGLIB庫,可以通過Maven添加依賴:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>版本號</version> </dependency>
然后,定義普通類 Calculator
:
public class Calculator { public int add(int a, int b) { return a + b; } }
接下來,創(chuàng)建一個代理類生成器 CalculatorProxyGenerator
:
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CalculatorProxyGenerator implements MethodInterceptor { private Object target; public CalculatorProxyGenerator(Object target) { this.target = target; } public Object createProxy() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before invoking: " + method.getName()); Object result = proxy.invokeSuper(obj, args); System.out.println("After invoking: " + method.getName()); return result; } }
在 intercept
方法中,我們執(zhí)行了代理類的方法(通過 proxy.invokeSuper(obj, args)
),在方法執(zhí)行前后添加了額外的處理。
最后,在使用時,我們可以這樣創(chuàng)建代理對象并調(diào)用方法:
public class Main { public static void main(String[] args) { Calculator calculator = new Calculator(); CalculatorProxyGenerator proxyGenerator = new CalculatorProxyGenerator(calculator); Calculator proxyCalculator = (Calculator) proxyGenerator.createProxy(); int result = proxyCalculator.add(3, 5); System.out.println("Result: " + result); } }
運行上述代碼,輸出將會是:
Before invoking: add
After invoking: add
Result: 8
這樣,通過CGLIB代理,我們成功在方法執(zhí)行前后添加了額外的處理(記錄日志),實現(xiàn)了解耦。CGLIB代理在不需要接口的情況下也能很好地完成代理任務(wù),但由于它是通過繼承的方式生成代理類,可能會影響某些場景,比如無法代理 final
方法。
動態(tài)代理與CGLIB代理的區(qū)別
Spring在5.X之前默認的動態(tài)代理實現(xiàn)一直是jdk動態(tài)代理。但是從5.X開始,spring就開始默認使用Cglib來作為動態(tài)代理實現(xiàn)。并且springboot從2.X開始也轉(zhuǎn)向了Cglib動態(tài)代理實現(xiàn)。
為什么spring體系整體轉(zhuǎn)投Cglib呢,jdk動態(tài)代理又有什么缺點呢?
- jdk動態(tài)代理只能基于接口,代理生成的對象只能賦值給接口變量,而Cglib就不存在這個問題,Cglib是通過生成子類來實現(xiàn)的,代理對象既可以賦值給實現(xiàn)類,又可以賦值給接口。
- Cglib速度比jdk動態(tài)代理更快,性能更好。
以上就是一文搞懂Java的三種代理模式(靜態(tài)代理、動態(tài)代理和cglib代理)的詳細內(nèi)容,更多關(guān)于Java代理模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot項目中的多數(shù)據(jù)源支持的方法
本篇文章主要介紹了SpringBoot項目中的多數(shù)據(jù)源支持的方法,主要介紹在SpringBoot項目中利用SpringDataJpa技術(shù)如何支持多個數(shù)據(jù)庫的數(shù)據(jù)源,有興趣的可以了解一下2017-10-10SpringBoot2.3.0配置JPA的實現(xiàn)示例
這篇文章主要介紹了SpringBoot2.3.0配置JPA的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08- 本章具體介紹了字節(jié)流、字符流的基本使用方法,圖解穿插代碼實現(xiàn)。 JAVA從基礎(chǔ)開始講,后續(xù)會講到JAVA高級,中間會穿插面試題和項目實戰(zhàn),希望能給大家?guī)韼椭?/div> 2022-03-03
最新評論