JDK動態代理和cglib代理詳解
- JDK動態代理
先做一下簡單的描述,通過代理之後返回的物件已並非原類所new出來的物件,而是代理物件。JDK的動態代理是基於介面的,也就是說,被代理類必須實現一個或多個介面。主要原因是JDK的代理原理是建立一個與被代理類同等級別(具有同樣的繼承或實現體系)的類,這裡稱之為代理類。那麼該代理類就具備了被代理類同樣的方法,這裡同樣的方法指的是介面中的方法,由被代理類自己定義的方法將不會被代理。那麼問題來了,被代理類中對介面方法的實現又如何被代理類知曉呢?因為在建立代理類的時候還繼承了Proxy類。該類中有一個InvocationHandler屬性,該屬性會持有被代理類的物件,由此,相當於代理物件就持有了被代理物件的引用。因此在呼叫方法時,會呼叫代理物件的方法,然後通過InvocationHandler的invoke方法反射呼叫該代理物件持有的被代理物件的方法。上程式碼!!!
1 interface People { 2 3 public void sayHi(); 4 } 5 class ChinesePeople implements People { 6 7 8 public ChinesePeople(){} 9 10 public void sayHi() { 11 System.out.println("你好!"); 12 } 13 14 } 15 class ProxyFactory implements InvocationHandler { 16 17 privateObject targetObject; 18 19 public Object createTargetObject(Object targetObject){ 20 this.targetObject = targetObject; 21 22 return Proxy.newProxyInstance(this.targetObject.getClass() 23 .getClassLoader(), 24 this.targetObject.getClass().getInterfaces(), this); 25 } 26 27 public Object invoke(Object arg0, Method method, Object[] args) 28 throws Throwable { 29 Object result = null; 30 result = method.invoke(targetObject, args); 31 return result; 32 } 33 34 } 35 public class Tests { 36 37 public static void main(String[] args){ 38 ProxyFactory pf = new ProxyFactory(); 39 People p = (People)pf.createTargetObject(new ChinesePeople()); 40 p.sayHi(); 41 } 42 }
以上就是動態代理的一個簡單實現,主要是在15行以後比較重要。createTargetObject方法的引數就是被代理的物件。Proxy.newProxyInstance方法就是通過被代理物件來建立代理物件。在這裡debug會發現返回物件的結構和被代理物件的結構不同,當然對應的引用自然不一樣。然後在到39行處,此處接收物件時用的介面,並使用了強轉型。這就說明了代理類和介面之間的關係。而如果將這行程式碼修改為用被代理類來接收(ChinesePeople p = (ChinesePeople)pf.createTargetObject(new ChinesePeople());)執行時會丟擲型別轉換異常。這就解釋了生成的代理類和被代理類關係(同等級別)。也解釋了為什麼JDK代理基於介面了。
然後,大家就知道,在ProxyFactory中就可以對被代理方法做一些處理了。比如:
1 public Object invoke(Object arg0, Method method, Object[] args) 2 throws Throwable { 3 System.out.print("xxx:"); 4 Object result = null; 5 result = method.invoke(targetObject, args); 6 System.out.print(",This is proxy"); 7 return result; 8 }
當然,還可以做其他的很多的操作比如對Object的方法不做任何處理,等等。至於如何生成代理類的class,可以根據Proxy.newProxyInstance()詳細去追一下原始碼,下面貼一個代理類的片段:
public final class $Proxy0 extends Proxy implements People { //變數,都是private static Method XXX private static Method m3; private static Method m1; private static Method m0; private static Method m2; //代理類的建構函式,引數是InvocationHandler例項, // Proxy.newInstance方法就是通過這個建構函式來建立代理例項的 public $Proxy0(InvocationHandler var1) throws Exception{ super(var1); } //介面代理方法,在這裡InvocationHandler.invoke()來實現對方法的呼叫 public final void sayHi(){ try { super.h.invoke(this, m3, (Object[]) null); } catch (RuntimeException var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } }
- cglib代理
cglib代理可以對沒有介面的類進行代理,它的原理是生成一個被代理類的子類,以代理該類所有的方法。但是,不能對final類以及final方法進行代理。下面看看程式碼
1 public class Test { 2 3 public static void main(String[] args){ 4 5 new Test().testProxy(); 6 } 7 8 public void testProxy() { 9 Enhancer en = new Enhancer(); //建立CGLIB增強類 10 en.setSuperclass(Dog.class); 11 en.setCallback(new MethodInterceptor() { 12 public Object intercept(Object target, Method method, 13 Object[] args, MethodProxy proxy) throws Throwable { 14 System.out.println("before proxy"); 15 Object o = proxy.invokeSuper(target,args); 16 System.out.println("after proxy"); 17 return o; 18 } 19 }); 20 Dog dogProxy = (Dog)en.create(); 21 22 dogProxy.eat(); 23 } 24 25 } 26 27 class Dog{ 28 29 public void eat() { 30 System.out.println(this.getClass()); 31 System.out.println("eating"); 32 } 33 }
以上就實現了對沒有實現介面的類的代理,並沒有通過反射機制來呼叫,並且完全是通過代理類來呼叫方法。控制檯列印結果:
before proxy
class test.Dog$$EnhancerByCGLIB$$19bdc068
eating
after proxy
在cglib中同樣可以實現反射呼叫和對實現介面類的代理,這種情況下都必須持有被代理的物件引用,首先先看看反射實現
1 public class Test { 2 3 public static void main(String[] args){ 4 5 new Test().testProxy2(); 6 } 7 8 public void testProxy2() { 9 Dog dog = new Dog(); //建立被代理物件 10 Enhancer en = new Enhancer(); //建立CGLIB增強類 11 en.setSuperclass(Dog.class); 12 en.setCallback(new MethodInterceptor() { 13 public Object intercept(Object target, Method method, 14 Object[] args, MethodProxy proxy) throws Throwable { 15 System.out.println("before proxy"); 16 Object o = method.invoke(dog, args); 17 System.out.println("after proxy"); 18 return o; 19 } 20 }); 21 Dog dogProxy = (Dog)en.create(); 22 23 dogProxy.eat(); 24 } 25 26 } 27 28 class Dog{ 29 30 public void eat() { 31 System.out.println("eating"); 32 } 33 }
看看列印結果:method.invoke(dog,args)也可以用proxy.invoke(dog,args);
before proxy
class test.Dog
eating
after proxy
下面再看看實現介面後的情況,如果suppserClass是被代理類的父介面或父類的話,則物件必須要用介面或父類來接收,否則會報錯。
public class Test { public static void main(String[] args){ new Test().testProxy(); } public void testProxy() { Dog dog = new Dog(); Enhancer en = new Enhancer(); //建立CGLIB增強類 en.setSuperclass(Animal.class); en.setCallback(new MethodInterceptor() { public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before proxy"); // Object o = method.invoke(dog,args); Object o = proxy.invoke(dog,args); System.out.println("after proxy"); return o; } }); // Dog dogProxy = (Dog)en.create();//java.lang.ClassCastException Animal dogProxy = (Animal)en.create(); dogProxy.eat(); } } class Dog implements Animal{ public void eat() { System.out.println(this.getClass()); System.out.println("eating"); } } interface Animal{ public void eat(); }
看看列印結果:
before proxy
class test.Dog
eating
after proxy
下面也看看cglib生成的class檔案的片段。如果methodProxy.invoke()方法的引數是代理物件,則會出現死迴圈,所以要正常使用invoke()方法,這必須依賴被代理物件。不管是invoke方法還是invokeSuper,都與FastClass有關。
1 //methodProxy.invokeSuper會呼叫 2 final void CGLIB$sayHi$0() { 3 super.sayHi(); 4 } 5 //methodProxy.invoke會呼叫 6 public final void sayHi() { 7 MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; 8 if(this.CGLIB$CALLBACK_0 == null) { 9 CGLIB$BIND_CALLBACKS(this); 10 var10000 = this.CGLIB$CALLBACK_0; 11 } 12 13 if(var10000 != null) { 14 //呼叫自己實現的攔截器 15 var10000.intercept(this, CGLIB$sayHi$0$Method, CGLIB$emptyArgs, CGLIB$sayHi$0$Proxy); 16 } else { 17 super.sayHi(); 18 } 19 }
- 總結
總結一下兩則區別:
- JDK代理是基於介面的代理,而cglib的代理是建立類的子類,可以代理沒有實現介面的類。可以理解為一個為橫向一個為豎向;
- JDK代理是通過反射呼叫方法,依賴被代理物件。cglib通過FastClass機制呼叫,可以不依賴代理物件;
- JDK是通過JNI直接生成代理class,而cglib通過ASM來生成代理class
在cglib的代理中還涉及到了FastClass這個類。這個類的處理現在還沒有搞懂,等下次在總結。總的來說,對這兩種代理的原理有了詳細瞭解,同事也明白了兩種之間的區別。以上來自個人學習總結,不保證全面和完全正確。如有不對之處,請海涵。同時歡迎指正。