Spring AOP 之中的JDK 和 CGLib 动态代理哪个效率更高

对不同用法的一点比较研究

Posted by Haiming on September 26, 2019

无意之间看到了一篇文章,里面对于Spring AOP 之中 JDK 和 CGLib 二者动态代理哪个效率更高做了测试,于是在自己博客之中记一下笔记,算是兴趣的一点探究。

一、基本概念

首先,针对Spring 的两个特点:AOP和ROC之中的AOP,底层实现有两种方式:一种是 JDK 动态代理,一种是 CGLib 的方式。

自从 Java 1.3 开始,Java 提供了动态代理技术,允许在运行期创建接口的代理实例。

反射:主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

JDK 的动态代理主要涉及 java.lang.reflect 下面的 ProxyInvocationHandler, 其中InvocationHandler 是一个接口,可以通过实现该接口来定义横切逻辑,并通过反射机制来调用目标类的代码,这样就可以动态的将横切逻辑业务逻辑 编织在一起。

JDK 动态代理有一个限制,就是只可以为 接口 产生代理实例,而没有通过接口定义业务方法的类, 要通过 CGLib 来创建动态代理实例。

CGLib(Code Generation Library) 采用底层的字节码技术,其可以为类创建一个子类,在子类之中采用方法拦截的技术来拦截所有父类的方法的调用,并且顺势织入横切逻辑。

二、 JDK 和 CGLib 动态代理区别

2.1 JDK 动态代理实现原理

  1. 通过实现 InvocationHandler 接口创建自己的 调用处理器
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 Interface 来创建 动态代理
  3. 通过 反射 机制获取 动态代理类 的构造函数,其唯一参数类型,就是 调用处理器 的接口类型
  4. 通过 构造函数 创建 动态代理类 实例,构造时 调用处理器对象 作为参数传入

JDK 动态代理,是面向接口的代理模式,如果被代理目标没有接口,那么 Spring 就没有方法。Spring 通过 Java 的反射机制,生产被代理接口的新匿名实现类,重写了其中 AOP 的增强方法

2.2 CGLib 动态代理

CGLib 可以实现在运行期间动态扩展 Java 类, Spring 在运行期间,通过 CGLib 继承要被动态代理的类,重写父类的方法,实现 AOP 面向切面编程。

2.3 两者区别

  • JDK动态代理是面向接口的。
  • CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。

三、JDK 和 CGLib 动态代理的性能说明——教科书的叙述

我们不管是看书还是看文章亦或是我那个上搜索参考答案,可能很多时候,都可以找到如下的回答:

关于两者之间的性能的话,JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:

1、CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

2、但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

3、因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。

四、最后结果

代码在原文都有,我就不贴了。把图片贴上来,效果如下:

JDK 1.6

这里写图片描述

img

JDK 1.7

这里写图片描述

这里写图片描述

JDK 1.8

这里写图片描述

这里写图片描述

五、总结

最终的测试结果大致是这样的,在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,希望小伙伴在遇到这个问题的时候能够有的放矢!