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

时间:2021-08-26 17:57:44

一、背景

今天有小伙伴面试的时候被问到: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类

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

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

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

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

(2)JDK 1.7

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

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

(3)JDK 1.8

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

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

经过多次试验,可以看出平均情况下的话,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