Java動(dòng)態(tài)代理四種實(shí)現(xiàn)方式詳解
代理模式也是一種非常常見的設(shè)計(jì)模式。了解Spring框架的都知道,Spring AOP 使用的就是動(dòng)態(tài)代理模式。今天就來系統(tǒng)的重溫一遍代理模式。
在現(xiàn)實(shí)生活中代理是隨處可見的,當(dāng)事人因某些隱私不方便出面,或者當(dāng)事人不具備某些相關(guān)的專業(yè)技能,而需要一個(gè)職業(yè)人員來完成一些專業(yè)的操作, 也可能由于當(dāng)事人沒有時(shí)間處理事務(wù),而聘用代理人出面。而在軟件設(shè)計(jì)中,使用代理模式的地方也很多,由于安全原因,屏蔽客戶端直接訪問真實(shí)對(duì)象, 或者為了提升系統(tǒng)性能,使用代理模式實(shí)現(xiàn)延遲加載,還有就是AOP,對(duì)委托類的功能進(jìn)行增強(qiáng)等。
一、代理模式的結(jié)構(gòu)
代理模式的主要參與者有4個(gè),如下圖所示:
角色 | 作用 |
---|---|
Subject | 主題接口,定義了代理類和委托類的公共對(duì)外方法,也是代理類代理委托類的方法 |
RealSubject | 委托類,真實(shí)主題,真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類 |
Proxy | 代理類,代理和封裝委托類 |
Client | 客戶端,使用代理類和主題接口完成業(yè)務(wù)邏輯 |
loading="lazy" alt="" />角色作用Subject主題接口,定義了代理類和委托類的公共對(duì)外方法,也是代理類代理委托類的方法RealSubject委托類,真實(shí)主題,真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類Proxy代理類,代理和封裝委托類Client客戶端,使用代理類和主題接口完成業(yè)務(wù)邏輯
二、代理模式的實(shí)現(xiàn)
代理模式一般分為靜態(tài)代理和動(dòng)態(tài)代理兩種:
- 靜態(tài)代理,顧名思義,就是提前創(chuàng)建好代理類文件并在程序運(yùn)行前已經(jīng)編譯成字節(jié)碼。
- 動(dòng)態(tài)代理,是指在運(yùn)行時(shí)動(dòng)態(tài)生成代理類,即代理類的字節(jié)碼將在運(yùn)行時(shí)生成并載入到ClassLoader中。
了解了兩種代理模式大概區(qū)別后,接下來就以一個(gè)短信發(fā)送功能增強(qiáng)的示例來詳細(xì)闡述兩種代理的實(shí)現(xiàn)方式。
1、靜態(tài)代理實(shí)現(xiàn)
第一步,定義主題接口,該接口只有一個(gè)send
方法:
public interface ISender { public boolean send(); }
第二步,定義主題真正實(shí)現(xiàn)類:
public class SmsSender implements ISender { public boolean send() { System.out.println("sending msg"); return true; } }
第三步,創(chuàng)建代理類,封裝實(shí)現(xiàn)類:
public class ProxySender implements ISender { private ISender sender; public ProxySender(ISender sender){ this.sender = sender; } public boolean send() { System.out.println("處理前"); boolean result = sender.send(); System.out.println("處理后"); return result; } }
第四步,客戶端調(diào)用:
@Test public void testStaticProxy(){ ISender sender = new ProxySender(new SmsSender()); boolean result = sender.send(); System.out.println("輸出結(jié)果:" + result); }
以上就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的靜態(tài)代理,很明顯,靜態(tài)代理需要為每個(gè)真實(shí)主題定義一個(gè)形式上完全一樣的封裝類,
如果真實(shí)主題方法有所修改,那代理類也需要跟著修改,不利于系統(tǒng)的維護(hù)。
2、動(dòng)態(tài)代理實(shí)現(xiàn)
與靜態(tài)代理相比,動(dòng)態(tài)代理有更多優(yōu)勢(shì),動(dòng)態(tài)代理不僅不需要定義代理類,甚至可以在運(yùn)行時(shí)指定代理類的執(zhí)行邏輯,從而大大提升系統(tǒng)的靈活性。
目前動(dòng)態(tài)代理類的生成方法有很多,有JDK自帶的動(dòng)態(tài)代理、CGLIB、Javassist和ASM庫等。
- JDK動(dòng)態(tài)代理:內(nèi)置在JDK中,不需要引入第三方j(luò)ar,使用簡(jiǎn)單,但功能比較弱。
- CGLIB/Javassist:這兩個(gè)都是高級(jí)的字節(jié)碼生成庫,總體性能比JDK動(dòng)態(tài)代理好,且功能強(qiáng)大。
- ASM:低級(jí)字節(jié)碼生成工具,近乎使用bytecode編碼,對(duì)開發(fā)人員要求最高。當(dāng)然性能也是最好(相比前幾種也不是很大的提升,這里不做具體介紹)。
以下實(shí)例依然以SmsSender
和ISender
作為被代理對(duì)象和接口進(jìn)行試驗(yàn)。
1) JDK動(dòng)態(tài)代理
JDK的動(dòng)態(tài)代理需要實(shí)現(xiàn)一個(gè)處理方法調(diào)用的Handler,用于實(shí)現(xiàn)代理方法的內(nèi)部邏輯,實(shí)現(xiàn)InvocationHandler
接口。
public class JdkProxyHandler implements InvocationHandler { private Object target; public JdkProxyHandler(Object target){ this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("處理前"); Object result = method.invoke(target,args); System.out.println("處理后"); return result; } }
客戶端調(diào)用:
@Test public void testJdkProxy(){ ISender sender = (ISender) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{ISender.class}, new JdkProxyHandler(new SmsSender())); boolean result = sender.send(); System.out.println("代理對(duì)象:" + sender.getClass().getName()); System.out.println("輸出結(jié)果:" + result); }
輸出結(jié)果:
處理前
sending msg
處理后
代理對(duì)象:com.sun.proxy.$Proxy4
輸出結(jié)果:true
這樣實(shí)現(xiàn)一個(gè)簡(jiǎn)單的AOP就完成了,我們看到代理類的類型是com.sun.proxy.$Proxy4
。那JDK是如何創(chuàng)建代理類?
首先從Proxy.newProxyInstance入手,來研究JDK是如何生成代理類:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
該方法有3個(gè)參數(shù):
- loader:用哪個(gè)類加載器去加載代理對(duì)象,生成目標(biāo)對(duì)象的代理需要確保其類加載器相同,所以需要將目標(biāo)對(duì)象的類加載器作為參數(shù)傳遞。
- interfaces:代理類需實(shí)現(xiàn)的接口列表,JDK動(dòng)態(tài)代理技術(shù)需要代理類和目標(biāo)對(duì)象都繼承自同一接口,所以需要將目標(biāo)對(duì)象的接口作為參數(shù)傳遞。
- h:調(diào)用處理器,調(diào)用實(shí)現(xiàn)了InvocationHandler類的一個(gè)回調(diào)方法,對(duì)目標(biāo)對(duì)象的增強(qiáng)邏輯在這個(gè)實(shí)現(xiàn)類中。
具體代碼如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //1.檢查 Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ //獲取代理類類型 Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //通過反射創(chuàng)建代理對(duì)象 final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
總結(jié):具體代碼細(xì)節(jié)就不在這里深究,但可以明顯的看出,JDK的動(dòng)態(tài)代理底層是通過Java反射機(jī)制實(shí)現(xiàn)的,并且需要目標(biāo)對(duì)象繼承自一個(gè)接口才能生成它的代理類。
2) CGLIB(Code Generation Library)動(dòng)態(tài)代理
使用CGLIB動(dòng)態(tài)代理前需要引入依賴:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
和JDK代理不同,CGLib動(dòng)態(tài)代理技術(shù)不需要目標(biāo)對(duì)象實(shí)現(xiàn)自一個(gè)接口,只需要實(shí)現(xiàn)一個(gè)處理代理邏輯的切入類,并實(shí)現(xiàn)MethodInterceptor
接口。
定義真實(shí)主題實(shí)現(xiàn)類:
public class BdSender { public boolean send() { System.out.println("sending msg"); return true; } }
代理類邏輯處理類:
public class CglibProxyInterceptor implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); /** * 獲取代理類 * @param clazz * @return */ public Object getProxy(Class clazz) { enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("處理前"); Object result = methodProxy.invokeSuper(object,args); System.out.println("處理后"); return result; } }
客戶端調(diào)用:
@Test public void testCglibProxy(){ BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class); boolean result = sender.send(); System.out.println("代理對(duì)象:" + sender.getClass().getName()); System.out.println("輸出結(jié)果:" + result); }
輸出結(jié)果:
處理前
sending msg
處理后
代理對(duì)象:org.yd.proxy.BdSender$$EnhancerByCGLIB$$d65f9e34
輸出結(jié)果:true
總結(jié)CgLib的特點(diǎn):
- 使用CGLib實(shí)現(xiàn)動(dòng)態(tài)代理,完全不受代理類必須實(shí)現(xiàn)接口的限制
- CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,比使用Java反射效率要高
- CGLib不能對(duì)聲明為final的方法進(jìn)行代理,因?yàn)镃GLib原理是動(dòng)態(tài)生成被代理類的子類
3)Javassist動(dòng)態(tài)代理
Javassist是一個(gè)開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫,可以直接編輯和生成Java生成的字節(jié)碼。
相對(duì)于bcel, asm等這些工具,開發(fā)者不需要了解虛擬機(jī)指令,就能動(dòng)態(tài)改變類的結(jié)構(gòu),或者動(dòng)態(tài)生成類。
使用avassist動(dòng)態(tài)代理前需要引入依賴:
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.27.0-GA</version> </dependency>
使用Javassist生成動(dòng)態(tài)代理可以有以下兩種方式:
- 代理工廠創(chuàng)建:需要實(shí)現(xiàn)
MethodHandler
用于代理邏輯處理,實(shí)現(xiàn)與CGLib非常類似 - 動(dòng)態(tài)代碼創(chuàng)建:可通過Java代碼生成字節(jié)碼,這種方式創(chuàng)建的動(dòng)態(tài)代理非常靈活,甚至可以在運(yùn)行時(shí)生成業(yè)務(wù)邏輯
代理工廠創(chuàng)建 — 代理邏輯處理類
public class JavassistProxyHandler implements MethodHandler { private ProxyFactory proxyFactory = new ProxyFactory(); /** * 獲取代理對(duì)象 * @param clazz 被代理類 * @return * @throws Exception */ public Object getProxy(Class clazz) throws Exception { proxyFactory.setSuperclass(clazz); Class<?> factoryClass = proxyFactory.createClass(); Object proxy = factoryClass.newInstance(); ((ProxyObject)proxy).setHandler(this); return proxy; } public Object invoke(Object object, Method method, Method method1, Object[] args) throws Throwable { System.out.println("處理前"); Object result = method1.invoke(object,args); System.out.println("處理后"); return result; } }
客戶端調(diào)用:
@Test public void testJavassistProxy() throws Exception { BdSender sender = (BdSender) new JavassistProxyHandler().getProxy(BdSender.class); boolean result = sender.send(); System.out.println("代理對(duì)象:" + sender.getClass().getName()); System.out.println("輸出結(jié)果:" + result); }
輸出結(jié)果
處理前
sending msg
處理后
代理對(duì)象:org.yd.proxy.BdSender_$$_jvstbce_0
輸出結(jié)果:true
動(dòng)態(tài)代碼創(chuàng)建 — 代理邏輯處理類:
public static Object getProxy(Class clazz) throws Exception { ClassPool mPool = ClassPool.getDefault(); CtClass c0 = mPool.get(clazz.getName()); //定義代理類名稱 CtClass mCtc = mPool.makeClass(clazz.getName() + "$$BytecodeProxy"); //添加父類繼承 mCtc.setSuperclass(c0); //添加類的字段信息 CtField field = new CtField(c0, "real", mCtc); field.setModifiers(AccessFlag.PRIVATE); mCtc.addField(field); //添加構(gòu)造函數(shù) CtConstructor constructor = new CtConstructor(new CtClass[]{c0},mCtc); constructor.setBody("{$0.real = $1;}"); // $0代表this, $1代表構(gòu)造函數(shù)的第1個(gè)參數(shù) mCtc.addConstructor(constructor); //添加方法 CtMethod ctMethod = mCtc.getSuperclass().getDeclaredMethod("send"); CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(),ctMethod.getParameterTypes(), mCtc); newMethod.setBody("{" + "System.out.println(\"處理前\");" + "boolean result = $0.real.send();" + "System.out.println(\"處理后\");" + "return result;}"); mCtc.addMethod(newMethod); //生成動(dòng)態(tài)類 return mCtc.toClass().getConstructor(clazz).newInstance(clazz.newInstance()); }
客戶端調(diào)用:
@Test public void testJavassisBytecodetProxy() throws Exception { BdSender sender = (BdSender) JavassistDynamicCodeProxy.getProxy(BdSender.class); boolean result = sender.send(); System.out.println("代理對(duì)象:" + sender.getClass().getName()); System.out.println("輸出結(jié)果:" + result); }
輸出結(jié)果:
處理前
sending msg
處理后
代理對(duì)象:org.yd.proxy.BdSender$$BytecodeProxy
輸出結(jié)果:true
Javassist被用于struts2和hibernate中,都用來做動(dòng)態(tài)字節(jié)碼修改使用。一般開發(fā)中不會(huì)用到,但在封裝框架時(shí)比較有用。
以上介紹了靜態(tài)代理和動(dòng)態(tài)代理創(chuàng)建的幾種方法與優(yōu)缺點(diǎn)介紹,希望可以幫到大家。
到此這篇關(guān)于Java四種動(dòng)態(tài)代理實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)Java動(dòng)態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解spring boot 以jar的方式啟動(dòng)常用shell腳本
本篇文章主要介紹了詳解spring boot 以jar的方式啟動(dòng)常用shell腳本,具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09IDEA引MAVEN項(xiàng)目jar包依賴導(dǎo)入問題解決方法
這篇文章主要介紹了IDEA引MAVEN項(xiàng)目jar包依賴導(dǎo)入問題解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11JAVA通過Filter實(shí)現(xiàn)允許服務(wù)跨域請(qǐng)求的方法
這里的域指的是這樣的一個(gè)概念:我們認(rèn)為若協(xié)議 + 域名 + 端口號(hào)均相同,那么就是同域即我們常說的瀏覽器請(qǐng)求的同源策略。這篇文章主要介紹了JAVA通過Filter實(shí)現(xiàn)允許服務(wù)跨域請(qǐng)求,需要的朋友可以參考下2018-11-11Spring Boot中使用Spring-data-jpa實(shí)現(xiàn)數(shù)據(jù)庫增刪查改
本篇文章主要介紹了Spring Boot中使用Spring-data-jpa實(shí)現(xiàn)增刪查改,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-03-03Java增強(qiáng)for循環(huán)的增刪操作代碼
Foreach循環(huán)(Foreach loop)是計(jì)算機(jī)編程語言中的一種控制流程語句,通常用來循環(huán)遍歷數(shù)組或集合中的元素,本文通過實(shí)例演示普通for循環(huán)和foreach循環(huán)使用,java增強(qiáng)for循環(huán)的操作代碼感興趣的朋友一起看看吧2024-02-02mybatis中的擴(kuò)展實(shí)現(xiàn)源碼解析
這篇文章主要介給大家紹了關(guān)于mybatis中擴(kuò)展實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01