三分钟快速了解 Cglib 动态代理
本期来和小伙伴们分享这个 Cglib
动态代理啦~ (~ ̄(OO) ̄)ブ
文章概览
一. 基本介绍
二. 源码探索
三. FastClass
四. CGlib 比 JDK 快?
五. CGLIB 和 Jdk 动态代理的区别
六. ASM
七. SpringAOP
基本介绍
CGLIB(Code Generation Library),是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。
1.先在 pom 文件中引入这个包
2.实现 MethodInterceptor 接口
代码如下, 这里实现 MethodInterceptor
接口,并重写 intercept
方法,感觉这一步和 JDK 动态代理 差不多 😄
3.增强调用
最后在客户端中进行增强调用即可~🐷
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
中非常重要的一环!
比如:
MethodProxy
中通过FastClassInfo
保存FastClass
和调用方法的index
下标只代理 非 final 或者 非 static 方法
如果需要对所有的方法进行增强调用,可以使用
invokeSuper
(第一个参数为增强的对象)。如果只想对调用的方法进行增强,不增强该方法内部使用到的同类中的其他方法,可以使用
invoke
方法 (第一个参数为原始对象)
小图小结
老规矩,来画个小图小结下👇
注意,拦截方法时,会根据这个增强的对象进行选择 f1 ,f2 ; 他们是两个不同的 FassClass
文件(f2 名字最长~)
如果传如的是代理对象,则会选择 f2 ,
如果是原始对象,则会选择 f1 ,
接着根据方法的下标
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代理模式和字节码的探索
Jdk 动态代理 只能对实现了接口的类进行代理 ,而 CGLIB 只能代理非 final 类,因为它要去生成代理类的子类(没有 Jdk 动态代理 的局限性)
Jdk 动态代理 生成的字节码文件中,代理类都继承了
Proxy
类,并实现了接口,而且生成字节码的效率比 CGLIB 高(cglib 一式三份 ,jdk 只有一份)Jdk 动态代理 中有个显眼的变量
h
,debug
时多留意下就可以了~CGLIB 底层使用
ASM
框架来操作字节码文件,同时利用FassClass
机制,实现对方法的快速索引并直接调用,不用经过反射,而 Jdk 动态代理 则采用反射 API 进行操作
ASM
介绍
ASM
是一个通用的Java
字节码操作和分析框架。
结合上面的 cglib
有这么一张图(来自网络)
ASM
的用途:
AOP
结合
javaagent
可以实现 热部署,以及日志追踪(类似SkyWalking
)arthas
的运行也是基于这个javaagent
和ASM
的,同时还使用到JVMTI(JVM Tool Interface)
真就知道这点皮毛 哈哈哈
Spring AOP
嘿嘿,赶紧来看最后一点吧~ 在 Spring
中的使用
在 Spring
中,有下面这么两个代理类: JdkDynamicAopProxy
和 CglibAopProxy
JdkDynamicAopProxy
源码如下👇
CglibAopProxy
这里删去了很多代码,可以看到高亮的部分使用到了我们上面提到的 enhancer
有没有小伙伴好奇在 Spring
中 , 实现了MethodInterceptor
的动态代理类中,是使用 methodProxy
的 invoke
还是 invokeSuper
方法呢?
答案就在这里~ 如图,这里前两个是用到了这个 invoke
,后面的几个是都没有用到 invoke
和 invokeSuper
目前
不过耐心点点看还是发现有用到这个 invokeSuper
的 , 比如
SpringAOP 小结
在 Spring
中:
对象实现接口,使用 JDK 动态代理
对象没有实现接口,使用 Cglib 动态代理
可以强制使用 CGlib ,配置
@EnableAspectJAutoProxy(proxyTargetClass = true)
或者spring.aop.proxy-target-class=true
总结
这次的内容比较多,就弄成了个思维导图啦,最主要的是 cglib
特点, FassClass
机制 以及 Cglib 和 JDK 动态代理的对比 ,还有 SpringAOP
这四个,扩展的有这个 ASM
😄
小伙伴们如果需要这个思维导图可以直接在公众号后台回复 "代理模式 2" 获取😝
下期预告: 通过一个实际项目,来和大家分享下这个 Springboot
中 AOP
失效的原因 😄
最后
欢迎小伙伴们来一起探讨问题~
如果你觉得本篇文章还不错的话,那拜托再点点赞支持一下呀😝
让我们开始这一场意外的相遇吧!~
欢迎留言!谢谢支持!ヾ(≧▽≦*)o 冲冲冲!!
我是 4ye 咱们下期应该……很快再见!! 😆
如果文章对您有所帮助,欢迎关注公众号 J a v a 4 y e 😆
版权声明: 本文为 InfoQ 作者【4ye】的原创文章。
原文链接:【http://xie.infoq.cn/article/9ab521e515aabb99b60caaf2f】。文章转载请联系作者。
评论