Spring AOP中的JDK和CGLib * 哪个效率更高?
作者:徐刘根 发布时间:2023-07-02 18:42:50
一、背景
今天有小伙伴面试的时候被问到:Spring AOP中JDK 和 CGLib * 哪个效率更高?
二、基本概念
首先,我们知道Spring AOP的底层实现有两种方式:一种是JDK * ,另一种是CGLib的方式。
自Java 1.3以后,Java提供了 * 技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了Spring的很多地方。
JDK * 主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑贬值在一起。
JDK * 的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建 * 实例哪?答案就是CGLib。
CGLib采用底层的字节码技术,全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
三、JDK 和 CGLib * 区别
1、JDK * 具体实现原理:
通过实现InvocationHandlet接口创建自己的调用处理器;
通过为Proxy类指定ClassLoader对象和一组interface来创建 * ;
通过反射机制获取 * 类的构造函数,其唯一参数类型就是调用处理器接口类型;
通过构造函数创建 * 类实例,构造时调用处理器对象作为参数参入;
JDK * 是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2、CGLib * :
CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被 * 的类,重写父类的方法,实现AOP面向切面编程呢。
3、两者对比:
JDK * 是面向接口的。
CGLib * 是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。
4、使用注意:
如果要被代理的对象是个实现类,那么Spring会使用JDK * 来完成操作(Spirng默认采用JDK * 实现机制);
如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现 * 。
四、JDK 和 CGLib * 性能对比-教科书上的描述
我们不管是看书还是看文章亦或是我那个上搜索参考答案,可能很多时候,都可以找到如下的回答:
关于两者之间的性能的话,JDK * 所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK * 对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:
1、CGLib所创建的 * 对象在实际运行时候的性能要比JDK * 高不少,有研究表明,大概要高10倍;
2、但是CGLib在创建对象的时候所花费的时间却比JDK * 要多很多,有研究表明,大概有8倍的差距;
3、因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib * ,反正,则比较适用JDK * 。
结果是不是如上边1、2、3条描述的那样哪?下边我们做一些小实验分析一下!
五、性能测试
1、首先有几个Java类
2、Target.java
package com.java.proxy.test;
public interface Target {
int test(int i);
}
3、TargetImpl.java
package com.java.proxy.test;
public class TargetImpl implements Target {
@Override
public int test(int i) {
return i + 1;
}
}
4、JdkDynamicProxyTest.java
package com.java.proxy.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkDynamicProxyTest implements InvocationHandler {
private Target target;
private JdkDynamicProxyTest(Target target) {
this.target = target;
}
public static Target newProxyInstance(Target target) {
return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(),
new Class<?>[]{Target.class},
new JdkDynamicProxyTest(target));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
5、CglibProxyTest.java
package com.java.proxy.test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyTest implements MethodInterceptor {
private CglibProxyTest() {
}
public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetInstanceClazz);
enhancer.setCallback(new CglibProxyTest());
return (Target) enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
}
6、ProxyPerformanceTest.java
package com.java.proxy.test;
import java.util.LinkedHashMap;
import java.util.Map;
public class ProxyPerformanceTest {
public static void main(String[] args) {
//创建测试对象
Target nativeTest = new TargetImpl();
Target dynamicProxy = JdkDynamicProxyTest.newProxyInstance(nativeTest);
Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class);
//预热一下
int preRunCount = 10000;
runWithoutMonitor(nativeTest, preRunCount);
runWithoutMonitor(cglibProxy, preRunCount);
runWithoutMonitor(dynamicProxy, preRunCount);
//执行测试
Map<String, Target> tests = new LinkedHashMap<String, Target>();
tests.put("Native ", nativeTest);
tests.put("Dynamic ", dynamicProxy);
tests.put("Cglib ", cglibProxy);
int repeatCount = 3;
int runCount = 1000000;
runTest(repeatCount, runCount, tests);
runCount = 50000000;
runTest(repeatCount, runCount, tests);
}
private static void runTest(int repeatCount, int runCount, Map<String, Target> tests) {
System.out.println(
String.format("\n===== run test : [repeatCount=%s] [runCount=%s] [java.version=%s] =====",
repeatCount, runCount, System.getProperty("java.version")));
for (int i = 0; i < repeatCount; i++) {
System.out.println(String.format("\n--------- test : [%s] ---------", (i + 1)));
for (String key : tests.keySet()) {
runWithMonitor(tests.get(key), runCount, key);
}
}
}
private static void runWithoutMonitor(Target target, int runCount) {
for (int i = 0; i < runCount; i++) {
target.test(i);
}
}
private static void runWithMonitor(Target target, int runCount, String tag) {
long start = System.currentTimeMillis();
for (int i = 0; i < runCount; i++) {
target.test(i);
}
long end = System.currentTimeMillis();
System.out.println("[" + tag + "] Total Time:" + (end - start) + "ms");
}
}
7、测试结果
(1)JDK 1.6
(2)JDK 1.7
(3)JDK 1.8
经过多次试验,可以看出平均情况下的话,JDK * 的运行速度已经逐渐提高了,在低版本的时候,运行的性能可能不如CGLib,但是在1.8版本中运行多次,基本都可以得到一致的测试结果,那就是JDK * 已经比CGLib * 快了!
但是JDK * 和CGLib * 的适用场景还是不一样的哈!
六、总结
最终的测试结果大致是这样的,在1.6和1.7的时候,JDK * 的速度要比CGLib * 的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK * 的速度已经比CGLib * 的速度快很多了,希望小伙伴在遇到这个问题的时候能够有的放矢!
Spring AOP中的JDK和CGLib * 关于这个知识点很重要,关于两者之间性能的对比经过测试实验已经有了一个初步的结果,以后再有人问你Spring AOP,不要简单的说JDK * 和CGLib这两个了,是时候的可以抛出来对两者之间区别的理解,是有加分的哦!
来源:https://blog.csdn.net/xlgen157387/article/details/82497594


猜你喜欢
- Java GC 机制与内存分配策略详解收集算法是内存回收的方 * ,垃圾收集器是内存回收的具体实现自动内存管理解决的是:给对象分配内存 以及
- 本文为大家分享了一个简单的android左滑删除控件,供大家参考,具体内容如下import android.animation.ValueA
- 本文介绍restTemplate基础用法。Java中get和post的用法请参考:一文带你搞懂Java中Get和Post的使用1 提供get
- 单例模式的介绍说到单例模式,大家第一反应应该就是——什么是单例模式?,从“单例”字面意思上理解为——一个类只有一个实例,所以单例模式也就是保
- 1. 父工程构建1.1 Maven项目搭建环境版本JDK1.8Maven3.6+Maven模板maven-archetype-size删除父
- 目录1. 定义排序列数组2. 修改表头点击事件3. 修改表格排序方法4. 修改后台传参实现思路也比较简单,只需要用一个数组来存放所有排序的列
- 最近这段时间一直在看Android,利用Listview去实现点赞功能,下面给大家介绍下基本思路。基本思路:进入界面–》获取数据–》 在Li
- 具体代码如下所示:/// <summary> /// 启用事务提交多条带参数的SQL语句 /// </summary>
- 简述Preference是Android的控件之一,相对来说我们用的比较少,但在系统应用的Settings设置应用模块中大部分由Prefer
- feign.codec.DecodeException异常在微服务项目使用Feign进行远程服务调用时出现该异常:feign.codec.D
- 基于Android的五子棋的开发,供大家参考,具体内容如下需求分析1 棋盘和棋子绘制2 按照五子棋的规则制定游戏胜负规则 3 鼠标
- 一、题目描述题目:同步锁出现的目的就是为了解决多线程安全问题。同步锁的几种方式synchronized1、同步代码块2、同步方法jdk1.5
- 1. F1弹出帮助文档先找个后缀是pdf、docx等格式的的帮助文档,将它放在项目的helpDoc下。设置其“复制到输出
- init和clinit区别①init和clinit方法执行时机不同init是对象构造器方法,也就是说在程序执行 new 一个对象调用该对象类
- 递归三要素:1、明确递归终止条件;2、给出递归终止时的处理办法;3、提取重复的逻辑,缩小问题规模。1、1+2+3+…+nimport jav
- 本文主要讲解MVP开发模式以及具体实例。一、简介MVP(Model View Presenter)模式是著名的MVC(Model View
- AutoCompleteTextView是一个具有自动补全功能的EditView,当用户输入数据后,AutoCompleteTextView
- C++ boost::asio编程-异步TCP大家好,我是异步方式和同步方式不同,我从来不花时间去等那些龟速的IO操作,我只是向系统说一声要
- 新建控制台程序CAStudy.在应用程序中,添加books.xml,belowAvg.xsl 代码分别如下:books.xml<?xm
- Unity 跑马灯抽奖效果实现代码,供大家参考,具体内容如下这边用到插件是NGUI+Dotween,思路简单说下:先排版,通过移动图片蒙版来