本篇內容主要講解“什么是JDK動態代理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“什么是JDK動態代理”吧!
林芝網站建設公司創新互聯公司,林芝網站設計制作,有大型網站制作公司豐富經驗。已為林芝上千多家提供企業網站建設服務。企業網站搭建\外貿營銷網站建設要多少錢,請找那個售后服務好的林芝做網站的公司定做!
JDK動態代理是指:代理類實例在程序運行時,由JVM根據反射機制動態的生成。也就是說代理類不是用戶自己定義的,而是由JVM生成的。
由于其原理是通過Java反射機制實現的,所以在學習前,要對反射機制有一定的了解。傳送門:Java反射機制:跟著代碼學反射
下面是本篇講述內容:
JDK動態代理有兩大核心類,它們都在Java的反射包下(java.lang.reflect
),分別為InvocationHandler
接口和Proxy
類。
代理實例的調用處理器需要實現
InvocationHandler
接口,并且每個代理實例都有一個關聯的調用處理器。當一個方法在代理實例上被調用時,這個方法調用將被編碼并分派到其調用處理器的invoke
方法上。
也就是說,我們創建的每一個代理實例都要有一個關聯的InvocationHandler
,并且在調用代理實例的方法時,會被轉到InvocationHandler
的invoke
方法上。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
該invoke
方法的作用是:處理代理實例上的方法調用并返回結果。
其有三個參數,分別為:
proxy:是調用該方法的代理實例。
method:是在代理實例上調用的接口方法對應的Method
實例。
args:一個Object
數組,是在代理實例上的方法調用中傳遞的參數值。如果接口方法為無參,則該值為null。
其返回值為:調用代理實例上的方法的返回值。
Proxy
類提供了創建動態代理類及其實例的靜態方法,該類也是動態代理類的超類。
代理類具有以下屬性:
代理類的名稱以 “$Proxy” 開頭,后面跟著一個數字序號。
代理類繼承了Proxy
類。
代理類實現了創建時指定的接口(JDK動態代理是面向接口的)。
每個代理類都有一個公共構造函數,它接受一個參數,即接口InvocationHandler
的實現,用于設置代理實例的調用處理器。
Proxy
提供了兩個靜態方法,用于獲取代理對象。
用于獲取代理類的Class
對象,再通過調用構造函數創建代理實例。
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException
該方法有兩個參數:
loader:為類加載器。
intefaces:為接口的Class
對象數組。
返回值為動態代理類的Class
對象。
用于創建一個代理實例。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
該方法有三個參數:
loader:為類加載器。
interfaces:為接口的Class
對象數組。
h:指定的調用處理器。
返回值為指定接口的代理類的實例。
Proxy
類主要用來獲取動態代理對象,InvocationHandler
接口主要用于方法調用的約束與增強。
上一章中已經介紹了獲取代理實例的兩個靜態方法,現在通過代碼示例來演示具體實現。
JDK動態代理是基于接口的,我們創建一個接口及其實現類。
Foo接口:
public interface Foo { String ping(String name); }
Foo接口的實現類RealFoo:
public class RealFoo implements Foo { @Override public String ping(String name) { System.out.println("ping"); return "pong"; } }
創建一個InvocationHandler
接口的實現類MyInvocationHandler。該類的構造方法參數為要代理的目標對象。
invoke
方法中的三個參數上面已經介紹過,通過調用method
的invoke
方法來完成方法的調用。
這里一時看不懂沒關系,后面源碼解析章節會進行剖析。
public class MyInvocationHandler implements InvocationHandler { // 目標對象 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); } }
具體實現步驟如下:
根據類加載器和接口數組獲取代理類的Class對象
過Class對象的構造器創建一個實例(代理類的實例)
將代理實例強轉成目標接口Foo(因為代理類實現了目標接口,所以可以強轉)。
最后使用代理進行方法調用。
@Test public void test1() throws Exception { Foo foo = new RealFoo(); // 根據類加載器和接口數組獲取代理類的Class對象 Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class); // 通過Class對象的構造器創建一個實例(代理類的實例) Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class) .newInstance(new MyInvocationHandler(foo)); // 調用 ping 方法,并輸出返回值 String value = fooProxy.ping("楊過"); System.out.println(value); }
輸出結果:
proxy - class com.sun.proxy.$Proxy4 method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String) args - [楊過] ping pong
通過輸出結果可以看出:
代理類的名稱是以$Proxy
開頭的。
方法實例為代理類調用的方法。
參數為代理類調用方法時傳的參數。
通過這種方法是最簡單的,也是推薦使用的,通過該方法可以直接獲取代理對象。
注:其實該方法后臺實現實際與上面使用getProxyClass方法的過程一樣。
@Test public void test2() { Foo foo = new RealFoo(); // 通過類加載器、接口數組和調用處理器,創建代理類的實例 Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[]{Foo.class}, new MyInvocationHandler(foo)); String value = fooProxy.ping("小龍女"); System.out.println(value); }
其實InvocationHander
接口也不用創建一個實現類,可以使用Lambad表達式進行簡化的實現,如下代碼:
@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為我們自動生成的代理類到底是什么樣子的呢?下面我們先來生成一下,再來看里面的構造。
JVM默認不創建該.class文件,需要增加一個啟動參數: -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在IDEA中點擊【Edit Configurations...】,打開 Run/Debug Configurations 配置框。
將上面啟動參數加到【VM options】中,點擊【OK】即可。
再次運行代碼,會在項目中的【com.sun.proxy】目錄中找到這個.class文件,我這里是“$Proxy4.class”
在Proxy
類中有個ProxyClassFactory
靜態內部類,該類主要作用就是生成靜態代理的。
其中有一段代碼ProxyGenerator.generateProxyClass
用來生成代理類的.class文件。
其中變量saveGeneratedFiles
便是引用了此啟動參數的值。將該啟動參數配置為true
會生成.class文件。
神秘的面紗即將揭露,前面很多未解之迷在這里可以找到答案!
打開這個$Proxy
文件,我這里生成的是$Proxy4
,下面是內容:
// 該類為final類,其繼承了Proxy類,并實現了被代理接口Foo public final class $Proxy4 extends Proxy implements Foo { // 這4個Method實例,代表了本類實現的4個方法 private static Method m1; private static Method m2; private static Method m3; private static Method m0; // 靜態代碼塊根據反射獲取這4個方法的Method實例 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()); } } // 一個公開的構造函數,參數為指定的 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接口的實現方法,最終調用了 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
。
代理類實現了被代理的接口Foo,這也是為什么代理類可以直接強轉成接口的原因。
有一個公開的構造函數,參數為指定的InvocationHandler
,并將參數傳遞到父類Proxy
中。
每一個實現的方法,都會調用InvocationHandler
中的invoke
方法,并將代理類本身、Method實例、入參三個參數進行傳遞。這也是為什么調用代理類中的方法時,總會分派到InvocationHandler
中的invoke
方法的原因。
我們從Proxy
類為我們提供的兩個靜態方法開始getProxyClass
和newProxyInstance
。上面已經介紹了,這兩個方法是用來創建代理類及其實例的,下面來看源碼。
通過上面源碼可以看出,這兩個方法最終都會調用getProxyClass0
方法來生成代理類的Class
對象。只不過newProxyInstance
方法為我們創建好了代理實例,而getProxyClass
方法需要我們自己創建代理實例。
下面來看這個統一的入口:getProxyClass0
從源碼和注解可以看出:
代理接口的最多不能超過65535個
會先從緩存中獲取代理類,則沒有再通過ProxyClassFactory
創建代理類。(代理類會被緩存一段時間。)
這里簡單介紹一下WeakCache<K, P, V>
類,該類主要是為代理類進行緩存的。獲取代理類時,會首先從緩存中獲取,若沒有會調用ProxyClassFactory
類進行創建,創建好后會進行緩存。
ProxyClassFactory
是Proxy
類的一個靜態內部類,該類用于生成代理類。下圖是源碼的部分內容:
代理類的名稱就是在這里定義的,其前綴是$Proxy
,后綴是一個數字。
調用ProxyGenerator.generateProxyClass
來生成指定的代理類。
defineClass0
方法是一個native
方法,負責字節碼加載的實現,并返回對應的Class
對象。
為了便于記錄,將代理類的生成過程整理成了一張圖。
到此,相信大家對“什么是JDK動態代理”有了更深的了解,不妨來實際操作一番吧!這里是創新互聯網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
當前名稱:什么是JDK動態代理
URL標題:http://newbst.com/article40/jhcgho.html
成都網站建設公司_創新互聯,為您提供定制網站、域名注冊、企業建站、虛擬主機、品牌網站制作、網站改版
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯