詳解Java的Proxy動態(tài)代理機制
一、Jvm加載對象
在說Java動態(tài)代理之前,還是要說一下Jvm加載對象的過程,這個依舊是理解動態(tài)代理的基礎性原理:
Java類即源代碼程序.java
類型文件,經(jīng)過編譯器編譯之后就被轉換成字節(jié)代碼.class
類型文件,類加載器負責讀取字節(jié)代碼,并轉換成java.lang.Class對象,描述類在元數(shù)據(jù)空間的數(shù)據(jù)結構,類被實例化時,堆中存儲實例化的對象信息,并且通過對象類型數(shù)據(jù)的指針找到類。
過程描述:源碼->.java文件->.class文件->Class對象->實例對象
所以通過New創(chuàng)建對象,獨斷其背后很多實現(xiàn)細節(jié),理解上述過程之后,再了解一個常用的設計模式,即代理模式。
二、代理模式
2.1、基本描述
代理模式給某一個(目標)對象提供一個代理對象,并由代理對象持有目標對象的引用。所謂代理,就是一個對象代表另一個對象執(zhí)行相應的動作程序。而代理對象可以在客戶端和目標對象之間起到中介的作用。
代理模式在實際的生活中場景很多,例如中介、律師、代購等行業(yè),都是簡單的代理邏輯,在這個模式下存在兩個關鍵角色:
目標對象角色:即代理對象所代表的對象。
代理對象角色:內部含有目標對象的引用,可以操作目標對象;AOP編程就是基于這個思想。
2.2、靜動態(tài)模式
- 靜態(tài)代理:在程序運行之前確定代理角色,并且明確代理類和目標類的關系。
- 動態(tài)代理:基于Java反射機制,在JVM運行時動態(tài)創(chuàng)建和生成代理對象。
三、靜態(tài)代理
基于上述靜態(tài)代理的概念,用一段代碼進行描述實現(xiàn),基本邏輯如下:
- 明確目標對象即被代理的對象;
- 定義代理對象,通過構造器持有目標對象;
- 代理對象中定義前后置增強方法;
目標對象與前后置增強代碼就組成了代理對象,這樣就不用直接訪問目標對象,像極了電視劇中那句話:我是律師,我的當事人不方便和你對話。
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("目標類方法執(zhí)行..."); } } class ProxyObj { private TargetObj targetObj ; /** * 持有目標對象 */ public ProxyObj (TargetObj targetObj){ this.targetObj = targetObj ; } /** * 目標對象方法調用 */ public void invoke (){ before () ; targetObj.execute(); after () ; } /** * 前后置處理 */ public void before (){ System.out.println("代理對象前置處理..."); } public void after (){ System.out.println("代理對象后置處理..."); } }
靜態(tài)代理明確定義了代理對象,即有一個代理對象的.java
文件加載到JVM的過程,很顯然的一個問題,在實際的開發(fā)過程中,不可能為每個目標對象都定義一個代理類,同樣也不能讓一個代理對象去代理多個目標對象,這兩種方式的維護成本都極高。
代理模式的本質是在目標對象的方法前后置入增強操作,但是又不想修改目標類,通過前面反射機制可以知道,在運行的時候可以獲取對象的結構信息,基于Class信息去動態(tài)創(chuàng)建代理對象,這就是動態(tài)代理機制。
順便說一句:技術的底層實現(xiàn)邏輯不好理解是眾所周知,然而基礎知識點并不復雜,例如代理模式的基本原理,但是結合到實際的復雜應用中(AOP模式),很難活靈活現(xiàn)的理解到是基于反射和動態(tài)代理的方式實現(xiàn)的。
四、動態(tài)代理
4.1、場景描述
基于一個場景來描述動態(tài)代理和靜態(tài)代理的區(qū)別,即最近幾年很火的概念,海外代購:
在代購剛興起的初期,是一些常去海外出差的人,會接代購需求,即代理人固定;后來就興起海外代購平臺,海淘等一系列產(chǎn)品,即用戶代購需求(目標對象)由代購平臺去實現(xiàn),但是具體誰來操作這個就看即時分配,這個場景與動態(tài)代理的原理類似。
4.2、基礎API案例
首先看兩個核心類,這里簡述下概念,看完基本過程再細聊:
Proxy-創(chuàng)建代理對象,核心參數(shù):
- ClassLoader:(目標類)加載器;
- Interfaces:(目標類)接口數(shù)組;
- InvocationHandler:代理調用機制;
InvocationHandler-代理類調用機制:
- invoke:這個上篇說的反射原理;
- method:反射類庫中的核心API;
目標對象和接口
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 ; } }
代理對象執(zhí)行機制
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"); /* * 目標對象信息 */ IUser userService = new UserService(); ClassLoader classLoader = userService.getClass().getClassLoader(); Class<?>[] interfaces = UserService.class.getInterfaces() ; /* * 創(chuàng)建代理對象 */ InvocationHandler userHandler = new UserHandler(userService); /* * 代理類對象名 * proxyClassName=com.java.proxy.$Proxy0 */ String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName(); System.out.println("proxyClassName="+proxyClassName); /* * 具體業(yè)務實現(xiàn)模擬 */ IUser proxyUser1 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler); IUser proxyUser2 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler); proxyUser1.update("cicada") ; proxyUser2.update("smile") ; } }
這里之所以要生成代理類的結構信息,因為從JVM加載的過程看不到相關內容,關鍵信息再次被獨斷:
javap -v Proxy02.class
查看代理類名稱
/* * proxyClassName=com.java.proxy.$Proxy0 */ String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName(); System.out.println("proxyClassName="+proxyClassName);
下意識輸出代理對象名稱,這里即對應JVM機制,找到Class對象名,然后分析結構,這樣就明白動態(tài)代理具體的執(zhí)行原理了。
生成代理類.class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
通過上面JVM加載對象的機制可知,描述代理類的Class對象一定存在,只是在運行時并沒有生成顯式的.class
文件,通過上面生成代理類.class
的語法,會在項目目錄的/com/java/proxy
路徑下創(chuàng)建文件。
順便說一句:作為一只程序員,復雜總是和我們環(huán)環(huán)相繞,說好的簡單點呢?
4.3、代理類結構
繼承與實現(xiàn)
class $Proxy0 extends Proxy implements IUser {}
從代理類的功能來思考,可以想到需要繼承Proxy與實現(xiàn)IUser接口,還有就是持有調用機制的具體實現(xiàn)類,用來做業(yè)務增強。
構造方法
public $Proxy0(InvocationHandler var1) throws { super(var1); }
通過構造方法,持有UserHandler具體的執(zhí)行機制對象。
接口實現(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); } } }
目標類的基本需求update()
方法,通過代理類進行承接,并基于UserHandler實現(xiàn)具體的增強業(yè)務處理。
基礎方法
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中幾個常用方法equals()判斷,toString()方法,hashCode()值,這個在分析Map源碼的時候有說過為什么這幾個方法通常都是一起出現(xiàn)。
4.4、JDK源碼
上面是案例執(zhí)行的過程和原理,還有一個關鍵點要明白,即JDK源碼的邏輯:
IUser proxyUser = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
Proxy提供的靜態(tài)方法newProxyInstance()
,通過各個參數(shù)的傳入,構建一個新的代理Class對象,即$Proxy0類的結構信息,這里再回首看下三個核心參數(shù):
- ClassLoader:基于JVM運行過程,所以需要獲取目標類UserService的類加載器;
- Interfaces:目標類UserService實現(xiàn)的接口,從面向對象來考慮,接口與實現(xiàn)分離,代理類通過實現(xiàn)IUser接口,模擬目標類的需求;
- InvocationHandler:代理類提供的功能封裝即UserHandler,可以在目標方法調用前后做增強處理;
最后總結一下動態(tài)代理的實現(xiàn)的核心技術點:Jvm加載原理、反射機制、面向對象思想;每次閱讀JDK的源碼都會驚嘆設計者的鬼斧神工,滴水穿石堅持才會有收獲。
五、源代碼地址
GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent
以上就是詳解Java的Proxy動態(tài)代理機制的詳細內容,更多關于Java Proxy 動態(tài)代理機制的資料請關注腳本之家其它相關文章!
相關文章
java使用JNA(Java Native Access)調用dll的方法
java使用JNA(Java Native Access)調用windows系統(tǒng)的dll文件的例子2013-11-11Java通過python命令執(zhí)行DataX任務的實例
今天小編就為大家分享一篇Java通過python命令執(zhí)行DataX任務的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08關于Unsupported major.minor version 49.0的錯誤解決辦法
這篇文章主要介紹了關于Unsupported major.minor version 49.0的錯誤解決辦法的相關資料,需要的朋友可以參考下2015-11-11Java注解實現(xiàn)動態(tài)數(shù)據(jù)源切換的實例代碼
本篇文章主要介紹了Java注解實現(xiàn)動態(tài)數(shù)據(jù)源切換的實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06