詳解Java的Proxy動(dòng)態(tài)代理機(jī)制
在說Java動(dòng)態(tài)代理之前,還是要說一下Jvm加載對(duì)象的過程,這個(gè)依舊是理解動(dòng)態(tài)代理的基礎(chǔ)性原理:
Java類即源代碼程序.java類型文件,經(jīng)過編譯器編譯之后就被轉(zhuǎn)換成字節(jié)代碼.class類型文件,類加載器負(fù)責(zé)讀取字節(jié)代碼,并轉(zhuǎn)換成java.lang.Class對(duì)象,描述類在元數(shù)據(jù)空間的數(shù)據(jù)結(jié)構(gòu),類被實(shí)例化時(shí),堆中存儲(chǔ)實(shí)例化的對(duì)象信息,并且通過對(duì)象類型數(shù)據(jù)的指針找到類。
過程描述:源碼->.java文件->.class文件->Class對(duì)象->實(shí)例對(duì)象
所以通過New創(chuàng)建對(duì)象,獨(dú)斷其背后很多實(shí)現(xiàn)細(xì)節(jié),理解上述過程之后,再了解一個(gè)常用的設(shè)計(jì)模式,即代理模式。
二、代理模式2.1、基本描述代理模式給某一個(gè)(目標(biāo))對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象持有目標(biāo)對(duì)象的引用。所謂代理,就是一個(gè)對(duì)象代表另一個(gè)對(duì)象執(zhí)行相應(yīng)的動(dòng)作程序。而代理對(duì)象可以在客戶端和目標(biāo)對(duì)象之間起到中介的作用。
代理模式在實(shí)際的生活中場(chǎng)景很多,例如中介、律師、代購(gòu)等行業(yè),都是簡(jiǎn)單的代理邏輯,在這個(gè)模式下存在兩個(gè)關(guān)鍵角色:
目標(biāo)對(duì)象角色:即代理對(duì)象所代表的對(duì)象。
代理對(duì)象角色:內(nèi)部含有目標(biāo)對(duì)象的引用,可以操作目標(biāo)對(duì)象;AOP編程就是基于這個(gè)思想。
2.2、靜動(dòng)態(tài)模式 靜態(tài)代理:在程序運(yùn)行之前確定代理角色,并且明確代理類和目標(biāo)類的關(guān)系。 動(dòng)態(tài)代理:基于Java反射機(jī)制,在JVM運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建和生成代理對(duì)象。三、靜態(tài)代理基于上述靜態(tài)代理的概念,用一段代碼進(jìn)行描述實(shí)現(xiàn),基本邏輯如下:
明確目標(biāo)對(duì)象即被代理的對(duì)象; 定義代理對(duì)象,通過構(gòu)造器持有目標(biāo)對(duì)象; 代理對(duì)象中定義前后置增強(qiáng)方法;目標(biāo)對(duì)象與前后置增強(qiáng)代碼就組成了代理對(duì)象,這樣就不用直接訪問目標(biāo)對(duì)象,像極了電視劇中那句話:我是律師,我的當(dāng)事人不方便和你對(duì)話。
public class Proxy01 { public static void main(String[] args) {TargetObj targetObj = new TargetObj() ;ProxyObj proxyObj = new ProxyObj(targetObj) ;proxyObj.invoke(); }}class TargetObj { public void execute (){System.out.println('目標(biāo)類方法執(zhí)行...'); }}class ProxyObj { private TargetObj targetObj ; /** * 持有目標(biāo)對(duì)象 */ public ProxyObj (TargetObj targetObj){this.targetObj = targetObj ; } /** * 目標(biāo)對(duì)象方法調(diào)用 */ public void invoke (){before () ;targetObj.execute();after () ; } /** * 前后置處理 */ public void before (){System.out.println('代理對(duì)象前置處理...'); } public void after (){System.out.println('代理對(duì)象后置處理...'); }}
靜態(tài)代理明確定義了代理對(duì)象,即有一個(gè)代理對(duì)象的.java文件加載到JVM的過程,很顯然的一個(gè)問題,在實(shí)際的開發(fā)過程中,不可能為每個(gè)目標(biāo)對(duì)象都定義一個(gè)代理類,同樣也不能讓一個(gè)代理對(duì)象去代理多個(gè)目標(biāo)對(duì)象,這兩種方式的維護(hù)成本都極高。
代理模式的本質(zhì)是在目標(biāo)對(duì)象的方法前后置入增強(qiáng)操作,但是又不想修改目標(biāo)類,通過前面反射機(jī)制可以知道,在運(yùn)行的時(shí)候可以獲取對(duì)象的結(jié)構(gòu)信息,基于Class信息去動(dòng)態(tài)創(chuàng)建代理對(duì)象,這就是動(dòng)態(tài)代理機(jī)制。
順便說一句:技術(shù)的底層實(shí)現(xiàn)邏輯不好理解是眾所周知,然而基礎(chǔ)知識(shí)點(diǎn)并不復(fù)雜,例如代理模式的基本原理,但是結(jié)合到實(shí)際的復(fù)雜應(yīng)用中(AOP模式),很難活靈活現(xiàn)的理解到是基于反射和動(dòng)態(tài)代理的方式實(shí)現(xiàn)的。
四、動(dòng)態(tài)代理4.1、場(chǎng)景描述基于一個(gè)場(chǎng)景來描述動(dòng)態(tài)代理和靜態(tài)代理的區(qū)別,即最近幾年很火的概念,海外代購(gòu):
在代購(gòu)剛興起的初期,是一些常去海外出差的人,會(huì)接代購(gòu)需求,即代理人固定;后來就興起海外代購(gòu)平臺(tái),海淘等一系列產(chǎn)品,即用戶代購(gòu)需求(目標(biāo)對(duì)象)由代購(gòu)平臺(tái)去實(shí)現(xiàn),但是具體誰來操作這個(gè)就看即時(shí)分配,這個(gè)場(chǎng)景與動(dòng)態(tài)代理的原理類似。
4.2、基礎(chǔ)API案例首先看兩個(gè)核心類,這里簡(jiǎn)述下概念,看完基本過程再細(xì)聊:
Proxy-創(chuàng)建代理對(duì)象,核心參數(shù):
ClassLoader:(目標(biāo)類)加載器; Interfaces:(目標(biāo)類)接口數(shù)組; InvocationHandler:代理調(diào)用機(jī)制;InvocationHandler-代理類調(diào)用機(jī)制:
invoke:這個(gè)上篇說的反射原理; method:反射類庫(kù)中的核心API;目標(biāo)對(duì)象和接口
interface IUser { Integer update (String name) ;}class UserService implements IUser { @Override public Integer update(String name) {Integer userId = 99 ;System.out.println('UserId='+userId+';updateName='+name);return userId ; }}
代理對(duì)象執(zhí)行機(jī)制
class UserHandler implements InvocationHandler { private Object target ; public UserHandler (Object target){this.target = target ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println('before()...');Object result = method.invoke(target, args);System.out.println('after()...');return result; }}
具體組合方式
public class Proxy02 { public static void main(String[] args) {/* * 生成$Proxy0的class文件 */System.getProperties().put('sun.misc.ProxyGenerator.saveGeneratedFiles', 'true');/* * 目標(biāo)對(duì)象信息 */IUser userService = new UserService();ClassLoader classLoader = userService.getClass().getClassLoader();Class<?>[] interfaces = UserService.class.getInterfaces() ;/* * 創(chuàng)建代理對(duì)象 */InvocationHandler userHandler = new UserHandler(userService);/* * 代理類對(duì)象名 * proxyClassName=com.java.proxy.$Proxy0 */String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName();System.out.println('proxyClassName='+proxyClassName);/* * 具體業(yè)務(wù)實(shí)現(xiàn)模擬 */IUser proxyUser1 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);IUser proxyUser2 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);proxyUser1.update('cicada') ;proxyUser2.update('smile') ; }}
這里之所以要生成代理類的結(jié)構(gòu)信息,因?yàn)閺腏VM加載的過程看不到相關(guān)內(nèi)容,關(guān)鍵信息再次被獨(dú)斷:
javap -v Proxy02.class
查看代理類名稱
/* * proxyClassName=com.java.proxy.$Proxy0 */String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName();System.out.println('proxyClassName='+proxyClassName);
下意識(shí)輸出代理對(duì)象名稱,這里即對(duì)應(yīng)JVM機(jī)制,找到Class對(duì)象名,然后分析結(jié)構(gòu),這樣就明白動(dòng)態(tài)代理具體的執(zhí)行原理了。
生成代理類.class文件
System.getProperties().put('sun.misc.ProxyGenerator.saveGeneratedFiles', 'true');
通過上面JVM加載對(duì)象的機(jī)制可知,描述代理類的Class對(duì)象一定存在,只是在運(yùn)行時(shí)并沒有生成顯式的.class文件,通過上面生成代理類.class的語法,會(huì)在項(xiàng)目目錄的/com/java/proxy路徑下創(chuàng)建文件。
順便說一句:作為一只程序員,復(fù)雜總是和我們環(huán)環(huán)相繞,說好的簡(jiǎn)單點(diǎn)呢?
4.3、代理類結(jié)構(gòu)繼承與實(shí)現(xiàn)
class $Proxy0 extends Proxy implements IUser {}
從代理類的功能來思考,可以想到需要繼承Proxy與實(shí)現(xiàn)IUser接口,還有就是持有調(diào)用機(jī)制的具體實(shí)現(xiàn)類,用來做業(yè)務(wù)增強(qiáng)。
構(gòu)造方法
public $Proxy0(InvocationHandler var1) throws { super(var1);}
通過構(gòu)造方法,持有UserHandler具體的執(zhí)行機(jī)制對(duì)象。
接口實(shí)現(xiàn)
final class $Proxy0 extends Proxy implements IUser { private static Method m3; public final Integer update(String var1) throws {try { return (Integer)super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) { throw var3;} catch (Throwable var4) { throw new UndeclaredThrowableException(var4);} }}
目標(biāo)類的基本需求update()方法,通過代理類進(jìn)行承接,并基于UserHandler實(shí)現(xiàn)具體的增強(qiáng)業(yè)務(wù)處理。
基礎(chǔ)方法
final class $Proxy0 extends Proxy implements IUser { private static Method m0; private static Method m1; private static Method m2; public $Proxy0(InvocationHandler var1) throws {super(var1); } static {try { m1 = Class.forName('java.lang.Object').getMethod('equals', Class.forName('java.lang.Object')); m2 = Class.forName('java.lang.Object').getMethod('toString'); m3 = Class.forName('com.java.proxy.IUser').getMethod('update', Class.forName('java.lang.String')); m0 = Class.forName('java.lang.Object').getMethod('hashCode');} catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage());} } public final boolean equals(Object var1) throws {try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) { throw var3;} catch (Throwable var4) { throw new UndeclaredThrowableException(var4);} } public final String toString() throws {try { return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) { throw var2;} catch (Throwable var3) { throw new UndeclaredThrowableException(var3);} } public final int hashCode() throws {try { return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) { throw var2;} catch (Throwable var3) { throw new UndeclaredThrowableException(var3);} }}
基于Object類,定義Java中幾個(gè)常用方法equals()判斷,toString()方法,hashCode()值,這個(gè)在分析Map源碼的時(shí)候有說過為什么這幾個(gè)方法通常都是一起出現(xiàn)。
4.4、JDK源碼上面是案例執(zhí)行的過程和原理,還有一個(gè)關(guān)鍵點(diǎn)要明白,即JDK源碼的邏輯:
IUser proxyUser = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
Proxy提供的靜態(tài)方法newProxyInstance(),通過各個(gè)參數(shù)的傳入,構(gòu)建一個(gè)新的代理Class對(duì)象,即$Proxy0類的結(jié)構(gòu)信息,這里再回首看下三個(gè)核心參數(shù):
ClassLoader:基于JVM運(yùn)行過程,所以需要獲取目標(biāo)類UserService的類加載器; Interfaces:目標(biāo)類UserService實(shí)現(xiàn)的接口,從面向?qū)ο髞砜紤],接口與實(shí)現(xiàn)分離,代理類通過實(shí)現(xiàn)IUser接口,模擬目標(biāo)類的需求; InvocationHandler:代理類提供的功能封裝即UserHandler,可以在目標(biāo)方法調(diào)用前后做增強(qiáng)處理;最后總結(jié)一下動(dòng)態(tài)代理的實(shí)現(xiàn)的核心技術(shù)點(diǎn):Jvm加載原理、反射機(jī)制、面向?qū)ο笏枷耄幻看伍喿xJDK的源碼都會(huì)驚嘆設(shè)計(jì)者的鬼斧神工,滴水穿石堅(jiān)持才會(huì)有收獲。
五、源代碼地址GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent
以上就是詳解Java的Proxy動(dòng)態(tài)代理機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于Java Proxy 動(dòng)態(tài)代理機(jī)制的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. 得到XML文檔大小的方法2. JSP+Servlet實(shí)現(xiàn)文件上傳到服務(wù)器功能3. XML入門的常見問題(二)4. 在JSP中使用formatNumber控制要顯示的小數(shù)位數(shù)方法5. 如何在jsp界面中插入圖片6. ASP常用日期格式化函數(shù) FormatDate()7. jsp實(shí)現(xiàn)textarea中的文字保存換行空格存到數(shù)據(jù)庫(kù)的方法8. chat.asp聊天程序的編寫方法9. CSS3實(shí)例分享之多重背景的實(shí)現(xiàn)(Multiple backgrounds)10. Jsp中request的3個(gè)基礎(chǔ)實(shí)踐
