zoukankan      html  css  js  c++  java
  • cglib源码分析(四):cglib 动态代理原理分析

    本文分下面三个部分来分析cglib动态代理的原理。

    1. cglib 动态代理示例
    2. 代理类分析
    3. Fastclass 机制分析

     一、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的相关源码进行分析。

  • 相关阅读:
    视频直播和实时音视频区别调研
    MySQL5.7 并行复制
    MySQL5.7 并行复制
    SSH 超时设置
    有赞透明多级缓存解决方案(TMC)设计思路
    有赞透明多级缓存解决方案(TMC)设计思路
    spring.net异常处理
    python之for学习
    pyhton小方法
    时间戳处理
  • 原文地址:https://www.cnblogs.com/cruze/p/3865180.html
Copyright © 2011-2022 走看看