Cglib动态代理探索(ASM,Spring)

原创
2021/07/01 23:59
阅读数 16

本期来和小伙伴们分享这个 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动态代理 差不多 😄


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 修饰的无法被代理


源码探索

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

查看第二个字节码文件,如下图👇
可以发现它 继承 了我们的被代理类 UserService ,其他两个文件则 继承 FastClass

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


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

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

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


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

点击进去会去到它继承的 UserService 类的 say 方法~
那么至于第三个字节码文件的作用嘛……  就真的不知道了 哈哈😄



FastClass


1. 那什么是 FastClass 机制呢?
FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法

2. 在方法中调用另一个方法,会被增强几次呢?
突然奇想,哈哈😄
那么修改下方法,再重新运行下👇


可以看到这里出现了 两次


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

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

这什么情况呀🐖

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

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

4. 第三个字节码文件
在出现了这个 栈溢出 后,我也尝试着 debug 了一下,结果居然有了意外的收获 哈哈~
可以看到这里是调用 f1 的,对应着我们第三个字节码文件!而且调用方式也是一样,通过这个方法的下标~

也是找到了如下的方法!

那么到此,这三个字节码文件的大致作用我们就了解啦~
哈哈 没想到一下这经历了这么多😄(从不知道 ——> 真不知道 ——> 大致知道 )

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

5. 方法代理 MethodProxy
仔细观赏上面后,我们可以发现这个 MethodProxy Cglib 中非常重要的一环!
比如:
  1. MethodProxy 中通过 FastClassInfo 保存 FastClass 和调用方法的 index 下标
  2. 只代理 非final 或者 非static 方法
  3. 如果需要对所有的方法进行增强调用, 可以使用 invokeSuper (第一个参数为 增强的对象 )。
  4. 如果只想对调用的方法进行增强,不增强该方法内部使用到的同类中的其他方法,可以使用 invoke 方法  (第一个参数为 原始对象


小图小结


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

注意,拦截方法时,会根据这个增强的对象进行选择 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代理效率 确有点跟不上步伐。
—— https://blog.csdn.net/m0_57711043/article/details/117020684


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 有这么一张图(来自网络)

ASM 的用途:

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

真就知道这点皮毛 哈哈哈


Spring AOP


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

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

JdkDynamicAopProxy

源码如下👇

CglibAopProxy

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

SpringAOP 小结

Spring 中:

  1. 对象实现接口,使用 JDK动态代理

  2. 对象没有实现接口,使用 Cglib动态代理  

  3. 可以强制使用CGlib  ,配置 @EnableAspectJAutoProxy(proxyTargetClass = true) 或者 spring.aop.proxy-target-class=true



总结

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


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


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

最后



欢迎小伙伴们来一起探讨问题~
如果你觉得本篇文章还不错的话,那 拜托再点点赞 支持一下呀😝
让我们开始这一场意外的相遇吧!~
欢迎留言!谢谢支持!ヾ(≧▽≦*)o 冲冲冲!!
我是4ye 咱们下期应该……很快再见!! 😆

本文分享自微信公众号 - Java4ye(Java4ye)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
jdk
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部