当前位置: 首页 > news >正文

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

一、背景

今天有小伙伴面试的时候被问到: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这两个了,是时候的可以抛出来对两者之间区别的理解,是有加分的哦!

参考文章:

1、https://blog.csdn.net/qq1723205668/article/details/56481476
2、https://blog.csdn.net/xiangbq/article/details/49794335
在这里插入图片描述

【视频福利】2T免费学习视频,搜索或扫描上述二维码关注微信公众号:Java后端技术(ID: JavaITWork)回复:1024,即可免费获取!内含SSM、Spring全家桶、微服务、MySQL、MyCat、集群、分布式、中间件、Linux、网络、多线程,Jenkins、Nexus、Docker、ELK等等免费学习视频,持续更新!

相关文章:

  • 2016百度之星 - 初赛(Astar Round2A)
  • 为什么需要分布式配置中心?
  • 线上出故障了!我慌得一匹!教大家如何应对在线故障!
  • mysql远程访问cannot connect(10038) 问题解决的过程
  • Spring Cloud技术栈还没有学完!Hystrix又双叒叕停止更新了!
  • 2018年博客之星评选,需要您宝贵的一票!非常感谢!
  • 用私有构造器或者枚举类型强化Singleton 属性
  • 阿里分布式事务框架GTS(Seata)开源啦!
  • 完了!生产事故!几百万消息在消息队列里积压了几个小时!
  • [原创]java WEB学习笔记18:java EE 中的MVC 设计模式(理论)
  • 阿里巴巴的26款超神Java开源项目!
  • window下使用vnc远程登录阿里云ECS/ubuntu图形界面
  • 看似简单的hashCode和equals面试题,竟然有这么多坑!
  • 2019年互联网高频Java面试题指南!互联网升职加薪方案!
  • FZU 2112 并查集、欧拉通路
  • 【node学习】协程
  • 【刷算法】从上往下打印二叉树
  • 230. Kth Smallest Element in a BST
  • HTTP请求重发
  • JavaScript设计模式之工厂模式
  • Java多线程(4):使用线程池执行定时任务
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • vue-cli3搭建项目
  • 对JS继承的一点思考
  • 关于字符编码你应该知道的事情
  • 每天一个设计模式之命令模式
  • 最近的计划
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • ​香农与信息论三大定律
  • #数学建模# 线性规划问题的Matlab求解
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (libusb) usb口自动刷新
  • (安卓)跳转应用市场APP详情页的方式
  • (八)Flask之app.route装饰器函数的参数
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (三)终结任务
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (四)鸿鹄云架构一服务注册中心
  • (一)RocketMQ初步认识
  • (原)本想说脏话,奈何已放下
  • (转)3D模板阴影原理
  • (转)jQuery 基础
  • (转)Linux下编译安装log4cxx
  • ***利用Ms05002溢出找“肉鸡
  • .net程序集学习心得
  • ;号自动换行
  • @Autowired 与@Resource的区别
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [ vulhub漏洞复现篇 ] JBOSS AS 5.x/6.x反序列化远程代码执行漏洞CVE-2017-12149