Java 技术之动态代理机制
文章推薦
- 精選java等全套學(xué)習(xí)資源
- 精選java電子圖書資源
- 精選大數(shù)據(jù)學(xué)習(xí)資源
- java項目練習(xí)精選
常規(guī)的代理模式有以下三個部分組成:
功能接口
功能提供者
class FunctionProvider implement IFunction {public void doAThing {System.out.print("do A");} }功能代理者
class Proxy implement IFunction {private FunctionProvider provider;Proxy(FunctionProvider provider) {this.provider = provider;}public void doAThing {provider.doAThing();} }前兩者就是普通的接口和實現(xiàn)類,而第三個就是所謂的代理類。對于使用者而言,他會讓代理類去完成某件任務(wù),并不關(guān)心這件任務(wù)具體的跑腿者。
這就是靜態(tài)代理,好處是方便調(diào)整變換具體實現(xiàn)類,而使用者不會受到任何影響。
不過這種方式也存在弊端:比如有多個接口需要進(jìn)行代理,那么就要為每一個功能提供者創(chuàng)建對應(yīng)的一個代理類,那就會越來越龐大。而且,所謂的“靜態(tài)”代理,意味著必須提前知道被代理的委托類。
通過下面一個例子來說明下:
統(tǒng)計函數(shù)耗時–靜態(tài)代理實現(xiàn)
現(xiàn)在希望通過一個代理類,對我感興趣的方法進(jìn)行耗時統(tǒng)計,利用靜態(tài)代理有如下實現(xiàn):
interface IAFunc {void doA(); } interface IBFunc {void doB(); }class TimeConsumeProxy implement IAFunc, IBFunc {private AFunc a;private BFunc b;public(AFunc a, BFunc b) {this.a = a;this.b = b;}void doA() {long start = System.currentMillions();a.doA();System.out.println("耗時:" + (System.currentMillions() - start));}void doB() {long start = System.currentMillions();b.doB();System.out.println("耗時:" + (System.currentMillions() - start));} }弊端很明顯,如果接口越多,每新增一個函數(shù)都要去修改這個 TimeConsumeProxy 代理類:把委托類對象傳進(jìn)去,實現(xiàn)接口,在函數(shù)執(zhí)行前后統(tǒng)計耗時。
這種方式顯然不是可持續(xù)性的,下面來看下使用動態(tài)代理的實現(xiàn)方式,進(jìn)行對比。
###動態(tài)代理
動態(tài)代理的核心思想是通過 Java Proxy 類,為傳入進(jìn)來的任意對象動態(tài)生成一個代理對象,這個代理對象默認(rèn)實現(xiàn)了原始對象的所有接口。
還是通過統(tǒng)計函數(shù)耗時例子來說明更加直接。
統(tǒng)計函數(shù)耗時–動態(tài)代理實現(xiàn)
具體使用時:
public static void main(String[] args) {A a = new A();IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);aProxy.doA();B b = new B();IBFunc bProxy = (IBFunc) new TimeConsumeProxy().bind(b);bProxy.doB(); }這里最大的區(qū)別就是:代理類和委托類 互相透明獨(dú)立,邏輯沒有任何耦合,在運(yùn)行時才綁定在一起。這也就是靜態(tài)代理與動態(tài)代理最大的不同,帶來的好處就是:無論委托類有多少個,代理類不受到任何影響,而且在編譯時無需知道具體委托類。
回到動態(tài)代理本身,上面代碼中最重要的就是:
Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);通過 Proxy 工具,把真實委托類轉(zhuǎn)換成了一個代理類,最開始提到了一個代理模式的三要素:功能接口、功能提供者、功能代理者;在這里對應(yīng)的就是:realObject.getClass().getInterfaces(),realObject,TimeConsumeProxy。
其實動態(tài)代理并不復(fù)雜,通過一個 Proxy 工具,為委托類的接口自動生成一個代理對象,后續(xù)的函數(shù)調(diào)用都通過這個代理對象進(jìn)行發(fā)起,最終會執(zhí)行到 InvocationHandler#invoke 方法,在這個方法里除了調(diào)用真實委托類對應(yīng)的方法,還可以做一些其他自定義的邏輯,比如上面的運(yùn)行耗時統(tǒng)計等。
###探索動態(tài)代理實現(xiàn)機(jī)制
好了,上面我們已經(jīng)把動態(tài)代理的基本用法及為什么要用動態(tài)代理進(jìn)行了講解,很多文章到這里也差不多了,不過我們還準(zhǔn)備進(jìn)一步探索一下給感興趣的讀者。
拋出幾個問題:
上面生成的代理對象 Object proxyObject 究竟是個什么東西?為什么它可以轉(zhuǎn)型成 IAFunc,還能調(diào)用doA() 方法?
這個 proxyObject 是怎么生成出來的?它是一個 class 嗎?
下面我先給出答案,再一步步探究這個答案是如何來的。
問題一: proxyObject 究竟是個什么 -> 動態(tài)生成的 $Proxy0.class 文件
在調(diào)用 Proxy.newInstance 后,Java 最終會為委托類 A 生成一個真實的 class 文件:$Proxy0.class,而 proxyObject 就是這個 class 的一個實例。
猜一下,這個 $Proxy0.class 類長什么樣呢,包含了什么方法呢?回看下剛剛的代碼:
IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a); aProxy.doA();推理下,顯然這個 $Proxy0.class 實現(xiàn)了 IAFunc 接口,同時它內(nèi)部也實現(xiàn)了 doA() 方法,而且重點(diǎn)是:這個 doA() 方法在運(yùn)行時會執(zhí)行到 TimeConsumeProxy#invoke() 方法里。
重點(diǎn)來了!下面我們來看下這個 $Proxy0.class 文件,把它放進(jìn) IDE 反編譯下,可以看到如下內(nèi)容,來驗證下剛剛的猜想:
final class $Proxy0 extends Proxy implements IAFunc {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws {super(var1);}public final boolean equals(Object var1) throws {// 省略}public final void doA() throws {try {// 劃重點(diǎn)super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws {// 省略}public final int hashCode() throws {// 省略}static {try {// 劃重點(diǎn)m3 = Class.forName("proxy.IAFunc").getMethod("doA", new Class[0]);m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}} }沒錯,剛剛的猜想都中了!實現(xiàn)了 IAFunc 接口和 doA() 方法,不過,doA()里是什么鬼?
super.h.invoke(this, m3, (Object[])null);回看下,TimeConsumeProxy里面的 invoke 方法,它的函數(shù)簽名是啥?
public Object invoke(Object proxy, Method method, Object[] args);沒錯,doA()里做的就是調(diào)用 TimeConsumeProxy#invoke() 方法。
那么也就是說,下面這段代碼執(zhí)行流程如下:
IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a); aProxy.doA();基于傳入的委托類 A,生成一個$Proxy0.class 文件;
創(chuàng)建一個 $Proxy0.class 對象,轉(zhuǎn)型為 IAFunc 接口;
調(diào)用 aProxy.doA() 時,自動調(diào)用 TimeConsumeProxy 內(nèi)部的 invoke 方法。
問題二:proxyObject 是怎么一步步生成出來的 -> $Proxy0.class 文件生成流程
剛剛從末尾看了結(jié)果,現(xiàn)在我們回到代碼的起始端來看:
Object proxyObject = Proxy.newInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);準(zhǔn)備好,開始發(fā)車讀源碼了。我會截取重要的代碼并加上注釋。
先看Proxy.newInstance():
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {// 復(fù)制要代理的接口 final Class<?>[] intfs = interfaces.clone();// 重點(diǎn):生成 $Proxy0.class 文件并通過 ClassLoader 加載進(jìn)來Class<?> cl = getProxyClass0(loader, intfs);// 對 $Proxy0.class 生成一個實例,就是 `proxyObject`final Constructor<?> cons = cl.getConstructor(constructorParams);return cons.newInstance(new Object[]{h}); }再來看 getProxyClass0 的具體實現(xiàn):ProxyClassFactory工廠類:
@Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 參數(shù)為 ClassLoader 和要代理的接口Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);// 1. 驗證 ClassLoader 和接口有效性for (Class<?> intf : interfaces) {// 驗證 classLoader 正確性Class<?> interfaceClass = Class.forName(intf.getName(), false, loader);if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}// 驗證傳入的接口 class 有效if (!interfaceClass.isInterface()) { ... } // 驗證接口是否重復(fù)if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { ... }}// 2. 創(chuàng)建包名及類名 $Proxy0.classproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;// 3. 創(chuàng)建 class 字節(jié)碼內(nèi)容byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);// 4. 基于字節(jié)碼和類名,生成 Class<?> 對象return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); }再看下第三步生成 class 內(nèi)容 ProxyGenerator.generateProxyClass:
// 添加 hashCode equals toString 方法 addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); // 添加委托類的接口實現(xiàn) for (int i = 0; i < interfaces.length; i++) {Method[] methods = interfaces[i].getMethods();for (int j = 0; j < methods.length; j++) {addProxyMethod(methods[j], interfaces[i]);} } // 添加構(gòu)造函數(shù) methods.add(this.generateConstructor());這里構(gòu)造好了類的內(nèi)容:添加必要的函數(shù),實現(xiàn)接口,構(gòu)造函數(shù)等,下面就是要寫入上一步看到的 $Proxy0.class 了。
ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); dout.writeInt(0xCAFEBABE); ... dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); ... return bout.toByteArray();到這里就生成了第一步看到的 $Proxy0.class 文件了,完成閉環(huán),講解完成!
###動態(tài)代理小結(jié)
通過上面的講解可以看出,動態(tài)代理可以隨時為任意的委托類進(jìn)行代理,并可以在 InvocationHandler#invoke 拿到運(yùn)行時的信息,并可以做一些切面處理。
在動態(tài)代理背后,其實是為一個委托類動態(tài)生成了一個 $Proxy0.class 的代理類,該代理類會實現(xiàn)委托類的接口,并把接口調(diào)用轉(zhuǎn)發(fā)到 InvocationHandler#invoke 上,最終調(diào)用到真實委托類的對應(yīng)方法。
動態(tài)代理機(jī)制把委托類和代理類進(jìn)行了隔離,提高了擴(kuò)展性。
###Java 動態(tài)代理與 Python 裝飾器
這是 Java 語言提供的一個有意思的語言特性,而其實 Python 里也提供了一種類似的特性:裝飾器,可以達(dá)到類似的面相切面編程思想,下次有空再把兩者做下對比,這次先到這。
轉(zhuǎn)自:https://juejin.im/entry/5a82fd0f6fb9a0633757368c
文章有不當(dāng)之處,歡迎指正,你也可以關(guān)注我的微信公眾號:好好學(xué)java,獲取優(yōu)質(zhì)資源。
總結(jié)
以上是生活随笔為你收集整理的Java 技术之动态代理机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何生成安全的密码 Hash:MD5,
- 下一篇: Java7 HashMap详解