详解Java动态代理机制(二)----cglib实现动态代理

     上篇文章的结尾我们介绍了普通的jdk实现动态代理的主要不足在于:它只能代理实现了接口的类,如果一个类没有继承于任何的接口,那么就不能代理该类,原因是我们动态生成的所有代理类都必须继承Proxy这个类,正是因为Java的单继承,所以注定会抛弃原类型的父类。而我们的cglib通过扫描该类以及其父类中所有的public非final修饰的方法,通过asm定义该类的子类字节码,其中该子类重写了父类所有的方法,然后返回该子类的实例作为代理类。也就是说我们的cglib是用该类的子类作为代理类来实现代理操作的。当然cglib的缺点也是呼之欲出,对于被代理类中的非public或者final修饰的方法,不能实现代理。

     在详细介绍cglib之前,我们先简单介绍下ASM框架,这是一个小而快的字节码处理框架,它负责生成从被代理类中扫描出的方法的字节码并将这些方法生成字节码暂存在内存中。然后我们通过方法将这些字节码转换成class类型,最后利用反射创建代理类的实例返回。下面看一个完整的实例,稍后从源代码的角度分析这个实例:

//定义一个接口
public interface MyInterface {
    public void sayHello();
}
//定义一个ClassB类
public class ClassB {
    public void welcome(){
        System.out.println("welcom walker");
    }
}

//模拟被代理的类,继承了ClassB和接口MyInterface
public class ClassA extends ClassB implements MyInterface {
    public void sayHello(){
        System.out.println("hello walker");
    }
}
//定义一个回调实例,稍后解释
public class MyMethod implements MethodInterceptor {

    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                            MethodProxy proxy) throws Throwable{
        proxy.invokeSuper(obj, args);
        return null;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {

        ClassA ca = new ClassA();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ClassA.class);
        enhancer.setCallback(new MyMethod());
        ClassA my = (ClassA)enhancer.create();
        my.welcome();
    }
}
输出结果:welcom walker

我们看到,此处我们获取了ClassA的代理对象,然后调用了ClassA父类中的welcome方法。这也间接证明了我们通过cglib代理了ClassA的父类中的方法,这一点使用jdk实现的动态处理是做不到的。下面我们解释原理。

在这之前,由于cglib是第三方库,所以我们需要下载相应的jar文件,主要包含两个文件,一个是cglib的jar,还有一个是cglib依赖的ASM框架的jar文件,注意这两个jar的版本不能冲突。

我们从main方法的主体代码中可以看出,Enhancer 类是创建代理实例的核心类。没错,该类负责整个代理对象的生命周期,它就像是一个工具一样,提供了很多方法帮助我们创建代理实例。首先我们调用了setSuperclass方法设置父类型,其实也就是将被代理对象传入,因为我们之前说过cglib创建的代理类是原对象的子类型,所以这里称原类型实例为父类也是合理的。

跟进去,我们看到:

public void setSuperclass(Class superclass)
  {
    if ((superclass != null) && (superclass.isInterface())) {
      setInterfaces(new Class[] { superclass });
    } else if ((superclass != null) && (superclass.equals(Object.class))) {
      this.superclass = null;
    } else {
      this.superclass = superclass;
    }
  }

这段代码的主要意思是:如果传入的类型是接口的话,保存在专门用于保存接口类型的变量中。

private Class[] interfaces;

如果传入的类型是Object类型的话,将用于保存普通类类型的变量赋值为null,否则保存该传入的参数的值在该变量中。这些操作过程中保存的一些数值是为了在最后create的时候提供帮助。

接下来是setCallback方法,该方法设置了回调。也就是将来对我们代理中方法的访问会转发到该回调中,所有自定义的回调类必须继承MethodInterceptor接口并实现其intercept方法,这一点和jdk的InvocationHandler类似。这里的intercept有几个参数:

  • Object obj:被代理的原对象
  • Method method:被调用的当前方法
  • Object[] args:该方法的参数集合
  • MethodProxy proxy:被调用方法的代理,它可以和method完成同样的事情,但是它使用FastClass机制非反射执行方法,效率高

我们对于所有调用代理方法的请求,转发到invokeSuper方法中,该方法源码如下:

//fastclassinfo类
    private static class FastClassInfo
  {
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
    
    private FastClassInfo() {}
    
    FastClassInfo(MethodProxy.1 x0)
    {
      this();
    }
  }
  
 
  public Object invokeSuper(Object obj, Object[] args)
    throws Throwable
  {
    try
    {
      init();
      FastClassInfo fci = this.fastClassInfo;
      return fci.f2.invoke(fci.i2, obj, args);
    }
    catch (InvocationTargetException e)
    {
      throw e.getTargetException();
    }
  }

其中fastclassinfo类中,几个参数的意思解释下,f1指向被代理对象,f2指向代理类对象,i1和i2分别是代理类中的该方法的两个索引。也就是这种fastclass机制并不是通过反射找到指定的方法的,而是在创建代理类的时候为其中的方法建立hash索引,这样调用的时候通过索引调用提高了效率。最后调用了代理类的方法,也就是重写了父类的方法。

最后也是最核心的一步是create方法的调用,这个方法才是实际创建代理实例的方法,我们看源码:

  public Object create()
  {
    this.classOnly = false;
    this.argumentTypes = null;
    return createHelper();
  }

该方法主要设置了两个参数配置,指定将要创建的对象不仅仅是一个类,指定参数为空。至于这两个参数有何作用,还需要往下追,我们看createHelper类:

  private Object createHelper()
  {
    validate();
    if (this.superclass != null) {
      setNamePrefix(this.superclass.getName());
    } else if (this.interfaces != null) {
      setNamePrefix(this.interfaces[ReflectUtils.findPackageProtected(this.interfaces)].getName());
    }
    return super.create(KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter, this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID));
  }

validate()方法主要对于一些参数进行校验,如果不符合创建实例的标准将抛出异常,我们可以简单的看一眼:

private void validate()
  {
    if ((this.classOnly ^ this.callbacks == null))
    {
      if (this.classOnly) {
        throw new IllegalStateException("createClass does not accept callbacks");
      }
      throw new IllegalStateException("Callbacks are required");
    }
    if ((this.classOnly) && (this.callbackTypes == null)) {
      throw new IllegalStateException("Callback types are required");
    }
    if ((this.callbacks != null) && (this.callbackTypes != null))
    {
      if (this.callbacks.length != this.callbackTypes.length) {
        throw new IllegalStateException("Lengths of callback and callback types array must be the same");
..........
..........
.........      
 }

主要还是判断回调是否指定,类型是否正确等,如果不符合创建条件就抛出异常。我们回去,接着就做了两个判断,用于指定被创建的代理类的名称,我们暂时不管他。又到了一个核心的方法,该方法将创建代理类并返回该类实例。首先我们看参数都是是什么意思,就一个参数,该参数是由KEY_FACTORY.newInstance方法返回的一个Object类型,我们看到在该方法的传入参数中,包括了父类类名或者接口名,回调类型,版本号等。该方法实际上返回了一个该代理类的一个唯一标识,这还不是关键,最关键的方法是这个create方法:

  protected Object create(Object key)
  {
    try
    {
      Class gen = null;
      synchronized (this.source)
      {
        ClassLoader loader = getClassLoader();
        Map cache2 = null;
        cache2 = (Map)this.source.cache.get(loader);
        if (cache2 == null)
        {
          cache2 = new HashMap();
          cache2.put(NAME_KEY, new HashSet());
          this.source.cache.put(loader, cache2);
        }
        else if (this.useCache)
        {
          Reference ref = (Reference)cache2.get(key);
          gen = (Class)(ref == null ? null : ref.get());
        }
        if (gen == null)
        {
          Object save = CURRENT.get();
          CURRENT.set(this);
          try
          {
            this.key = key;
            if (this.attemptLoad) {
              try
              {
                gen = loader.loadClass(getClassName());
              }
              catch (ClassNotFoundException e) {}
            }
            if (gen == null)
            {
              b = this.strategy.generate(this);
              String className = ClassNameReader.getClassName(new ClassReader(b));
              getClassNameCache(loader).add(className);
              gen = ReflectUtils.defineClass(className, b, loader);
            }
            if (this.useCache) {
              cache2.put(key, new WeakReference(gen));
            }
            byte[] b = firstInstance(gen);
            
            CURRENT.set(save);return b;
          }
          finally
          {
            CURRENT.set(save);
          }
        }
      }
      return firstInstance(gen);
//省去了异常捕获的代码块

如果usecache为为true表明该代理类已经在cache中了,直接返回引用即可。否则通过 this.strategy.generate(this);方法生成该代理类的字节码,然后通过通过类加载器加载该字节码生成class类型,最后通过firstInstance方法生成代理类的实例返回。

最后我们看一眼刚才生成的代理的源码:

//代码很多,此处贴出部分
public class ClassA$$EnhancerByCGLIB$$64984e8e extends ClassA
    implements Factory
{
    final void CGLIB$sayHello$0()
    {
            super.sayHello();
    }
    public final void sayHello()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$sayHello$0$Method;
        CGLIB$emptyArgs;
        CGLIB$sayHello$0$Proxy;
        intercept();
        return;
        super.sayHello();
        return;
    }

    final void CGLIB$welcome$1()
    {
        super.welcome();
    }

    public final void welcome()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$welcome$1$Method;
        CGLIB$emptyArgs;
        CGLIB$welcome$1$Proxy;
        intercept();
        return;
        super.welcome();
        return;
    }
....
....
}

从中我们看到,该类ClassA$$EnhancerByCGLIB$$64984e8e继承自ClassA,实现了接口factory。并且在其中我们看到不仅是父类ClassA中的方法sayHello在其中被重写了之外,ClassA的父类ClassB中的welcome方法也被重写了。足以见得,cglib利用继承的方式动态创建了被代理类的子类,通过ASM生成父类中所有public非final修饰的方法,实现了代理。

最后稍微小结下,cglib的实现代理的逻辑。首先我们通过Enhancer实例设置被代理类,然后设置该代理类的回调,也就是在访问代理类方法的时候会首先转向该回调,在回调中我们调用invokeSuper方法以fastclass这种非反射机制快速的调用到代理类中的方法,其中代理类中方法又调用原类型的对应方法。

由于cglib已经停止维护好多年,导致参考文档很少,学习难度很大,此篇文章也是作者研读jdk和网上优秀博文总结,不当之处,望大家指出,学习 !学习!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 之前介绍的反射和注解都是Java中的动态特性,还有即将介绍的动态代理也是Java中的一个动态特性。这些动态特性使得...
    Single_YAM阅读 2,044评论 0 9
  • 如何使用动态代理? 参照上面的例子,我们可以知道要实现动态代理需要做两方面的工作。 首先需要新建一个类,并且这个类...
    java部落阅读 2,100评论 1 16
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • 1.女孩 “明年四月我再来找你,你这段时间就乖乖呆着工作,好不好?”发送~女孩将手机握在胸口,手骨惨白。叮~“好,...
    七渺阅读 300评论 0 0