1. 程式人生 > >JDK動態代理和cglib代理詳解

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     private
Object 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     }
  • 總結

  總結一下兩則區別:

  1. JDK代理是基於介面的代理,而cglib的代理是建立類的子類,可以代理沒有實現介面的類。可以理解為一個為橫向一個為豎向;
  2. JDK代理是通過反射呼叫方法,依賴被代理物件。cglib通過FastClass機制呼叫,可以不依賴代理物件;
  3. JDK是通過JNI直接生成代理class,而cglib通過ASM來生成代理class

  在cglib的代理中還涉及到了FastClass這個類。這個類的處理現在還沒有搞懂,等下次在總結。總的來說,對這兩種代理的原理有了詳細瞭解,同事也明白了兩種之間的區別。以上來自個人學習總結,不保證全面和完全正確。如有不對之處,請海涵。同時歡迎指正。