无意之间看到了一篇文章,里面对于Spring AOP 之中 JDK 和 CGLib 二者动态代理哪个效率更高做了测试,于是在自己博客之中记一下笔记,算是兴趣的一点探究。
一、基本概念
首先,针对Spring 的两个特点:AOP和ROC之中的AOP,底层实现有两种方式:一种是 JDK 动态代理,一种是 CGLib 的方式。
自从 Java 1.3 开始,Java 提供了动态代理技术,允许在运行期创建接口的代理实例。
反射:主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
JDK 的动态代理主要涉及 java.lang.reflect 下面的 Proxy
和 InvocationHandler
, 其中InvocationHandler
是一个接口,可以通过实现该接口来定义横切逻辑,并通过反射机制来调用目标类的代码,这样就可以动态的将横切逻辑和业务逻辑 编织在一起。
JDK 动态代理有一个限制,就是只可以为 接口 产生代理实例,而没有通过接口定义业务方法的类, 要通过 CGLib 来创建动态代理实例。
CGLib(Code Generation Library) 采用底层的字节码技术,其可以为类创建一个子类,在子类之中采用方法拦截的技术来拦截所有父类的方法的调用,并且顺势织入横切逻辑。
二、 JDK 和 CGLib 动态代理区别
2.1 JDK 动态代理实现原理
- 通过实现
InvocationHandler
接口创建自己的 调用处理器 - 通过为
Proxy
类指定ClassLoader
对象和一组 Interface 来创建 动态代理 - 通过 反射 机制获取 动态代理类 的构造函数,其唯一参数类型,就是 调用处理器 的接口类型
- 通过 构造函数 创建 动态代理类 实例,构造时 调用处理器对象 作为参数传入
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
JDK 1.7
JDK 1.8
五、总结
最终的测试结果大致是这样的,在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了,希望小伙伴在遇到这个问题的时候能够有的放矢!