1. 程式人生 > >cglib動態代理原始碼分析

cglib動態代理原始碼分析

本文分下面三個部分來分析cglib動態代理的原理。

 一、cglib 動態代理示例  

複製程式碼
 1 public class Target{
 2     public void f(){
 3         System.out.println("Target f()");
 4     }
 5     public void g(){
 6         System.out.println("Target g()");
 7     }
 8 }
 9 
10 public class Interceptor implements MethodInterceptor {
11     @Override
12 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 13 System.out.println("I am intercept begin"); 14 //Note: 此處一定要使用proxy的invokeSuper方法來呼叫目標類的方法 15 proxy.invokeSuper(obj, args); 16 System.out.println("I am intercept end");
17 return null; 18 } 19 } 20 21 public class Test { 22 public static void main(String[] args) { 23 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code"); 24 //例項化一個增強器,也就是cglib中的一個class generator 25 Enhancer eh = new Enhancer(); 26 //設定目標類
27 eh.setSuperclass(Target.class); 28 // 設定攔截物件 29 eh.setCallback(new Interceptor()); 30 // 生成代理類並返回一個例項 31 Target t = (Target) eh.create(); 32 t.f(); 33 t.g(); 34 } 35 }
複製程式碼

執行結果為:

I am intercept begin
Target f()
I am intercept end
I am intercept begin
Target g()
I am intercept end

與JDK動態代理相比,cglib可以實現對一般類的代理而無需實現介面。在上例中通過下列步驟來生成目標類Target的代理類:

  1. 建立Enhancer例項
  2. 通過setSuperclass方法來設定目標類
  3. 通過setCallback 方法來設定攔截物件
  4. create方法生成Target的代理類,並返回代理類的例項

二、代理類分析

      在示例程式碼中我們通過設定DebuggingClassWriter.DEBUG_LOCATION_PROPERTY的屬性值來獲取cglib生成的代理類。通過之前分析的命名規則我們可以很容易的在F:\\code下面找到生成的代理類 Target$$EnhancerByCGLIB$$788444a0.class 。

使用jd-gui進行反編譯(由於版本的問題,此處只能顯示部分程式碼,可以結合javap的反編譯結果來進行分析),由於cglib會代理Object中的finalize,equals, toString,hashCode,clone方法,為了清晰的展示代理類我們省略這部分程式碼,反編譯的結果如下:

複製程式碼
 1 public class Target$$EnhancerByCGLIB$$788444a0 extends Target implements Factory
 2 {
 3     private boolean CGLIB$BOUND;
 4     private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
 5     private static final Callback[] CGLIB$STATIC_CALLBACKS;
 6     private MethodInterceptor CGLIB$CALLBACK_0;
 7     private static final Method CGLIB$g$0$Method;
 8     private static final MethodProxy CGLIB$g$0$Proxy;
 9     private static final Object[] CGLIB$emptyArgs;
10     private static final Method CGLIB$f$1$Method;
11     private static final MethodProxy CGLIB$f$1$Proxy;
12     
13     static void CGLIB$STATICHOOK1()
14     {
15       CGLIB$THREAD_CALLBACKS = new ThreadLocal();
16       CGLIB$emptyArgs = new Object[0];
17       Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
18       Class localClass2;
19       Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "g", "()V", "f", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.Target")).getDeclaredMethods());
20       CGLIB$g$0$Method = tmp60_57[0];
21       CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");
22       CGLIB$f$1$Method = tmp60_57[1];
23       CGLIB$f$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "f", "CGLIB$f$1");
25     }
26     
27     final void CGLIB$g$0()
28     {
29       super.g();
30     }
31     
32     public final void g()
33     {
34       MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
35       if (tmp4_1 == null)
36       {
37           CGLIB$BIND_CALLBACKS(this);
38           tmp4_1 = this.CGLIB$CALLBACK_0;
39       }
40       if (this.CGLIB$CALLBACK_0 != null) {
41           tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy);
42       }
43       else{
44           super.g();
45       }
46     }
47 }
複製程式碼

      代理類(Target$$EnhancerByCGLIB$$788444a0)繼承了目標類(Target),至於代理類實現的factory介面與本文無關,殘忍無視。代理類為每個目標類的方法生成兩個方法,例如針對目標類中的每個非private方法,代理類會生成兩個方法,以g方法為例:一個是@Override的g方法,一個是CGLIB$g$0(CGLIB$g$0相當於目標類的g方法)。我們在示例程式碼中呼叫目標類的方法t.g()時,實際上呼叫的是代理類中的g()方法。接下來我們著重分析代理類中的g方法,看看是怎麼實現的代理功能。

      當呼叫代理類的g方法時,先判斷是否已經存在實現了MethodInterceptor介面的攔截物件,如果沒有的話就呼叫CGLIB$BIND_CALLBACKS方法來獲取攔截物件,CGLIB$BIND_CALLBACKS的反編譯結果如下:

複製程式碼
private static final void CGLIB$BIND_CALLBACKS(java.lang.Object);
  Code:
   0:   aload_0
   1:   checkcast       #2; //class net/sf/cglib/test/Target$$EnhancerByCGLIB$$788444a0
   4:   astore_1
   5:   aload_1
   6:   getfield        #212; //Field CGLIB$BOUND:Z
   9:   ifne    52
   12:  aload_1
   13:  iconst_1
   14:  putfield        #212; //Field CGLIB$BOUND:Z
   17:  getstatic       #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal;
   20:  invokevirtual   #215; //Method java/lang/ThreadLocal.get:()Ljava/lang/Object;
   23:  dup
   24:  ifnonnull       39
   27:  pop
   28:  getstatic       #210; //Field CGLIB$STATIC_CALLBACKS:[Lnet/sf/cglib/proxy/Callback;
   31:  dup
   32:  ifnonnull       39
   35:  pop
   36:  goto    52
   39:  checkcast       #216; //class "[Lnet/sf/cglib/proxy/Callback;"
   42:  aload_1
   43:  swap
   44:  iconst_0
   45:  aaload
   46:  checkcast       #48; //class net/sf/cglib/proxy/MethodInterceptor
   49:  putfield        #36; //Field CGLIB$CALLBACK_0:Lnet/sf/cglib/proxy/MethodInterceptor;
   52:  return
複製程式碼

為了方便閱讀,等價的程式碼如下:

複製程式碼
private static final void CGLIB$BIND_CALLBACKS(Object o){
        Target$$EnhancerByCGLIB$$788444a0 temp_1 = (Target$$EnhancerByCGLIB$$788444a0)o;
        Object temp_2;
        Callback[] temp_3
        if(temp_1.CGLIB$BOUND == true){
            return;
        }
        temp_1.CGLIB$BOUND = true;
        temp_2 = CGLIB$THREAD_CALLBACKS.get();
        if(temp_2!=null){
            temp_3 = (Callback[])temp_2;
        }
        else if(CGLIB$STATIC_CALLBACKS!=null){
            temp_3 = CGLIB$STATIC_CALLBACKS;
        }
        else{
            return;
        }
        temp_1.CGLIB$CALLBACK_0 = (MethodInterceptor)temp_3[0];
        return;
    }
複製程式碼

CGLIB$BIND_CALLBACKS 先從CGLIB$THREAD_CALLBACKS中get攔截物件,如果獲取不到的話,再從CGLIB$STATIC_CALLBACKS來獲取,如果也沒有則認為該方法不需要代理。

那麼攔截物件是如何設定到CGLIB$THREAD_CALLBACKS 或者 CGLIB$STATIC_CALLBACKS中的呢?

在Jdk動態代理中攔截物件是在例項化代理類時由建構函式傳入的,在cglib中是呼叫Enhancer的firstInstance方法來生成代理類例項並設定攔截物件的。firstInstance的呼叫軌跡為:

  1. Enhancer:firstInstance
  2. Enhancer:createUsingReflection
  3. Enhancer:setThreadCallbacks
  4. Enhancer:setCallbacksHelper
  5. Target$$EnhancerByCGLIB$$788444a0 : CGLIB$SET_THREAD_CALLBACKS

 在第5步,呼叫了代理類的CGLIB$SET_THREAD_CALLBACKS來完成攔截物件的注入。下面我們看一下CGLIB$SET_THREAD_CALLBACKS的反編譯結果:

public static void CGLIB$SET_THREAD_CALLBACKS(net.sf.cglib.proxy.Callback[]);
  Code:
   0:   getstatic       #24; //Field CGLIB$THREAD_CALLBACKS:Ljava/lang/ThreadLocal;
   3:   aload_0
   4:   invokevirtual   #207; //Method java/lang/ThreadLocal.set:(Ljava/lang/Object;)V
   7:   return

在CGLIB$SET_THREAD_CALLBACKS方法中呼叫了CGLIB$THREAD_CALLBACKS的set方法來儲存攔截物件,在CGLIB$BIND_CALLBACKS方法中使用了CGLIB$THREAD_CALLBACKS的get方法來獲取攔截物件,並儲存到CGLIB$CALLBACK_0中。這樣,在我們呼叫代理類的g方法時,就可以獲取到我們設定的攔截物件,然後通過  tmp4_1.intercept(this, CGLIB$g$0$Method, CGLIB$emptyArgs, CGLIB$g$0$Proxy)  來實現代理。這裡來解釋一下intercept方法的引數含義:

@para1 obj :代理物件本身

@para2 method : 被攔截的方法物件

@para3 args:方法呼叫入參

@para4 proxy:用於呼叫被攔截方法的方法代理物件

這裡會有一個疑問,為什麼不直接反射呼叫代理類生成的(CGLIB$g$0)來間接呼叫目標類的被攔截方法,而使用proxy的invokeSuper方法呢?這裡就涉及到了另外一個點— FastClass 。

三、Fastclass 機制分析

     Jdk動態代理的攔截物件是通過反射的機制來呼叫被攔截方法的,反射的效率比較低,所以cglib採用了FastClass的機制來實現對被攔截方法的呼叫。FastClass機制就是對一個類的方法建立索引,通過索引來直接呼叫相應的方法,下面用一個小例子來說明一下,這樣比較直觀:

複製程式碼
public class test10 {
    public static void main(String[] args){
        Test tt = new Test();
        Test2 fc = new Test2();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}

class Test{
    public void f(){
        System.out.println("f method");
    }
    
    public void g(){
        System.out.println("g method");
    }
}
class Test2{
    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }
    
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }
}
複製程式碼

上例中,Test2是Test的Fastclass,在Test2中有兩個方法getIndex和invoke。在getIndex方法中對Test的每個方法建立索引,並根據入參(方法名+方法的描述符)來返回相應的索引。Invoke根據指定的索引,以ol為入參呼叫物件O的方法。這樣就避免了反射呼叫,提高了效率。代理類(Target$$EnhancerByCGLIB$$788444a0)中與生成Fastclass相關的程式碼如下:

Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
localClass2 = Class.forName("net.sf.cglib.test.Target");
CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");

MethodProxy中會對localClass1和localClass2進行分析並生成FastClass,然後再使用getIndex來獲取方法g 和 CGLIB$g$0的索引,具體的生成過程將在後續進行介紹,這裡介紹一個關鍵的內部類:

複製程式碼
 private static class FastClassInfo
    {
        FastClass f1; // net.sf.cglib.test.Target的fastclass
        FastClass f2; // Target$$EnhancerByCGLIB$$788444a0 的fastclass
        int i1; //方法g在f1中的索引
        int i2; //方法CGLIB$g$0在f2中的索引
    }
複製程式碼

MethodProxy 中invokeSuper方法的程式碼如下:

    FastClassInfo fci = fastClassInfo;
    return fci.f2.invoke(fci.i2, obj, args);

當呼叫invokeSuper方法時,實際上是呼叫代理類的CGLIB$g$0方法,CGLIB$g$0直接呼叫了目標類的g方法。所以,在第一節示例程式碼中我們使用invokeSuper方法來呼叫被攔截的目標類方法。

至此,我們已經瞭解cglib動態代理的工作原理,接下來會對cglib的相關原始碼進行分析。