本篇內(nèi)容主要講解“什么是JDK動(dòng)態(tài)代理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“什么是JDK動(dòng)態(tài)代理”吧!
林芝網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,林芝網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為林芝上千多家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營銷網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的林芝做網(wǎng)站的公司定做!
JDK動(dòng)態(tài)代理是指:代理類實(shí)例在程序運(yùn)行時(shí),由JVM根據(jù)反射機(jī)制動(dòng)態(tài)的生成。也就是說代理類不是用戶自己定義的,而是由JVM生成的。
由于其原理是通過Java反射機(jī)制實(shí)現(xiàn)的,所以在學(xué)習(xí)前,要對(duì)反射機(jī)制有一定的了解。傳送門:Java反射機(jī)制:跟著代碼學(xué)反射
下面是本篇講述內(nèi)容:
JDK動(dòng)態(tài)代理有兩大核心類,它們都在Java的反射包下(java.lang.reflect
),分別為InvocationHandler
接口和Proxy
類。
代理實(shí)例的調(diào)用處理器需要實(shí)現(xiàn)
InvocationHandler
接口,并且每個(gè)代理實(shí)例都有一個(gè)關(guān)聯(lián)的調(diào)用處理器。當(dāng)一個(gè)方法在代理實(shí)例上被調(diào)用時(shí),這個(gè)方法調(diào)用將被編碼并分派到其調(diào)用處理器的invoke
方法上。
也就是說,我們創(chuàng)建的每一個(gè)代理實(shí)例都要有一個(gè)關(guān)聯(lián)的InvocationHandler
,并且在調(diào)用代理實(shí)例的方法時(shí),會(huì)被轉(zhuǎn)到InvocationHandler
的invoke
方法上。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
該invoke
方法的作用是:處理代理實(shí)例上的方法調(diào)用并返回結(jié)果。
其有三個(gè)參數(shù),分別為:
proxy:是調(diào)用該方法的代理實(shí)例。
method:是在代理實(shí)例上調(diào)用的接口方法對(duì)應(yīng)的Method
實(shí)例。
args:一個(gè)Object
數(shù)組,是在代理實(shí)例上的方法調(diào)用中傳遞的參數(shù)值。如果接口方法為無參,則該值為null。
其返回值為:調(diào)用代理實(shí)例上的方法的返回值。
Proxy
類提供了創(chuàng)建動(dòng)態(tài)代理類及其實(shí)例的靜態(tài)方法,該類也是動(dòng)態(tài)代理類的超類。
代理類具有以下屬性:
代理類的名稱以 “$Proxy” 開頭,后面跟著一個(gè)數(shù)字序號(hào)。
代理類繼承了Proxy
類。
代理類實(shí)現(xiàn)了創(chuàng)建時(shí)指定的接口(JDK動(dòng)態(tài)代理是面向接口的)。
每個(gè)代理類都有一個(gè)公共構(gòu)造函數(shù),它接受一個(gè)參數(shù),即接口InvocationHandler
的實(shí)現(xiàn),用于設(shè)置代理實(shí)例的調(diào)用處理器。
Proxy
提供了兩個(gè)靜態(tài)方法,用于獲取代理對(duì)象。
用于獲取代理類的Class
對(duì)象,再通過調(diào)用構(gòu)造函數(shù)創(chuàng)建代理實(shí)例。
public static Class> getProxyClass(ClassLoader loader, Class>... interfaces) throws IllegalArgumentException
該方法有兩個(gè)參數(shù):
loader:為類加載器。
intefaces:為接口的Class
對(duì)象數(shù)組。
返回值為動(dòng)態(tài)代理類的Class
對(duì)象。
用于創(chuàng)建一個(gè)代理實(shí)例。
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException
該方法有三個(gè)參數(shù):
loader:為類加載器。
interfaces:為接口的Class
對(duì)象數(shù)組。
h:指定的調(diào)用處理器。
返回值為指定接口的代理類的實(shí)例。
Proxy
類主要用來獲取動(dòng)態(tài)代理對(duì)象,InvocationHandler
接口主要用于方法調(diào)用的約束與增強(qiáng)。
上一章中已經(jīng)介紹了獲取代理實(shí)例的兩個(gè)靜態(tài)方法,現(xiàn)在通過代碼示例來演示具體實(shí)現(xiàn)。
JDK動(dòng)態(tài)代理是基于接口的,我們創(chuàng)建一個(gè)接口及其實(shí)現(xiàn)類。
Foo接口:
public interface Foo { String ping(String name); }
Foo接口的實(shí)現(xiàn)類RealFoo:
public class RealFoo implements Foo { @Override public String ping(String name) { System.out.println("ping"); return "pong"; } }
創(chuàng)建一個(gè)InvocationHandler
接口的實(shí)現(xiàn)類MyInvocationHandler。該類的構(gòu)造方法參數(shù)為要代理的目標(biāo)對(duì)象。
invoke
方法中的三個(gè)參數(shù)上面已經(jīng)介紹過,通過調(diào)用method
的invoke
方法來完成方法的調(diào)用。
這里一時(shí)看不懂沒關(guān)系,后面源碼解析章節(jié)會(huì)進(jìn)行剖析。
public class MyInvocationHandler implements InvocationHandler { // 目標(biāo)對(duì)象 private final Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy - " + proxy.getClass()); System.out.println("method - " + method); System.out.println("args - " + Arrays.toString(args)); return method.invoke(target, args); } }
具體實(shí)現(xiàn)步驟如下:
根據(jù)類加載器和接口數(shù)組獲取代理類的Class對(duì)象
過Class對(duì)象的構(gòu)造器創(chuàng)建一個(gè)實(shí)例(代理類的實(shí)例)
將代理實(shí)例強(qiáng)轉(zhuǎn)成目標(biāo)接口Foo(因?yàn)榇眍悓?shí)現(xiàn)了目標(biāo)接口,所以可以強(qiáng)轉(zhuǎn))。
最后使用代理進(jìn)行方法調(diào)用。
@Test public void test1() throws Exception { Foo foo = new RealFoo(); // 根據(jù)類加載器和接口數(shù)組獲取代理類的Class對(duì)象 Class> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class); // 通過Class對(duì)象的構(gòu)造器創(chuàng)建一個(gè)實(shí)例(代理類的實(shí)例) Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class) .newInstance(new MyInvocationHandler(foo)); // 調(diào)用 ping 方法,并輸出返回值 String value = fooProxy.ping("楊過"); System.out.println(value); }
輸出結(jié)果:
proxy - class com.sun.proxy.$Proxy4 method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String) args - [楊過] ping pong
通過輸出結(jié)果可以看出:
代理類的名稱是以$Proxy
開頭的。
方法實(shí)例為代理類調(diào)用的方法。
參數(shù)為代理類調(diào)用方法時(shí)傳的參數(shù)。
通過這種方法是最簡(jiǎn)單的,也是推薦使用的,通過該方法可以直接獲取代理對(duì)象。
注:其實(shí)該方法后臺(tái)實(shí)現(xiàn)實(shí)際與上面使用getProxyClass方法的過程一樣。
@Test public void test2() { Foo foo = new RealFoo(); // 通過類加載器、接口數(shù)組和調(diào)用處理器,創(chuàng)建代理類的實(shí)例 Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[]{Foo.class}, new MyInvocationHandler(foo)); String value = fooProxy.ping("小龍女"); System.out.println(value); }
其實(shí)InvocationHander
接口也不用創(chuàng)建一個(gè)實(shí)現(xiàn)類,可以使用Lambad表達(dá)式進(jìn)行簡(jiǎn)化的實(shí)現(xiàn),如下代碼:
@Test public void test3() { Foo foo = new RealFoo(); Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[]{Foo.class}, (proxy, method, args) -> method.invoke(foo, args)); String value = fooProxy.ping("雕兄"); System.out.println(value); }
JVM為我們自動(dòng)生成的代理類到底是什么樣子的呢?下面我們先來生成一下,再來看里面的構(gòu)造。
JVM默認(rèn)不創(chuàng)建該.class文件,需要增加一個(gè)啟動(dòng)參數(shù): -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在IDEA中點(diǎn)擊【Edit Configurations...】,打開 Run/Debug Configurations 配置框。
將上面啟動(dòng)參數(shù)加到【VM options】中,點(diǎn)擊【OK】即可。
再次運(yùn)行代碼,會(huì)在項(xiàng)目中的【com.sun.proxy】目錄中找到這個(gè).class文件,我這里是“$Proxy4.class”
在Proxy
類中有個(gè)ProxyClassFactory
靜態(tài)內(nèi)部類,該類主要作用就是生成靜態(tài)代理的。
其中有一段代碼ProxyGenerator.generateProxyClass
用來生成代理類的.class文件。
其中變量saveGeneratedFiles
便是引用了此啟動(dòng)參數(shù)的值。將該啟動(dòng)參數(shù)配置為true
會(huì)生成.class文件。
神秘的面紗即將揭露,前面很多未解之迷在這里可以找到答案!
打開這個(gè)$Proxy
文件,我這里生成的是$Proxy4
,下面是內(nèi)容:
// 該類為final類,其繼承了Proxy類,并實(shí)現(xiàn)了被代理接口Foo public final class $Proxy4 extends Proxy implements Foo { // 這4個(gè)Method實(shí)例,代表了本類實(shí)現(xiàn)的4個(gè)方法 private static Method m1; private static Method m2; private static Method m3; private static Method m0; // 靜態(tài)代碼塊根據(jù)反射獲取這4個(gè)方法的Method實(shí)例 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("io.github.gozhuyinglong.proxy.Foo").getMethod("ping"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } // 一個(gè)公開的構(gòu)造函數(shù),參數(shù)為指定的 InvocationHandler public $Proxy4(InvocationHandler var1) throws { super(var1); } 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); } } // Foo接口的實(shí)現(xiàn)方法,最終調(diào)用了 InvocationHandler 中的 invoke 方法 public final String ping(String var1) throws { try { return (String)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } 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); } } }
通過該文件可以看出:
代理類繼承了Proxy
類,其主要目的是為了傳遞InvocationHandler
。
代理類實(shí)現(xiàn)了被代理的接口Foo,這也是為什么代理類可以直接強(qiáng)轉(zhuǎn)成接口的原因。
有一個(gè)公開的構(gòu)造函數(shù),參數(shù)為指定的InvocationHandler
,并將參數(shù)傳遞到父類Proxy
中。
每一個(gè)實(shí)現(xiàn)的方法,都會(huì)調(diào)用InvocationHandler
中的invoke
方法,并將代理類本身、Method實(shí)例、入?yún)⑷齻€(gè)參數(shù)進(jìn)行傳遞。這也是為什么調(diào)用代理類中的方法時(shí),總會(huì)分派到InvocationHandler
中的invoke
方法的原因。
我們從Proxy
類為我們提供的兩個(gè)靜態(tài)方法開始getProxyClass
和newProxyInstance
。上面已經(jīng)介紹了,這兩個(gè)方法是用來創(chuàng)建代理類及其實(shí)例的,下面來看源碼。
通過上面源碼可以看出,這兩個(gè)方法最終都會(huì)調(diào)用getProxyClass0
方法來生成代理類的Class
對(duì)象。只不過newProxyInstance
方法為我們創(chuàng)建好了代理實(shí)例,而getProxyClass
方法需要我們自己創(chuàng)建代理實(shí)例。
下面來看這個(gè)統(tǒng)一的入口:getProxyClass0
從源碼和注解可以看出:
代理接口的最多不能超過65535個(gè)
會(huì)先從緩存中獲取代理類,則沒有再通過ProxyClassFactory
創(chuàng)建代理類。(代理類會(huì)被緩存一段時(shí)間。)
這里簡(jiǎn)單介紹一下WeakCache
類,該類主要是為代理類進(jìn)行緩存的。獲取代理類時(shí),會(huì)首先從緩存中獲取,若沒有會(huì)調(diào)用ProxyClassFactory
類進(jìn)行創(chuàng)建,創(chuàng)建好后會(huì)進(jìn)行緩存。
ProxyClassFactory
是Proxy
類的一個(gè)靜態(tài)內(nèi)部類,該類用于生成代理類。下圖是源碼的部分內(nèi)容:
代理類的名稱就是在這里定義的,其前綴是$Proxy
,后綴是一個(gè)數(shù)字。
調(diào)用ProxyGenerator.generateProxyClass
來生成指定的代理類。
defineClass0
方法是一個(gè)native
方法,負(fù)責(zé)字節(jié)碼加載的實(shí)現(xiàn),并返回對(duì)應(yīng)的Class
對(duì)象。
為了便于記錄,將代理類的生成過程整理成了一張圖。
到此,相信大家對(duì)“什么是JDK動(dòng)態(tài)代理”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!