动态代理之 CGLIB 动态代理

CGLIB 动态代理

之前我们详细介绍了 JDK 自身的 API 所提供的动态代理的实现,也说明了它存在的缺陷,那就是只能为接口中的方法做代理,被代理类自身和父类的方法都不能被代理。

CGLIB 就是为了解决这个问题而生的,高新能的,底层基于 ASM 框架的一个代码生成框架,它完美的解决了 JDK 版本的动态代理只能为接口中的方法做代理的问题。

例子

我们还是先来看一个例子。首先定义一个 Person 接口,它能够讲话和跑步,在定一个一个 Father 类,它定义了一个 sayHello 方法。

public class Father{
  public void sayHello(){
    System.out.println("hi somebody....");
  }
}

public interface Person{
  void speak();
  void run();
}

下面是我们的被代理类,它本身继承自 Father 类,并且实现 Person 接口。

public class Student extends Father implements Person{
  // student 类自己实现的方法
  public void study(){
    System.out.println("i can study....");
  }
  
  @Override
  public void speak(){
    System.out.println("i am a student, i can speak....");
  }
  
  @Override
  public void run(){
    System.out.println("i am a student, i can run....");
  }
}

定义一个 CGLIB 拦截器,这个拦截器有点像 JDK 动态代理中的 InvocationHandler。

public class MyMethodInterceptor implmenets MethodInterceptor{
  public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy){
    System.out.println("Before:" + method);
    Object object = proxy.invokeSuper(obj,arg);
    System.out.println("After:" + method);
    return object;
  }
}

CGLIB 创建的是被代理类的子类,可以强转为被代理类的类型。

public class Test{
  public static void main(String[] args){
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Student.class);
    enhancer.setCallback(new MyMethodInterceptor());
    Student stu = (Student)enhancer.create();
    stu.sayHello();
    stu.run();
    stu.study();
  }
}

所有方法都可以代理了。

原理

我们还是通过反编译来看看他的原理。

动态代理之 CGLIB 动态代理_第1张图片

  1. Student 是被代理类,生成代理类直接继承了被代理类,是它的子类。解决了 JDK 代理的缺陷。
  2. Factory 接口用于设置和获取回调,也就是我们的拦截器。

动态代理之 CGLIB 动态代理_第2张图片

程序里面通过反射获取了父类也就是被代理类的所有方法,包括父类的方法以及父类的父类和父类的接口的方法。

动态代理之 CGLIB 动态代理_第3张图片

重写了父类也就是被代理类的所有的方法。将当前方法的签名作为参数传入到拦截器中,这里也称拦截器为回调。

从这一点来看,JDK 动态代理和 CGLIB 动态代理是类似的,都需要依赖一个回调器,CGLIB 里面称为拦截器,JDK 中称为处理器。

还有一点需要注意,代理类中的每一个方法都具有两个版本,一个是原名重写的方法,另一个是不经过拦截器的对应方法。这是 CGLIB FastClass 机制的一个结果,FastClass 我们呆会介绍。

既然代理类中的所有方法都会交给拦截器处理,我们就能够知道拦截器里面的参数的意义了。

  • obj:代表代理类的实例对象。
  • method:当前调用方法的引用。
  • arg:调用该方法的参数。
  • 它代表着当前方法的引用,基于 FastClass 机制。

我们知道 Method 是基于反射来调用的,但是反射的效率总是要低于直接调用的,而 MethodProxy 基于 FastClass 机制对方法直接下标索引,并通过索引直接定位和调用方法,是一点性能上的提升。

FastClass

在拦截器中我们使用的是 MethodProxy 调用的方法,我们说过 MethodProxy 是基于 FastClass 机制对方法的直接下标索引。下面我们通过它来了解一下 FastClass。

MethodProxy 的源码:

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    MethodProxy proxy = new MethodProxy();
    proxy.sig1 = new Signature(name1, desc);
    proxy.sig2 = new Signature(name2, desc);
    proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
    return proxy;
}
  1. desc 代表是一个方法的描述符。
  2. c1 代表的是这个方法所属的类,就是被代理类。
  3. c2 代表的值往往是我们生成的代理类。
  4. name1 是委托方法中的方法名。
  5. name2 是代理类中该方法的方法名。

举个例子:

var1 = Class.forName("Main.Student");
var0 = Class.forName("Main.Student$$EnhancerByCGLIB$$56e20d66");
MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$3");

var1 是我们的被代理类,var0 是我们的代理类,[()V] 是 sayHello 方法的方法签名,CGLIB$sayHello$3 是 sayHello 方法在代理类中的方法名。

有了这几个参数,MethodProxy 就可以出实话一个 FastClassInfo。

private static class FastClassInfo {
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
    private FastClassInfo() {
    }
}

FastClass 内部包含一个 class 对象,并且会对其中所有方法进行索引标记,于是外部对于任意方法的调用只需要提供一个索引值,FastClass 就能快速定位到具体方法。

这里 f1 包装着被代理类,f2 包装着代理类。i1 是当前方法在 f1 中的索引值,i2 是当前方法在 f2 中的索引值。所以基于 FastClass 的调用很简单,invoke 方法指定一个索引即可,而不需要传统的反射方式了。

缺点

CGLIB 虽然解决了 JDK 动态代理的致命问题,它可以代理父类以及自身以及父接口中的方法,但是他也不是所有的方法都能代理。

CGLIB 需要继承被代理类,如果被代理类被 final 修饰,那就意味着,CGLIB 代理不了。或者说 final 修饰的方法也代理不了,因为 CGLIB 生成的代理类需要重写被代理类的方法,而被 final 修饰的方法是不与许被重写的。

你可能感兴趣的:(动态代理之 CGLIB 动态代理)