本期来和小伙伴们分享这个 ​​Cglib 动态代理啦~ (~ ̄(OO) ̄)ブ​


文章概览

一.  基本介绍

二.  源码探索

三.  FastClass

四.  CGlib比JDK快?

五.  CGLIB和Jdk动态代理的区别

六.  ASM

七.  SpringAOP


基本介绍


CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。


1.先在 pom 文件中引入这个包

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.7</version>
</dependency>
复制代码

2.实现 MethodInterceptor 接口

代码如下, 这里实现 ​​MethodInterceptor​​ 接口,并重写 ​​intercept​​ 方法,感觉这一步和 JDK动态代理 差不多 😄

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理

3.增强调用

最后在客户端中进行增强调用即可~🐷

// Cglib 动态代理

// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(UserService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyInterceptor());
// 创建代理对象
UserService userService = (UserService)enhancer.create();
// 通过代理对象调用目标方法
userService.say();
userService.finalMethod();
UserService.staicMethod();
复制代码

4.结果

结果如下,可以发现只有 ​​say​​ 方法被增强了,被 ​​final​​ 或者 ​​static​​ 修饰的无法被代理

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_02


源码探索

在上面的代码中有配置这个字节码的生成👆 ,可以发现目录中多了个 ​​code​​ 目录,而且居然生成了三份字节码文件

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_03

查看第二个字节码文件,如下图👇

可以发现它 继承 了我们的被代理类 ​​UserService​​ ,其他两个文件则 继承 了 ​​FastClass​​ 类

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_04

至于其他两个文件怎么调用呢,我也不了解……

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_spring_05

哈哈哈 不过咱们 ​​debug​​ 一下就可以猜出来啦~

在 ​​say​​ 方法处打个断点🐷,然后可以看到这里在调用 ​​invoke​​ 方法时出现了这个 14

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_06

在这三个文件中,搜索后发现 只有第一个文件有这个 14 ,分析源码后可以发现这个 ​​var10000​​ 是表示第二个字节码文件

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_07

在第二个字节码文件中,​​CGLIB$say$0​​ 方法如下图红框中所示~ 。

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_08

点击进去会去到它继承的 ​​UserService​​ 类的 ​​say​​ 方法~

那么至于第三个字节码文件的作用嘛…… 就真的不知道了 哈哈😄

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_09


FastClass

1.那什么是 FastClass 机制呢?


FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法


2.在方法中调用另一个方法,会被增强几次呢?

突然奇想,哈哈😄

那么修改下方法,再重新运行下👇

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_10

可以看到这里出现了 两次

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_11

3.StackOverflowError

在拦截器 ​​MethodInterceptor​​ 的 ​​intercept​​ 方法中,我们除了使用方法代理类​MethodProxy​​ 的 ​​invokeSuper​​ 外,它还有一个 ​​invoke​​ 方法可以给我们使用。

但是,在我使用这个 ​​invoke​​ 方法时,却出现了 栈溢出 的情况! 如图🐖

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_12

这什么情况呀🐖

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_13

于是我仔细阅读了这个方法说明,发现这两个方法使用的描述不一样~

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_14

抱着试试看的心态,将 参数换为 原始对象 ,结果成功了! ,而且看结果,可以发现只代理了一次😄

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_15

4.第三个字节码文件

在出现了这个栈溢出后,我也尝试着 debug 了一下,结果居然有了意外的收获 哈哈~

可以看到这里是调用 f1 的,对应着我们第三个字节码文件! 而且调用方式也是一样,通过这个方法的下标~

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_spring_16

也是找到了如下的方法!

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_17

那么到此,这三个字节码文件的大致作用我们就了解啦~

哈哈 没想到一下这经历了这么多😄(从不知道 ——> 真不知道 ——> 大致知道 )

当然,这个框架的精髓还在最底层的 ​​ASM​​ ,这是一个小而快的字节码处理框架,用来转换字节码并生成新的类,是一个非常有意思的技术! (不过这我真不会了…… 哈哈哈 为啥总有种欠打的感觉~ 可能蕉太狼的表情包有毒 哈哈)

不过一些小技术要点,应用场景还是稍微了解过滴~(后面再介绍下)

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_18

5.方法代理 MethodProxy

仔细观赏上面后,我们可以发现这个 ​​MethodProxy​​ 是 ​​Cglib​​ 中非常重要的一环!

比如:


  1. ​MethodProxy​​ 中通过 ​​FastClassInfo​​ 保存 ​​FastClass​​ 和调用方法的 ​​index​​ 下标
  2. 只代理 非final 或者 非static 方法
  3. 如果需要对所有的方法进行增强调用,可以使用 ​​invokeSuper​​ (第一个参数为增强的对象)。
  4. 如果只想对调用的方法进行增强,不增强该方法内部使用到的同类中的其他方法,可以使用 ​​invoke​​ 方法 (第一个参数为原始对象


小图小结

老规矩,来画个小图小结下👇

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_19

注意,拦截方法时,会根据这个增强的对象进行选择 f1 ,f2 ; 他们是两个不同的 ​​FassClass​​ 文件(f2名字最长~)


  1. 如果传如的是代理对象,则会选择 f2
  2. 如果是原始对象,则会选择 f1
  3. 接着根据方法的下标 ​​index​​ 去文件中查找对应的方法,并进行调用~


CGlib比JDK快?


使用 ​​CGLiB​​ 实现动态代理,​​CGLib​​ 底层采用 ​​ASM​​ 字节码生成框架,使用字节码技术生成代理类, 在 ​​jdk6 ​​之前比使用 ​​Java ​​反射效率要高。唯一需要注意的是,​​CGLib ​​不能对声明为 ​​final​​ 的方法进行代理, 因为 ​​CGLib​​ 原理是动态生成被代理类的子类。

在 ​​jdk6​​、​​jdk7​​、​​jdk8​​ 逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率 高于 CGLIB代理效率。只有当进行大量调用的时候,​​jdk6​​ 和 ​​jdk7​​ 比 ​​CGLIB​​ 代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次 jdk版本升级JDK代理效率 都得到提升,而 CGLIB代理效率 确有点跟不上步伐。

​ —— ​​blog.csdn.net/m0_57711043…​



CGLIB和Jdk动态代理的区别

关于 JDK动态代理的可以查看上文👉 ​​Java代理模式和字节码的探索​


  1. Jdk动态代理 只能对实现了接口的类进行代理 ,而 CGLIB 只能代理非final 类,因为它要去生成代理类的子类(没有 Jdk动态代理 的局限性)
  2. Jdk动态代理 生成的字节码文件中,代理类都继承了 ​​Proxy​​ 类,并实现了接口,而且生成字节码的效率CGLIB 高(cglib 一式三份 ,jdk只有一份)
  3. Jdk动态代理 中有个显眼的变量 ​​h​​ ,​​debug​​ 时多留意下就可以了~
  4. CGLIB 底层使用 ​​ASM​​ 框架来操作字节码文件,同时利用 ​​FassClass​​ 机制,实现对方法的快速索引并直接调用,不用经过反射,而 Jdk动态代理 则采用反射API进行操作


ASM

介绍


ASM​ 是一个通用的 ​Java​ 字节码操作和分析框架


结合上面的 ​​cglib​​ 有这么一张图(来自网络)

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_20

​ASM​​ 的用途:


  1. AOP
  2. 结合 ​​javaagent​​ 可以实现 热部署,以及日志追踪(类似 ​​SkyWalking​​ )
  3. ​arthas​​ 的运行也是基于这个 ​​javaagent​​ 和 ​​ASM​​ 的,同时还使用到 ​​JVMTI(JVM Tool Interface)​

真就知道这点皮毛 哈哈哈

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_21


Spring AOP

嘿嘿,赶紧来看最后一点吧~ 在 ​​Spring​​ 中的使用

在 ​​Spring​​ 中,有下面这么两个代理类: ​​JdkDynamicAopProxy​​ 和 ​​CglibAopProxy​

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_22

​JdkDynamicAopProxy​

源码如下👇

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_23

​CglibAopProxy​

这里删去了很多代码,可以看到高亮的部分使用到了我们上面提到的 ​​enhancer​

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_24

有没有小伙伴好奇在 ​​Spring​​ 中 , 实现了​​MethodInterceptor​​ 的动态代理类中,是使用 ​​methodProxy​​ 的 ​​invoke​​ 还是 ​​invokeSuper​​ 方法呢?

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_25

答案就在这里~ 如图,这里前两个是用到了这个 ​​invoke​​ ,后面的几个是都没有用到 ​​invoke​​ 和 ​​invokeSuper​

目前

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_26

不过耐心点点看还是发现有用到这个 ​​invokeSuper​​ 的 , 比如

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_27

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_jdk动态代理_28

SpringAOP 小结

在 ​​Spring​​ 中:


  1. 对象实现接口,使用 JDK动态代理
  2. 对象没有实现接口,使用 Cglib动态代理
  3. 可以强制使用CGlib ,配置 @EnableAspectJAutoProxy(proxyTargetClass = true) 或者 spring.aop.proxy-target-class=true


总结

这次的内容比较多,就弄成了个思维导图啦,最主要的是 ​​cglib​​ 特点, ​​FassClass​​机制 以及 Cglib和JDK动态代理的对比 ,还有 ​​SpringAOP​​ 这四个,扩展的有这个 ​​ASM​​😄

小伙伴们如果需要这个思维导图可以直接在公众号后台回复 "代理模式2" 获取😝

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_29

下期预告: 通过一个实际项目,来和大家分享下这个 ​​Springboot​​ 中 ​​AOP​​ 失效的原因 😄

最后

CGLIB动态代理探索(ASM,Spring)#yyds干货盘点#_字节码_30

欢迎小伙伴们来一起探讨问题~


如果你觉得本篇文章还不错的话,那拜托再点点赞支持一下呀😝

让我们开始这一场意外的相遇吧!~

欢迎留言!谢谢支持!ヾ(≧▽≦*)o 冲冲冲!!

我是4ye 咱们下期应该……很快再见!! 😆