?????之前介紹的反射和注解都是Java中的動態特性,還有即將介紹的動態代理也是Java中的一個動態特性。這些動態特性使得我們的程序很靈活。動態代理是面向AOP編程的基礎。通過動態代理,我們可以在運行時動態創建一個類,實現某些接口中的方法,目前為止該特性已被廣泛應用于各種框架和類庫中,例如:Spring,Hibernate,MyBatis等。理解動態代理是理解框架底層的基礎。
?????主要內容如下:
- 理解代理是何意
- Java SDK實現動態代理
- 第三方庫cglib實現動態代理
一、代理的概念
?????單從字面上理解,代理就是指原對象的委托人,它不是原對象但是卻有原對象的權限。Java中的代理意思類似,就是指通過代理來操作原對象的方法和屬性,而原對象不直接出現。這樣做有幾點好處:
- 節省創建原對象的高開銷,創建一個代理并不會立馬創建一個實際原對象,而是保存一個原對象的地址,按需加載
- 執行權限檢查,保護原對象
實際上代理堵在了原對象的前面,在代理的內部往往還是調用了原對象的方法,只是它還做了其他的一些操作。下面看第一種實現動態代理的方式。
二、Java SDK實現動態代理
?????實現動態代理主要有如下幾個步驟:
- 實現 InvocationHandler接口,完成自定義調用處理器
- 通過Proxy的getProxyClass方法獲取對應的代理類
- 利用反射技術獲取該代理類的constructor構造器
- 利用constructor構造代理實例對象
在一步步解析源碼之前,我們先通過一個完整的實例了解下,整個程序的一步步邏輯走向。
//定義了一個調用處理器
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);
}
}
//定義一個接口
public interface MyInterface {
public void sayHello();
}
//該接口的一個實現類,該類就是我們的原對象
public class ClassA implements MyInterface {
public void sayHello(){
System.out.println("hello walker");
}
}
//main 函數
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
簡單說下整體的運行過程,首先我們創建ClassA 實例并將它傳入自定義的調用處理器MyInvotion,在MyInvotion中用realObj接受該參數代表原對象。接著調用Proxy的getProxyClass方法,將ClassA 的類加載器和ClassA 的實現的接口集合傳入,該方法內部會實現所有接口返回該類的代理類,然后我們利用反射獲取代理類的構造器并創建實例。
以上便是整個程序運行的大致流程,接下來我們從源代碼的角度看看具體是如何實現的。首先我們看InvocationHandler接口,這是我們的調用處理器,在代理類中訪問的所有的方法都會被轉發到這執行,具體的等我們看了代理類源碼及理解了。該接口中唯一的方法是:
public Object invoke(Object proxy, Method method, Object[] args)
- 參數Proxy表示動態生成的代理類的對象,基本沒啥用
- 參數method表示當前正在被調用的方法
- 數組args指定了該方法的參數集合
我們上例中對該接口的實現情況,定義了一個realObj用于保存原對象的引用。重寫的invoke方法中調用了原對象realObj的method方法,具體誰來調用該方法以及傳入的參數是什么,在看完代理類源碼即可知曉。
接下來我們看看最核心的內容,如何動態創建代理類。這是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);
}
首先獲取了該類實現的所有的接口的集合,然后判斷創建該代理是否具有安全性問題,檢查接口類對象是否對類裝載器可見等。然后調用另外一個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類型的靜態變量。它指示了我們的類加載器和代理類之間的映射。所以proxyClassCache的get方法用于根據類加載器來獲取Proxy類,如果已經存在則直接從cache中返回,如果沒有則創建一個映射并更新cache表。具體創建一個Proxy類并存入cache表中的代碼限于能力,未能參透。
至此我們就獲取到了該ClassA類對應的代理類型,接著我們通過該類的getConstructor方法獲取該代理類的構造器,并傳入InvocationHandler.class作為參數,至于為何要傳入該類型作為參數,等會看代理類源碼變一目了然了。
最后newInstance創建該代理類的實例,實現對ClassA對象的代理。
可能看完上述的介紹,你還會有點暈。下面我們通過查看動態生成的代理類的源碼來加深理解。上述getProxyClass方法會動態創建一個代理類并返回他的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的動態代理類,我們看到該類的構造方法傳入參數InvocationHandler類型,并調用了父類Proxy的構造方法保存了這個InvocationHandler實例,這也解釋了我們為什么在獲取構造器的時候需要指定參數類型為InvocationHandler,就是因為動態代理類只有一個構造器并且參數類型為InvocationHandler。
接著我們看其中的方法,貌似只有一個sayHello是我們知道的,別的方法哪來的?我們說過在動態創建代理類的時候,會實現原對象的所有接口。所以sayHello方法是實現的MyInterface。而其余的四個方法是代理類由于比較常用,被默認添加到其中。而這些方法的內部都是調用的this.h.invoke這個方法,this.h就是保存在父類Proxy中的InvocationHandler實例(我們用構造器向其中保存的),調用了這個類的invoke方法,在我們自定義的InvocationHandler實例中重寫了invoke方法,我們寫的比較簡單,直接執行傳入的method。
也就是我們調用代理類的任何一個方法都會轉發到該InvocationHandler實例中的involve中,因為該實例中保存有我們的原對象,所以我們可以選擇直接調取原對象中的方法作為回調。
以上便是有關Java SDK中動態代理的相關內容,稍微總結下,首先我們通過實現InvocationHandler自定義一個調用處理類,該類中會保存我們的原對象,并提供一個invoke方法供代理類使用。然后我們通過getProxyClass方法動態創建代理類,最后用反射獲取代理類的實例對象。
需要注意的是:以上我們使用的四步創建代理實例時最根本的,其實Proxy中提供一個方法可以封裝2到4步的操作。上述代碼也可以這么寫:
ClassA a = new ClassA();
MyInterface aProxy = (MyInterface)Proxy.newProxyInstance(ClassA.class.getClassLoader(),new Class<?>[]{MyInterface.class},new MyInvotion(a));
aProxy.sayHello();
我們打開該方法的內部源碼,其實走的還是我們上述的過程,它就是做了封裝。
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);
}
//創建動態代理類
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實現動態代理
?????使用動態代理,我們編寫通用的代碼邏輯,即僅實現一個InvocationHandler實例完成對多個類型的代理。但是我們從動態生成的代理類的源碼可以看到,所有的代理類都繼承自Proxy這個類,這就導致我們這種方式不能代理類,只能代理接口。因為java中是單繼承的。也就是說,給我們一個類型,我們只能動態實現該類所有的接口類型,但是該類繼承的別的類我們在代理類中是不能使用的,因為它沒有被代理類繼承。下面看個例子:
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);
}
}
我們反編譯該代理類,和上述的源碼是一樣的,此處不再重復貼出。我們能從中看出來的是,我們的代理只會實現原類型中所有的接口,至于原類型所繼承的類,在生成Proxy代理類的時候會丟棄,因為所有的代理類必須繼承Proxy類,這就導致原類型的父類中的方法 在代理類中丟失。這是該種方式的一大弊端。下面我們看看另一種方式實現動態代理,該種方式完美解決了這種不足。
限于篇幅,我們下篇介紹cglib實現動態代理機制的內容,本篇暫時結束,總結的不好,望海涵。