Java动态代理 jdk和cglib的实现比较

发现Java面试很喜欢问Spring AOP怎么实现的之类的问题,因此写一篇文章来整理一下。关于AOP和代理模式的概念这里并不作赘述,而是直奔主题,即AOP的实现方式:动态代理。与静态代理对比,动态代理是在runtime动态生成Java代理类,由代理类完成对具体方法的封装,实现AOP的功能。java

本文将分析Java中两种动态代理的实现方式,jdk proxycglib,比较它们的异同。本文并不会过多地分析jdk和cglib的源码去探究底层的实现细节,而只关注最后生成的代理类应该是什么样的,如何实现代理。只是我我的的整理和思考,和真正的jdk,cglib的产生的结果可能不尽相同,但从原理上来说是一致的。git

文章的最后也会探讨如何本身实现一个简单的动态代理,并提供我本身实现的简单版本,固然仅供参考。github

JDK Proxy

这是Java反射包java.lang.reflect提供的动态代理的方式,这种代理方式是彻底基于接口的。这里先给出一个简单的例子。面试

定义接口:安全

interface ifc {
  int add(int, int);
}

而后是接口ifc的实现类Real多线程

class Real implements ifc {
  @Override
  public int add(int x, int y) {
    return x + y;
  }

Real就是咱们须要代理的类,好比咱们但愿在调用add的先后打印一些log,这实际上就是AOP了。咱们须要最终产生一个代理类,实现一样的接口ifc,执行Real.add的功能,但须要增长一行新的打印语句。这一切对用户是透明的,用户只须要关心接口的调用。为了能在Real.add的周围添加额外代码,动态代理都是经过一种相似方法拦截器的东西来实现的,在Java Proxy里这就是InvocationHandler.并发

class Handler implements InvocationHandler {
  private final Real real;

  public Handler(Real real) {
    this.real = real;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws IllegalAccessException, IllegalArgumentException,
             InvocationTargetException {
    System.out.println("=== BEFORE ===");
    Object re = method.invoke(real, args);
    System.out.println("=== AFTER ===");
    return re;
  }
}

这里最关键的就是invoke方法,实际上代理类的add方法,以及其它方法(若是接口还定义了其它方法),最终都只是调用这个Handlerinvoke方法,由你来具体定义在invoke里须要作什么,一般就是调用真正实体类Real的方法,这里就是add,以及额外的AOP行为(打印 BEFORE 和 AFTER)。因此可想而知,代理类里必然是有一个InvocationHandler的实例的,全部的接口方法调用都会由这个handler实例来代理。ide

因此咱们应该能大概刻画出这个代理类的模样:函数

public ProxyClass implements ifc {
  private static Method mAdd;

  private InvocationHandler handler;

  static {
    Class clazz = Class.forName("ifc");
    mAdd = clazz.getMethod("add", int.class, int.class);
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)handler.invoke(this, mAdd, new Object[] {x, y});
  }
}

这个版本很是简单,但已足够实现咱们的要求。咱们来观察这个类,首先毋庸置疑它实现了ifc接口,这是代理模式的根本。它的add方法直接调用InvocationHandler实例的invoke方法,传入三个参数,第一个是代理类自己this指针,第二个是add方法的反射类,第三个是参数列表。因此在invoke方法里,用户就能自由定义它的行为实现AOP,全部这一切的桥梁就是InvocationHandler,它完成方法的拦截与代理。this

代理模式通常要求代理类中有一个真正类(被代理类)的实例,在这里也就是Real的实例,这样代理类才能去调用Real中本来的add方法。那Real在哪里呢?答案也是在InvocationHandler里。这与标准的代理模式相比,彷佛多了一层嵌套,不过这并无关系,只要这个代理的链条可以搭建起来,它就符合代理模式的要求。

注意到这里add方法的反射实例mAdd的初始化方式,咱们使用静态块static {...}来完成,只会被设置一次,而且不会有多线程问题。固然你也能够用懒加载等方式,不过就得考虑并发的安全性。

最后看一下JDK Proxy的具体使用:

Handler handler = new Handler(new Real());
ifc p = (ifc)Proxy.newProxyInstance(ifc.class.getClassLoader(),
                                    new Class[] {ifc},
                                    handler);
p.add(1, 2);

方法newProxyInstance就会动态产生代理类,而且返回给咱们一个实例,实现了ifc接口。这个方法须要三个参数,第一个ClassLoader并不重要;第二个是接口列表,即这个代理类须要实现那些接口,由于JDK的Proxy是彻底基于接口的,它封装的是接口的方法而不是实体类;第三个参数就是InvocationHandler的实例,它会被放置在最终的代理类中,做为方法拦截和代理的桥梁。注意到这里的handler包含了一个Real实例,这在上面已经说过是代理模式的必然要求。

总结一下JDK Proxy的原理,首先它是彻底面向接口的,其实这才是符合代理模式的标准定义的。咱们有两个类,被代理类Real和须要动态生成的代理类ProxyClass,都实现了接口ifc。类ProxyClass须要拦截接口ifc上全部方法的调用,而且最终转发到实体类Real上,这二者之间的桥梁就是方法拦截器InvocatioHandlerinvoke方法。

上面的例子里我给出类ProxyClass的源代码,固然实际上JDK Proxy是不会去产生源代码的,而是直接生成类的原始数据,它具体是怎么实现咱们暂时不讨论,咱们目前只须要关心这个类是什么样的,以及它实现代理的原理。

cglib实现动态代理

这是Spring使用的方式,与JDK Proxy不一样之处在于它不是面向接口的,而是基于类的继承。这彷佛是有点违背代理模式的标准格式,不过这没有关系,所谓的代理模式只是一种思想而不是严格的规范。咱们直接看它是如何使用的。

如今没有接口,咱们直接有实体类:

class Real {
  public int add(int x, int y) {
    return x + y;
  }
}

相似于InvocationHandler,这里cglib直接使用一个叫MethodInterceptor的类,顾名思义。

public class Interceptor implements MethodInterceptor {
  @Override
  public Object intercept(Object obj,
                          Method method,
                          Object[] args,
                          MethodProxy proxy) throws Throwable {
      System.out.println("=== BEFORE ===");
      Object re = proxy.invokeSuper(obj, args);
      System.out.println("=== AFTER ===");
      return re;
  }
}

使用方法:

public static void main(String[] args) {
  Enhancer eh = new Enhancer();
  eh.setSuperclass(Real.class);
  eh.setCallback(new Interceptor());

  Real r = (Real)eh.create();
  int result = r.add(1, 2);
}

若是你仔细和JDK Proxy比较,会发现它们实际上是相似的:

  1. 首先JDK Proxy提供interface列表,而cglib提供superclass供代理类继承,本质上都是同样的,就是提供这个代理类的签名,也就是对外表现为何类型。
  2. 而后是一个方法拦截器,JDK Proxy里是InvocationHandler,而cglib里通常就是MethodInterceptor,全部被代理的方法的调用是经过它们的invokeintercept方法进行转接的,AOP的逻辑也是在这一层实现。

它们不一样之处上面已经说了,就在于cglib生成的动态代理类是直接继承原始类的,因此咱们这里也能够大概刻画出这个代理类长什么样子:

public ProxyClass extends Real {
  private static Method mAdd;
  private static MethodProxy mAddProxy;

  private MethodInterceptor interceptor;

  static {
    Class clazz = Class.forName("ifc");
    mAdd = clazz.getMethod("add", int.class, int.class);
    // Some logic to generate mAddProxy.
    // ...
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)interceptor.invoke(
        this, mAdd, new Object[] {x, y}, mAddProxy);
  }
}

由于直接继承了Real,那天然就包含了Real的全部public方法,都经过interceptor.invoke进行拦截代理。这其实和上面JDK Proxy的原理是相似的,连invokeintercept方法的签名都差很少,第一个参数是this指针代理类自己,第二个参数是方法的反射,第三个参数是方法调用的参数列表。惟一不一样的是,这里多出一个MethodProxy,它是作什么用的?

若是你仔细看这里invoke方法内部的写法,当用户想调用原始类(这里是Real)定义的方法时,它必须使用:

Object re = proxy.invokeSuper(obj, args);

这里就用到了那个MethodProxy,那咱们为何不直接写:

Object re = method.invoke(obj, args);

答案固然是不能够,你不妨试一下,程序会进入一个无限递归调用。这里的缘由偏偏就是由于代理类是继承了原始类的,obj指向的就是代理类对象的实例,因此若是你对它使用method.invoke,因为多态性,就会又去调用代理类的add方法,继而又进入invoke方法,进入一个无限递归:

obj.add() {
  interceptor.invoke() {
    obj.add() {
      interceptor.invoke() {
        ...
      }
    }
  }
}

那我如何才能在interceptor.invoke()里去调用基类Realadd方法呢?固然一般作法是super.add(),然而这是在MethodInterceptor的方法里,并且这里的method调用必须经过反射完成,你并不能在语法层面上作到这一点。因此cglib封装了一个类叫MethodProxy帮助你,这也是为何那个方法的名字叫invokeSuper,代表它调用的是原始基类的真正方法。它到底是怎么办到的呢?你能够简单理解为,动态代理类里会生成这样一个方法:

int super_add(int x, int y) {
  return super.add(x, y);
}

固然你并不知道有这么一个方法,但invokeSuper会最终找到这个方法并调用,这都是在生成代理类时经过一系列反射的机制实现的,这里就不细展开了。

小结

以上我对比了JDK Proxycglib动态代理的使用方法和实现上的区别,它们本质上是相似的,都是提供两个最重要的东西:

  1. 接口列表或者基类,定义了代理类(固然也包括原始类)的签名。
  2. 一个方法拦截器,完成方法的拦截和代理,是全部调用链的桥梁。

须要说明的一点是,以上我给出的代理类ProxyClass的源代码,仅是参考性的最精简版本,只是为了说明原理,而不是JDK Proxycglib真正生成的代理类的样子,真正的代理类的逻辑要复杂的多,可是原理上基本是一致的。另外以前也说到过,事实上它们也不会生成源码,而是直接产生类的字节码,例如cglib是封装了ASM来直接生成Class数据的。

如何生成代理类

接下来的部分纯粹是实验性质的。既然知道了代理类长什么样,可能仍是有人会关心底层究竟如何在runtime动态生成这个类,这里我我的想了两种方案。

第一种方法是动态生成ProxyClass源码,而后动态编译,就能获得Class了。这里就须要利用反射,加上一系列字符串拼接,生成源码。若是你充分理解代理类应该长什么样,其实并非很难作到。那如何动态编译呢?你可使用JOOR,这是一个封装了javax.tools.JavaCompiler的库,帮助你方便地实现动态编译Java源代码。我试着写了一个Demo,纯粹是实验性质的。并且它有个重大问题,我不知道如何修改它编译使用的classpath,在默认状况下它没法引用到你本身定义的任何类,由于它们不在编译的classpath里,编译就不会经过,这实际上就使得这个代码生成器没有任何卵用。。。我强行经过修改System.setPropertyclasspath来添加个人class路径绕开了这个问题,然而这显然不是个解决根本问题的方法。

第二种方法更直接,就是生成类的字节码。这也是cglib使用的方法,它封装了ASM,这是一个能够用来直接操纵Class数据的库,经过它你就能够任意生成或修改你想要的Class,固然这须要你对虚拟机的字节码比较了解,才能玩得通这种比较黑科技的套路。这里我也写了一个Demo,也纯粹是实验而已,感兴趣的童鞋也能够本身试一下。写字节码仍是挺酸爽的,它相似汇编但其实比汇编容易的多。它不像汇编那样一下子寄存器一下子内存地址,一下子堆一下子栈,各类变量和地址绕来绕去。字节码的执行方式是很清晰的,变量都存储在本地变量表里,栈只是用来作函数调用,因此很是直观。

相关文章
相关标签/搜索