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
猜你喜欢
- 前言springboot提供了 spirng-boot-starter-test 以供开发者使用单元测试,在引入 spring-boot-s
- 一、问题重现1.配置文件spring: #DataSource数据源 datasource: &nbs
- java连接zookeeper实现zookeeperJava服务端连接Zookeeper,进行节点信息的获取,管理…整理成一个基本工具添加依
- Eclipse 最佳字体 推荐:步骤:Eclipse->Windows[窗口]->Preferences[首选项]->Ge
- java 中Spark中将对象序列化存储到hdfs摘要: Spark应用中经常会遇到这样一个需求: 需要将JAVA对象序列化并存储到HDFS
- 本文实例讲述了java生成xml格式文件的方法。分享给大家供大家参考,具体如下:这里演示利用Java生成xml格式文件Demo中所用到的ja
- 常见Bean后处理器的作用:public static void main(String[] args) { &
- 实现效果:奔溃的线程侠:(单线程)主线程正在处理刷新图片的请求时,无法再接受其他请求,从而陷入阻塞的死循环状态。绘制图片import jav
- 这段时间用到了QT的TCP通信,做了初步的学习与尝试,编写了一个客户端和服务器基于窗口通信的小例程。使用QT的网络套接字需要.pro文件中加
- 本文实例为大家分享了opencv实现轮廓高斯滤波平滑的具体代码,供大家参考,具体内容如下一个小测试的题目:在图像上点选,找到与点选处相邻的颜
- 将图片上传到webapp路径下 路径获取方式此方法获取到工程webapp文件夹下String contexPath= request.get
- 本文实例讲述了Java深度复制功能与用法。分享给大家供大家参考,具体如下:写在前面:什么是深度复制?在Java里面,在创建一个对象,我们通常
- 最近在做项目时需要对异常进行全局统一处理,主要是一些分类入库以及记录日志等,因为项目是基于Springboot的,所以去网络上找了一些博客文
- 前言在java Thread类中,我们会看到interrupt()、interrupted()及isInterrupted(),在大多数情况
- 嵌入式Servlet容器在Spring Boot中,默认支持的web容器有 Tomcat, Jetty, 和 Undertow1、原理分析那
- 本文实例讲述了.net的序列化与反序列化的实现方法。分享给大家供大家参考。具体方法如下:1.序列化与反序列化概述C#中如果需要:将一个结构很
- 本文将介绍如何通过Java程序来查找并高亮PDF中的文本。使用工具:Free Spire.PDF for Java(免费版)Jar文件获取及
- 获取e.printStackTrace()打印的信息某些情况下,我们需要获取应用打印的异常信息,这时就可以借助StringWriter和Pr
- 一、平衡二叉树的定义平衡二叉树是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1 。它是一种高度平衡的二叉排序树。意思是说,
- 自定义Starter命名规则注意artifactId的命名规则,Spring官方Starter通常命名为spring-boot-starte