?????之前介紹的反射和注解都是Java中的動態(tài)特性,還有即將介紹的動態(tài)代理也是Java中的一個動態(tài)特性排监。這些動態(tài)特性使得我們的程序很靈活。動態(tài)代理是面向AOP編程的基礎践樱。通過動態(tài)代理翰灾,我們可以在運行時動態(tài)創(chuàng)建一個類缕粹,實現(xiàn)某些接口中的方法,目前為止該特性已被廣泛應用于各種框架和類庫中纸淮,例如:Spring平斩,Hibernate,MyBatis等萎馅。理解動態(tài)代理是理解框架底層的基礎双戳。
?????主要內(nèi)容如下:
- 理解代理是何意
- Java SDK實現(xiàn)動態(tài)代理
- 第三方庫cglib實現(xiàn)動態(tài)代理
一、代理的概念
?????單從字面上理解糜芳,代理就是指原對象的委托人飒货,它不是原對象但是卻有原對象的權限。Java中的代理意思類似峭竣,就是指通過代理來操作原對象的方法和屬性塘辅,而原對象不直接出現(xiàn)。這樣做有幾點好處:
- 節(jié)省創(chuàng)建原對象的高開銷皆撩,創(chuàng)建一個代理并不會立馬創(chuàng)建一個實際原對象扣墩,而是保存一個原對象的地址,按需加載
- 執(zhí)行權限檢查扛吞,保護原對象
實際上代理堵在了原對象的前面呻惕,在代理的內(nèi)部往往還是調(diào)用了原對象的方法,只是它還做了其他的一些操作滥比。下面看第一種實現(xiàn)動態(tài)代理的方式亚脆。
二、Java SDK實現(xiàn)動態(tài)代理
?????實現(xiàn)動態(tài)代理主要有如下幾個步驟:
- 實現(xiàn) InvocationHandler接口盲泛,完成自定義調(diào)用處理器
- 通過Proxy的getProxyClass方法獲取對應的代理類
- 利用反射技術獲取該代理類的constructor構造器
- 利用constructor構造代理實例對象
在一步步解析源碼之前,我們先通過一個完整的實例了解下,整個程序的一步步邏輯走向枫振。
//定義了一個調(diào)用處理器
public class MyInvotion implements InvocationHandler {
private Object realObj;
public MyInvotion(Object obj){
this.realObj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//通過代理執(zhí)行原對象的方法
return method.invoke(realObj,args);
}
}
//定義一個接口
public interface MyInterface {
public void sayHello();
}
//該接口的一個實現(xiàn)類,該類就是我們的原對象
public class ClassA implements MyInterface {
public void sayHello(){
System.out.println("hello walker");
}
}
//main 函數(shù)
public static void main(String[] args){
ClassA a = new ClassA();
MyInvotion myInvotion = new MyInvotion(a);
Class myProxy = Proxy.getProxyClass(ClassA.class.getClassLoader(), new Class[]{MyInterface.class});
Constructor constructor =myProxy.getConstructor(new Class[]{InvocationHandler.class});
MyInterface m = (MyInterface)constructor.newInstance(myInvotion);
m.sayHello();
}
輸出結果:hello walker
簡單說下整體的運行過程屈雄,首先我們創(chuàng)建ClassA 實例并將它傳入自定義的調(diào)用處理器MyInvotion,在MyInvotion中用realObj接受該參數(shù)代表原對象官套。接著調(diào)用Proxy的getProxyClass方法酒奶,將ClassA 的類加載器和ClassA 的實現(xiàn)的接口集合傳入,該方法內(nèi)部會實現(xiàn)所有接口返回該類的代理類虏杰,然后我們利用反射獲取代理類的構造器并創(chuàng)建實例讥蟆。
以上便是整個程序運行的大致流程,接下來我們從源代碼的角度看看具體是如何實現(xiàn)的纺阔。首先我們看InvocationHandler接口瘸彤,這是我們的調(diào)用處理器,在代理類中訪問的所有的方法都會被轉發(fā)到這執(zhí)行笛钝,具體的等我們看了代理類源碼及理解了质况。該接口中唯一的方法是:
public Object invoke(Object proxy, Method method, Object[] args)
- 參數(shù)Proxy表示動態(tài)生成的代理類的對象,基本沒啥用
- 參數(shù)method表示當前正在被調(diào)用的方法
- 數(shù)組args指定了該方法的參數(shù)集合
我們上例中對該接口的實現(xiàn)情況玻靡,定義了一個realObj用于保存原對象的引用结榄。重寫的invoke方法中調(diào)用了原對象realObj的method方法,具體誰來調(diào)用該方法以及傳入的參數(shù)是什么囤捻,在看完代理類源碼即可知曉臼朗。
接下來我們看看最核心的內(nèi)容,如何動態(tài)創(chuàng)建代理類蝎土。這是getProxyClass方法的源碼:
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
return getProxyClass0(loader, intfs);
}
首先獲取了該類實現(xiàn)的所有的接口的集合视哑,然后判斷創(chuàng)建該代理是否具有安全性問題,檢查接口類對象是否對類裝載器可見等誊涯。然后調(diào)用另外一個getProxyClass0方法挡毅,我們跟進去:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
判斷如果該類的接口超過65535(想必還沒有那么牛的類),拋出異常暴构。在我們的Proxy類中有個屬性proxyClassCache跪呈,這是一個WeakCache類型的靜態(tài)變量。它指示了我們的類加載器和代理類之間的映射取逾。所以proxyClassCache的get方法用于根據(jù)類加載器來獲取Proxy類耗绿,如果已經(jīng)存在則直接從cache中返回,如果沒有則創(chuàng)建一個映射并更新cache表砾隅。具體創(chuàng)建一個Proxy類并存入cache表中的代碼限于能力缭乘,未能參透。
至此我們就獲取到了該ClassA類對應的代理類型,接著我們通過該類的getConstructor方法獲取該代理類的構造器堕绩,并傳入InvocationHandler.class作為參數(shù),至于為何要傳入該類型作為參數(shù)邑时,等會看代理類源碼變一目了然了奴紧。
最后newInstance創(chuàng)建該代理類的實例,實現(xiàn)對ClassA對象的代理晶丘。
可能看完上述的介紹黍氮,你還會有點暈。下面我們通過查看動態(tài)生成的代理類的源碼來加深理解浅浮。上述getProxyClass方法會動態(tài)創(chuàng)建一個代理類并返回他的Class類型沫浆,這個代理類一般被命名為$ProxyN,這個N是遞增的用于標記不同的代理類滚秩。我們可以利用反編譯工具反編譯該class:
final class $Proxy0 extends Proxy implements MyInterface {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {
return ((Boolean) this.h.invoke(this, m1,
new Object[] { paramObject })).booleanValue();
}
public final void sayHello() {
this.h.invoke(this, m3, null);
}
public final String toString() {
return (String) this.h.invoke(this, m2, null);
}
public final int hashCode() {
return ((Integer) this.h.invoke(this, m0, null)).intValue();
}
static {
m1 = Class.forName("java.lang.Object").getMethod("equals",
new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("laoma.demo.proxy.SimpleJDKDynamicProxyDemo$IService")
.getMethod("sayHello",new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
}
這就是上述的ClassA的動態(tài)代理類专执,我們看到該類的構造方法傳入?yún)?shù)InvocationHandler類型,并調(diào)用了父類Proxy的構造方法保存了這個InvocationHandler實例郁油,這也解釋了我們?yōu)槭裁丛讷@取構造器的時候需要指定參數(shù)類型為InvocationHandler本股,就是因為動態(tài)代理類只有一個構造器并且參數(shù)類型為InvocationHandler。
接著我們看其中的方法桐腌,貌似只有一個sayHello是我們知道的拄显,別的方法哪來的?我們說過在動態(tài)創(chuàng)建代理類的時候案站,會實現(xiàn)原對象的所有接口躬审。所以sayHello方法是實現(xiàn)的MyInterface。而其余的四個方法是代理類由于比較常用蟆盐,被默認添加到其中承边。而這些方法的內(nèi)部都是調(diào)用的this.h.invoke這個方法,this.h就是保存在父類Proxy中的InvocationHandler實例(我們用構造器向其中保存的)舱禽,調(diào)用了這個類的invoke方法炒刁,在我們自定義的InvocationHandler實例中重寫了invoke方法,我們寫的比較簡單誊稚,直接執(zhí)行傳入的method翔始。
也就是我們調(diào)用代理類的任何一個方法都會轉發(fā)到該InvocationHandler實例中的involve中,因為該實例中保存有我們的原對象里伯,所以我們可以選擇直接調(diào)取原對象中的方法作為回調(diào)城瞎。
以上便是有關Java SDK中動態(tài)代理的相關內(nèi)容,稍微總結下疾瓮,首先我們通過實現(xiàn)InvocationHandler自定義一個調(diào)用處理類脖镀,該類中會保存我們的原對象,并提供一個invoke方法供代理類使用狼电。然后我們通過getProxyClass方法動態(tài)創(chuàng)建代理類蜒灰,最后用反射獲取代理類的實例對象弦蹂。
需要注意的是:以上我們使用的四步創(chuàng)建代理實例時最根本的,其實Proxy中提供一個方法可以封裝2到4步的操作强窖。上述代碼也可以這么寫:
ClassA a = new ClassA();
MyInterface aProxy = (MyInterface)Proxy.newProxyInstance(ClassA.class.getClassLoader(),new Class<?>[]{MyInterface.class},new MyInvotion(a));
aProxy.sayHello();
我們打開該方法的內(nèi)部源碼凸椿,其實走的還是我們上述的過程,它就是做了封裝翅溺。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
{
Objects.requireNonNull(h);
//獲取所有接口
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//創(chuàng)建動態(tài)代理類
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
.............
..............
}
二脑漫、第三方庫cglib實現(xiàn)動態(tài)代理
?????使用動態(tài)代理,我們編寫通用的代碼邏輯咙崎,即僅實現(xiàn)一個InvocationHandler實例完成對多個類型的代理优幸。但是我們從動態(tài)生成的代理類的源碼可以看到,所有的代理類都繼承自Proxy這個類褪猛,這就導致我們這種方式不能代理類网杆,只能代理接口。因為java中是單繼承的握爷。也就是說跛璧,給我們一個類型,我們只能動態(tài)實現(xiàn)該類所有的接口類型新啼,但是該類繼承的別的類我們在代理類中是不能使用的追城,因為它沒有被代理類繼承。下面看個例子:
public class ClassB {
public void welcome(){
System.out.println("welcom walker");
}
}
public interface MyInterface {
public void sayHello();
}
//需要被代理的原類型燥撞,繼承了ClassB和接口MyInterface
public class ClassA extends ClassB implements MyInterface {
public void sayHello(){
System.out.println("hello walker");
}
}
//InvocationHandler 實例
public class MyInvotion implements InvocationHandler {
private Object realObj;
public MyInvotion(Object obj){
this.realObj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
return method.invoke(realObj,args);
}
}
我們反編譯該代理類座柱,和上述的源碼是一樣的,此處不再重復貼出物舒。我們能從中看出來的是色洞,我們的代理只會實現(xiàn)原類型中所有的接口,至于原類型所繼承的類冠胯,在生成Proxy代理類的時候會丟棄火诸,因為所有的代理類必須繼承Proxy類,這就導致原類型的父類中的方法 在代理類中丟失荠察。這是該種方式的一大弊端置蜀。下面我們看看另一種方式實現(xiàn)動態(tài)代理,該種方式完美解決了這種不足悉盆。
限于篇幅盯荤,我們下篇介紹cglib實現(xiàn)動態(tài)代理機制的內(nèi)容,本篇暫時結束焕盟,總結的不好秋秤,望海涵。