文章目录

  • 一、前言
  • 二、基本概念
  • 三、JDK 和 CGLib动态代理区别
    • 3.1 JDK动态代理具体实现原理
    • 3.2 CGLib动态代理
    • 3.3 两者对比
    • 3.4 使用注意
  • 四、JDK 和 CGLib动态代理性能对比-教科书上的描述
  • 五、使用层面:性能测试 + 模拟JDK动态代理 + 模拟Cglib动态代理
  • 六、原理层面:Fastclass机制
    • 6.1 cglib代理使用Fastclass机制
  • 七、面试金手指
    • 7.0 三种代理方式和两种动态代理方式
    • 7.1 jdk动态代理和cglib动态代理
      • 7.1.1 jdk动态代理和cglib动态代理实现
      • 7.1.2 jdk动态代理和cglib动态代理的优缺点
    • 7.2 cglib性能为什么比jdk动态代理性能高
    • 7.3 Fastclass机制描述下,哪些方法不能被动态代理
      • 7.3.1 什么是Fastclass机制?为什么cglib动态代理比jdk动态代理要快?
      • 7.3.2 Fastclass机制描述下,三个方法不能被动态代理
  • 八、小结

一、前言

Java三种代理模式和两种动态代理模式
Java的三种代理模式:静态代理 动态代理(动态代理也叫做:JDK代理,接口代理) Cglib代理
Java两种动态代理模式:JDK代理、CGLIB代理
Spring的AOP可以使用JDK代理,也可以使用CGLIB代理,前者基于接口,后者是基于子类。
JDK和Cglib实现动态代理优缺点分析
使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
使用Cglib动态原理,必须针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型。
从执行效率上看,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动态代理区别

3.1 JDK动态代理具体实现原理

通过实现InvocationHandlet接口创建自己的调用处理器;

通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;

通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;

通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

3.2 CGLib动态代理

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

3.3 两者对比

JDK动态代理是面向接口的。

CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。

3.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条描述的那样哪?下边我们做一些小实验分析一下!

五、使用层面:性能测试 + 模拟JDK动态代理 + 模拟Cglib动态代理

1、首先有几个Java类

Target.java 是代理类和实际类的共同接口
TargetImpl.java 实际类,实现了接口
JdkDynamicProxyTest.java JDK动态代理
CglibProxyTest.java Cglib动态代理
ProxyPerformanceTest.java main()方法测试运行

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 {@Overridepublic 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;   // 测试中注入接口引用targetprivate JdkDynamicProxyTest(Target target) {this.target = target;}// 新建Jdk动态代理对象public static Target newProxyInstance(Target target) {   // target实参return (Target) Proxy.newProxyInstance(JdkDynamicProxyTest.class.getClassLoader(),new Class<?>[]{Target.class},   // 这里一定是接口new JdkDynamicProxyTest(target));   // target实参用来初始化JDK动态代理构造函数}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(target, args);   // 使用反射的方式调用方法  // 又方法签名=方法名+参数列表// 三个参数  proxy 没用到  method 方法名 args 参数列表 // target newProxyInstance()方法中,构造函数新建JDK动态代理对象的时候由调用方main()方法确定}
}

JDK动态代理原理:
实现InvocationHandler接口,重写invoke()方法
使用Proxy.newProxyInstance() 新建动态代理对象
使用反射方式invoke() 调用方法

5、CglibProxyTest.java Cglib动态代理

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() {   // 这里就没有注入Target引用了}// 新建Cglib动态代理对象public static <T extends Target> Target newProxyInstance(Class<T> targetInstanceClazz) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(targetInstanceClazz);  // 实参为父类/父接口字节码enhancer.setCallback(new CglibProxyTest());   // 构建Cglib动态代理对象return (Target) enhancer.create();  // 返回}// 调用方法@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);   // 传递过来的代理调用方法// 方法签名=方法名+形参列表// 参数 obj// 参数 method  方法名// 参数 args  参数列表// 参数 proxy}}

Cglib动态代理原理:
实现MethodInterceptor 接口,重写intercept()方法
使用enhancer.create(); 新建动态代理对象
intercept()方法中,使用invokeSuper() 调用方法

注意:InvocationHandler 接口 和 MethodInterceptor 接口 都是 Callback 子接口,Callback接口没有方法,InvocationHandler接口是invoke()方法,MethodInterceptor 接口是intercept()方法。

6、ProxyPerformanceTest.java main()方法测试运行

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);  // Jdk动态代理Target cglibProxy = CglibProxyTest.newProxyInstance(TargetImpl.class);  // Cglib动态代理//预热一下  没什么用int preRunCount = 10000;  runWithoutMonitor(nativeTest, preRunCount);  // 两个参数的,没啥用runWithoutMonitor(cglibProxy, preRunCount);runWithoutMonitor(dynamicProxy, preRunCount);//执行测试Map<String, Target> tests = new LinkedHashMap<String, Target>();tests.put("Native   ", nativeTest);    // map类型的tests,里面放三个,第二层循环为3个   不使用代理tests.put("Dynamic  ", dynamicProxy);   //jdk代理tests.put("Cglib    ", cglibProxy);    // cglib代理int repeatCount = 3;  // 重复次数为3  第一层循环int runCount = 1000000;   // 第二层循环里面执行100万次runTest(repeatCount, runCount, tests);  runCount = 50000000;   // 第二层循环里面执行5000万次runTest(repeatCount, runCount, tests);}// runTest()方法,被main()方法调用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")));// 循环重复次数 repeatCount            for (int i = 0; i < repeatCount; i++) {System.out.println(String.format("\n--------- test : [%s] ---------", (i + 1)));    // 每一次循环都打印for (String key : tests.keySet()) {  // 对于map类型的tests调用runWithMonitor(tests.get(key), runCount, key);    // 调用三个参数的这个方法,可以打印执行时间}}}private static void runWithoutMonitor(Target target, int runCount) {  // 没有tag参数for (int i = 0; i < runCount; i++) {target.test(i);    // 执行runCount次,test()方法}}private static void runWithMonitor(Target target, int runCount, String tag) {  // 有tag参数long start = System.currentTimeMillis();  // 开始时间for (int i = 0; i < runCount; i++) {target.test(i);   // 执行runCount次,test()方法}long end = System.currentTimeMillis();   // 结束时间System.out.println("[" + tag + "] Total Time:" + (end - start) + "ms");  // 打印tag 和 所用时间(end-start)}
}

7、测试结果

(1)JDK 1.6


JDK6这种情况下,cglib代理比jdk代理快,当然,不使用代理才是最快的,毕竟少了一层。

(2)JDK 1.7


JDK7这种情况下,cglib代理比jdk代理快,当然,不使用代理才是最快的,毕竟少了一层。

(3)JDK 1.8


JDK8这种情况下,jdk代理比cglib代理快,当然,不使用代理才是最快的,毕竟少了一层。

干货(面试回答)

(1)在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距;

(2)在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了;

六、原理层面:Fastclass机制

Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。

问题:为什么cglib代理比jdk代理快?
Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。

6.1 cglib代理使用Fastclass机制

下面用一个小例子来说明一下,这样比较直观:

public class test10 {public static void main(String[] args){Test tt = new Test();    // main()方法 新建Test对象Test2 fc = new Test2();    //  新建Test2对象int index = fc.getIndex("f()V");  // Test2对象,调用getIndex()方法,根据哈希值,得到indexfc.invoke(index, tt, null);  // Test2对象,使用得到的index,调用方法}
}class Test{  // Test类两个方法,f()方法和g()方法public void f(){System.out.println("f method");}public void g(){System.out.println("g method");}
}
class Test2{   public Object invoke(int index, Object o, Object[] ol){Test t = (Test) o;  switch(index){case 1:t.f();  // invoke()方法中,调用f()方法,不是反射调用,直接调用,反射调用就速度慢了return null;case 2:t.g();   // invoke()方法中,调用g()方法,不是反射调用,直接调用,反射调用就速度慢了return null;}return null;}public int getIndex(String signature){  // 根据哈希值得到indexswitch(signature.hashCode()){case 3078479:   return 1;   case 3108270:return 2;}return -1;}
}

上例中,Test2是Test的Fastclass

FastClass有两个方法getIndex()和invoke()。
getIndex()方法:根据入参(方法名+方法的描述符),对Test的每个方法建立索引,并
返回。
invoke()方法:根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率。

代理类(

)中与生成Fastclass相关的代码如下:

Class localClass1 = Class.forName("net.sf.cglib.test.Target$$EnhancerByCGLIB$$788444a0");
localClass2 = Class.forName("net.sf.cglib.test.Target");
CGLIB$g$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "g", "CGLIB$g$0");

MethodProxy中会对localClass1和localClass2进行分析并生成FastClass,然后再使用getIndex来获取方法g 和 CGLIB$g$0的索引,具体的生成过程将在后续进行介绍,这里介绍一个关键的内部类:

 private static class FastClassInfo{FastClass f1; // net.sf.cglib.test.Target的fastclassFastClass f2; // Target$$EnhancerByCGLIB$$788444a0 的fastclassint i1; //方法g在f1中的索引int i2; //方法CGLIB$g$0在f2中的索引}

MethodProxy 中invokeSuper方法的代码如下:

FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);

当调用invokeSuper方法时,实际上是调用代理类的CGLIB$g0方法,CGLIB0方法,CGLIB0方法,CGLIBg$0直接调用了目标类的g方法。所以,在第一节示例代码中我们使用invokeSuper方法来调用被拦截的目标类方法。

七、面试金手指

7.0 三种代理方式和两种动态代理方式

Java三种代理模式和两种动态代理模式
Java的三种代理模式:静态代理 动态代理(动态代理也叫做:JDK代理,接口代理) Cglib代理
Java两种动态代理模式:JDK代理、CGLIB代理
Spring的AOP可以使用JDK代理,也可以使用CGLIB代理,前者基于接口,后者是基于子类。
JDK和Cglib实现动态代理优缺点分析
使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
使用Cglib动态原理,必须针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型。
从执行效率上看,Cglib动态代理效率较高。

7.1 jdk动态代理和cglib动态代理

7.1.1 jdk动态代理和cglib动态代理实现

JDK动态代理原理:
实现InvocationHandler接口,重写invoke()方法
使用Proxy.newProxyInstance() 新建动态代理对象
使用反射方式invoke() 调用方法

Cglib动态代理原理:
实现MethodInterceptor 接口,重写intercept()方法
使用enhancer.create(); 新建动态代理对象:步骤:
(1)创建Enhancer实例
(2)通过setSuperclass方法来设置目标类
(3)通过setCallback 方法来设置拦截对象
(4)create方法生成Target的代理类,并返回代理类的实例
intercept()方法中,使用invokeSuper() 调用方法

注意:InvocationHandler 接口 和 MethodInterceptor 接口 都是 Callback 子接口,Callback接口没有方法,InvocationHandler接口是invoke()方法,MethodInterceptor 接口是intercept()方法。

7.1.2 jdk动态代理和cglib动态代理的优缺点

jdk动态代理
具体来讲就三个步骤:
1.根据ClassLoader和Interface来获取接口类(前面已经讲了,类是由ClassLoader加载到JVM的,所以通过ClassLoader和Interface可以找到接口类)
2.获取构造对象;
3.通过构造对象和InvocationHandler生成实例,并返回,就是我们要的代理类。
Java动态代理优缺点:
优点:
1.Java本身支持,不用担心依赖问题,随着版本稳定升级;
2.代码实现简单;
缺点:
1.目标类必须实现某个接口,换言之,没有实现接口的类是不能生成代理对象的;
2.代理的方法必须都声明在接口中,否则,无法代理;
3.执行速度性能相对cglib较低;

cglib动态代理
Cglib原理:
1.通过字节码增强技术动态的创建代理对象;
2.代理的是代理对象的引用;
Cglib优缺点:
优点:
1.代理的类无需实现接口;
2.执行速度相对JDK动态代理较高;
缺点:
1.字节码库需要进行更新以保证在新版java上能运行;
2.动态创建代理对象的代价相对JDK动态代理较高;
Tips:
1.代理的对象不能是final关键字修饰的

7.2 cglib性能为什么比jdk动态代理性能高

分为两种情况:

(1)在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距;

(2)在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了

7.3 Fastclass机制描述下,哪些方法不能被动态代理

7.3.1 什么是Fastclass机制?为什么cglib动态代理比jdk动态代理要快?

FastClass有两个方法getIndex()和invoke()。
getIndex()方法:根据入参(方法名+方法的描述符),对Test的每个方法建立索引,并返回。
invoke()方法:根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率。

问题:为什么cglib代理比jdk代理快?
Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低;
cglib采用了FastClass的机制来实现对被拦截方法的调用,FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。

7.3.2 Fastclass机制描述下,三个方法不能被动态代理

Object类中
equals()方法、hashcode()方法、getClass()方法。

八、小结

两种代理方式,JDK和CGLib动态代理(三个问题),完成了

天天打码,天天进步!!!

谁与争锋,JDK动态代理大战CGLib动态代理相关推荐

  1. 你必须会的 JDK 动态代理和 CGLIB 动态代理

    来自:ytao 我们在阅读一些 Java 框架的源码时,基本上常会看到使用动态代理机制,它可以无感的对既有代码进行方法的增强,使得代码拥有更好的拓展性.通过从静态代理.JDK 动态代理.CGLIB 动 ...

  2. 什么是代理模式?代理模式有什么用?通过一个小程序分析静态代理和动态代理。自己简单实现动态代理。JDK动态代理和CGLIB动态代理的区别。

    1. 代理模式有什么用 ①功能增强,在实现目标功能的基础上,又增加了额外功能.就像生活中的中介一样,他跟两边客户会有私下的交流. ②控制访问,代理不让用户直接和目标接触.就像中间商一样,他们不会让我们 ...

  3. Java动态代理的两种实现方法:JDK动态代理和CGLIB动态代理

    Java动态代理的两种实现方法:JDK动态代理和CGLIB动态代理 代理模式 JDK动态代理 CGLIB动态代理 代理模式 代理模式是23种设计模式的一种,指一个对象A通过持有另一个对象B,可以具有B ...

  4. jdk动态代理与cglib动态代理例子

    1.JAVA的动态代理特征:特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代理类的对象 ...

  5. 利用代码分别实现jdk动态代理和cglib动态代理_面试之动态代理

    大家好!我是CSRobot,从今天开始,我将会发布一些技术文章,内容就是结合春招以来的面试所遇到的问题进行分享,首先会对知识点进行一个探讨和整理,在最后会给出一些面试题并作出解答,希望可以帮助到大家! ...

  6. JDK动态代理和CGLib动态代理简单演示

    JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...

  7. 一文理解JDK静态代理、JDK动态代理、Cglib动态代理

    代理模式 通过代理来访问真实的对象,而不是直接去访问真正干活的对象,比如二房东租房,二房是代理者,而一房东才是真正的房东:或者说生活中的中介.Spring中的AOP就是动态代理 适用场景 需要动态修改 ...

  8. JDK 动态代理与 CGLIB 动态代理,它俩真的不一样

    摘要:一文带你搞懂JDK 动态代理与 CGLIB 动态代理 本文分享自华为云社区<一文带你搞懂JDK 动态代理与 CGLIB 动态代理>,作者: Code皮皮虾 . 两者有何区别 1.Jd ...

  9. 浅谈Spring中JDK动态代理与CGLIB动态代理

    前言 Spring是Java程序员基本不可能绕开的一个框架,它的核心思想是IOC(控制反转)和AOP(面向切面编程).在Spring中这两个核心思想都是基于设计模式实现的,IOC思想的实现基于工厂模式 ...

最新文章

  1. MySQL数据单个数据太大,导入不进去
  2. 《3D数学基础》系列视频:这次,真的是广告!
  3. CH - 0502 七夕祭(思维+中位数优化+前缀和优化)
  4. pip强制更新包版本
  5. git maven 一键部署_jenkins+git+maven搭建自动化部署项目环境
  6. 手把手教你升级到MySQL 8.0
  7. 环境类sci期刊排名一区_这本国产SCI论文期刊今年首破5分,明年或超6分
  8. AE Dulik骨骼绑定脚本!
  9. Vue的8种通信方式
  10. 【bzoj3034】Heaven Cow与God Bull
  11. css怎么分开背景图片,css切背景图片(background-position)
  12. 【SAP Basis】SAP用户权限管理
  13. 学习ESP8266_11_系统软件定时器
  14. 现有论文和作者两个实体,论文实体的属性包括题目、期刊名称、年份、期刊号;作者实体的属性包括姓名、单位、地址;一篇论文可以有多个作者,且每一位作者写过多篇论文,在每一篇论文中有作者的顺序号。请完成以下操
  15. 下拉电阻阻值选多大?
  16. 关于utf-8和big5编码的问题
  17. 讲解关于编写跨平台Java程序时的注意事项 选择自 tiewen 的 Blog
  18. 职业生涯规划访谈记录关于计算机专业,计算机专业职业生涯人物访谈报告1500字...
  19. IBM X3850 X5 安装 windows 2008 enterprise 32
  20. Palm软件TOP10排行榜

热门文章

  1. [Oracle]-[recyclebin][索引]-回收站恢复的索引名称修改
  2. 第11章 菜单及其它资源
  3. 扩充C盘(将D盘的内存分给C盘)
  4. 小米手环3 NFC 自定义 门禁卡数据
  5. 怎么用计算机画爱心,怎么用cad画爱心
  6. 攻防世界 Misc 功夫再高也怕菜刀 参考大佬的wp
  7. 怎么在word里标上标和下标?
  8. 5G工业路由器 千兆高速低延时
  9. 变量和简单数据类型(化浮为整)
  10. python删除空值的行_python删除列为空的行的实现方法